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