Browse Source
Multiform per-form failure statuses (formstatus records, noformmatch errorcode
classes + -formerrors) are now sound suffix-viability verdicts: 'incomplete'
(viable) iff every supplied word was validly consumed as a prefix of the form
and the failure is pure exhaustion at end-of-input; anything already supplied
contradicting the form reports 'invalid'. Mechanism: viability-probe mode on
private::get_dict_form suppressing only the position-guarded pure-exhaustion
raises (count min-shortfalls, missing-required checks, adhoc option value at
end) while final validation of the consumed words stands as the soundness
backstop; multiform candidacy and parse_status's single-form formstatus record
confirm each classify-'incomplete' failure with one probe re-parse. Rendered
noformmatch form lines marked '(viable - needs more arguments)'/'(not viable)'
after the stable "form '<fid>':" anchor. Consumer contract documented in the
parse ('Multiform failure contract') and parse_status argdocs. Documented
conservatism: input ending inside a multi-member type clause reports invalid.
New testsuite formviability.test (acceptance scenarios, option position
guards, single-form record soundness, clause conservatism, message marking);
forms.test verdict pins deliberately updated (ms/idle incomplete->invalid for
type-screen rejections). punkshell 0.41.3 (patch: refined multiform error
classification/reporting at the shell surface).
Goal G-152 activated (user-directed) and flipped achieved 2026-08-02 with
acceptance verified via runtests (args subtree green; full-suite parity with
stashed baseline); entry archived to GOALS-archive.md, detail file to
goals/archive/ with verification evidence; consumer pointers pushed to
G-044/G-150 notes.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
11 changed files with 451 additions and 26 deletions
@ -0,0 +1,246 @@
|
||||
package require tcltest |
||||
|
||||
package require punk::args |
||||
|
||||
#G-152 suffix-viability verdict characterization - added 2026-08-02. |
||||
#A multiform parse failure reports a per-form suffix-viability verdict: a form's |
||||
#status is 'incomplete' (VIABLE) if and only if every supplied word was validly |
||||
#consumed as a prefix of that form and the failure is pure exhaustion at |
||||
#end-of-input; anything already supplied contradicting the form yields 'invalid' |
||||
#(NOT viable - no appended words can satisfy it). The verdict rides the |
||||
#noformmatch errorcode classes, the -formerrors records and the parse_status |
||||
#formstatus records (single-form parses included), and the rendered noformmatch |
||||
#message marks each form line '(viable - needs more arguments)' or '(not viable)'. |
||||
#Contract documentation: the punk::args::parse and punk::args::parse_status |
||||
#argdoc help bodies. |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
#tool-shaped multiform fixture (the G-152 acceptance shape): literal action |
||||
#leaders; the info form requiring a value; the build form carrying an option |
||||
#and optional multiple values |
||||
punk::args::define { |
||||
@id -id ::testspace::vtool |
||||
@cmd -name testspace::vtool -summary "tool-shaped multiform" -help "tool-shaped multiform fixture" |
||||
@form -form list |
||||
@leaders -min 1 -max 1 |
||||
action -type string -choices {list} -optional 0 |
||||
@form -form info |
||||
@leaders -min 1 -max 1 |
||||
action -type string -choices {info} -optional 0 |
||||
@values -min 1 -max 1 |
||||
toolname -type string -optional 0 |
||||
@form -form build |
||||
@leaders -min 1 -max 1 |
||||
action -type string -choices {build} -optional 0 |
||||
@opts |
||||
-test -type boolean -default 1 |
||||
@values -min 0 -max -1 |
||||
toolname -type string -optional 1 -multiple 1 |
||||
@form -form test |
||||
@leaders -min 1 -max 1 |
||||
action -type string -choices {test} -optional 0 |
||||
@values -min 0 -max -1 |
||||
toolname -type string -optional 1 -multiple 1 |
||||
} |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_viable_form_leads {a valid literal leader alone: its form is viable (incomplete), ranked first; every sibling form is invalid}\ |
||||
-setup $common -body { |
||||
if {[catch {punk::args::parse {info} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} { |
||||
set classinfo [lindex [dict get $erroropts -errorcode] 2] |
||||
lappend result [lindex $classinfo 0] |
||||
set payload [lrange $classinfo 1 end] |
||||
lappend result [lindex [dict get $payload forms] 0] |
||||
set classes [dict get $payload classes] |
||||
lappend result [lindex $classes 0] [lsort -unique [lrange $classes 1 end]] |
||||
} else { |
||||
lappend result UNEXPECTED-parsed |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list noformmatch info incomplete invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_no_viable_form {a word satisfying no action leader: NO form is viable - the false 'incomplete' on value-carrying forms is gone}\ |
||||
-setup $common -body { |
||||
if {[catch {punk::args::parse {frobnicate} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} { |
||||
set classinfo [lindex [dict get $erroropts -errorcode] 2] |
||||
lappend result [lindex $classinfo 0] |
||||
lappend result [lsort -unique [dict get [lrange $classinfo 1 end] classes]] |
||||
} else { |
||||
lappend result UNEXPECTED-parsed |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list noformmatch invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_bad_flag_no_viable_form {a valid action leader followed by an unknown flag: no form is viable}\ |
||||
-setup $common -body { |
||||
if {[catch {punk::args::parse {build -bogus} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} { |
||||
set classinfo [lindex [dict get $erroropts -errorcode] 2] |
||||
lappend result [lindex $classinfo 0] |
||||
lappend result [lsort -unique [dict get [lrange $classinfo 1 end] classes]] |
||||
} else { |
||||
lappend result UNEXPECTED-parsed |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list noformmatch invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_empty_input_all_viable {no words supplied: every form is viable}\ |
||||
-setup $common -body { |
||||
set pstat [punk::args::parse_status {} withid ::testspace::vtool] |
||||
lappend result [dict get $pstat ok] [dict get $pstat status] |
||||
foreach f {list info build test} { |
||||
lappend result [dict get $pstat formstatus $f status] |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 0 incomplete incomplete incomplete incomplete incomplete] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_formerrors_and_formstatus_agree {the -formerrors errorcode records and the parse_status formstatus carry the same ranked per-form verdicts}\ |
||||
-setup $common -body { |
||||
if {[catch {punk::args::parse {info} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} { |
||||
set customdict [lrange [dict get $erroropts -errorcode] 3 end] |
||||
set formerrors [dict get $customdict -formerrors] |
||||
dict for {f finfo} $formerrors { |
||||
lappend result $f [dict get $finfo status] |
||||
} |
||||
} else { |
||||
lappend result UNEXPECTED-parsed |
||||
} |
||||
set pstat [punk::args::parse_status {info} withid ::testspace::vtool] |
||||
dict for {f finfo} [dict get $pstat formstatus] { |
||||
lappend result [dict get $finfo status] |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list info incomplete list invalid build invalid test invalid incomplete invalid invalid invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_success_path_verdicts {an auto-selected multiform parse reports sound verdicts for the non-matching forms}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {info punkzip} withid ::testspace::vtool] |
||||
lappend result [dict get $argd form] |
||||
lappend result [dict get $argd formstatus info status] |
||||
lappend result [dict get $argd formstatus build status] |
||||
lappend result [dict get $argd formstatus list status] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list info valid invalid invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_required_option_position_guard {a missing required option is viable only while no value words have begun}\ |
||||
-setup $common -body { |
||||
punk::args::define { |
||||
@id -id ::testspace::vreqopt |
||||
@cmd -name testspace::vreqopt -summary "required-option multiform" -help "required-option multiform fixture" |
||||
@form -form go |
||||
@leaders -min 1 -max 1 |
||||
action -type string -choices {go} -optional 0 |
||||
@opts |
||||
-dest -type string -optional 0 |
||||
@values -min 0 -max -1 |
||||
extra -type string -optional 1 -multiple 1 |
||||
@form -form stop |
||||
@leaders -min 1 -max 1 |
||||
action -type string -choices {stop} -optional 0 |
||||
} |
||||
#no value words yet: appending '-dest <val>' can still satisfy the go form |
||||
set pstat [punk::args::parse_status {go} withid ::testspace::vreqopt] |
||||
lappend result [dict get $pstat formstatus go status] |
||||
#a value word has begun: appended flag words would be consumed as values |
||||
set pstat [punk::args::parse_status {go sometarget} withid ::testspace::vreqopt] |
||||
lappend result [dict get $pstat formstatus go status] |
||||
set pstat [punk::args::parse_status {go -dest x} withid ::testspace::vreqopt] |
||||
lappend result [dict get $pstat formstatus go status] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine ::testspace::vreqopt 1 |
||||
}\ |
||||
-result [list incomplete invalid valid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_singleform_formstatus_sound {single-form parse_status: the formstatus record carries the viability verdict while the top-level status keeps the engine classification}\ |
||||
-setup $common -body { |
||||
punk::args::define { |
||||
@id -id ::testspace::vsingle |
||||
@cmd -name testspace::vsingle -summary "single-form fixture" -help "single-form fixture" |
||||
@values -min 2 -max 2 |
||||
num -type int -optional 0 |
||||
name -type string -optional 0 |
||||
} |
||||
#valid prefix (5 satisfies num) ending in exhaustion: viable |
||||
set pstat [punk::args::parse_status {5} withid ::testspace::vsingle] |
||||
lappend result [dict get $pstat status] [dict get $pstat formstatus _default status] |
||||
#word contradicts num's int type: engine classifies the allocation |
||||
#shortfall incomplete (documented) but the form is NOT viable |
||||
set pstat [punk::args::parse_status {notanint} withid ::testspace::vsingle] |
||||
lappend result [dict get $pstat status] [dict get $pstat failureclass] [dict get $pstat formstatus _default status] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine ::testspace::vsingle 1 |
||||
}\ |
||||
-result [list incomplete incomplete incomplete missingrequiredvalue invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_clause_partial_conservative {input ending inside a multi-member type clause reports invalid - documented conservatism of the verdict}\ |
||||
-setup $common -body { |
||||
punk::args::define { |
||||
@id -id ::testspace::vclausey |
||||
@cmd -name testspace::vclausey -summary "clause multiform" -help "multi-member clause fixture" |
||||
@form -form pair |
||||
@values -min 0 -max -1 |
||||
coord -type {int int} -optional 0 -multiple 1 |
||||
@form -form reset |
||||
@values -min 1 -max 1 |
||||
reset -type literal(reset) |
||||
} |
||||
#{5} could be completed to {5 6} - but clause allocation cannot affirm the |
||||
#partial words, so the contract reports invalid (conservative direction) |
||||
set pstat [punk::args::parse_status {5} withid ::testspace::vclausey] |
||||
lappend result [dict get $pstat formstatus pair status] |
||||
#complete clause parses; the counterpart form judges soundly |
||||
set pstat [punk::args::parse_status {5 6} withid ::testspace::vclausey] |
||||
lappend result [dict get $pstat formstatus pair status] [dict get $pstat formstatus reset status] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine ::testspace::vclausey 1 |
||||
}\ |
||||
-result [list invalid valid invalid] |
||||
|
||||
#added 2026-08-02 (agent, G-152) |
||||
test formviability_message_marks_viability {the rendered noformmatch message visibly distinguishes viable forms from impossible ones per form line}\ |
||||
-setup $common -body { |
||||
catch {punk::args::parse {info} -errorstyle minimal -caller vtool withid ::testspace::vtool} errmsg |
||||
lappend result [string match "*form 'info': (viable - needs more arguments)*" $errmsg] |
||||
lappend result [string match "*form 'list': (not viable)*" $errmsg] |
||||
lappend result [string match "*form 'build': (not viable)*" $errmsg] |
||||
lappend result [string match "*form 'test': (not viable)*" $errmsg] |
||||
#the form-line anchor consumers/tests rely on stays intact |
||||
lappend result [string match "*form 'info':*" $errmsg] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 1 1 1 1] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
Loading…
Reference in new issue