From a5e729083ee09f890872661730808ab8fdeac55b Mon Sep 17 00:00:00 2001 From: Noah Gardner Date: Sat, 31 Oct 2026 23:05:56 -0400 Subject: [PATCH 1/3] refactor: one file per guarantee under laws/ and proofs/ SPEC.md, src/LAWS.bend (4k lines) and src/PROOF.bend (11.5k lines) had to be read side by side, matched up by ID tags. Each guarantee is now laws/.bend, its prose on top and its quantified laws under it, proved by proofs/.bend. One LAWS.bend and PROOF.bend at the root only import them; the pair under src/ is gone, and the gate is `bend PROOF.bend`. A law that proves part of several guarantees has its own file, and each guarantee that rests on it says so. Definitions the laws share are under laws/defs/, lemmas several proofs use under proofs/lemmas/, each named for what it holds. Requirement IDs are gone with SPEC.md: a file's name is its guarantee's. SPEC.md's definitions (well-formed, plain word, current command, selected path, env value) head LAWS.bend. Of the trust boundary, the checker's soundness and the gate runner hold for every Bend project and are dropped; what IO.args and IO.get_env hand on, and that argv and env_vars pass it through, is said on src/args.bend. This needs a bolt that reads law and proof files by what LAWS.bend and PROOF.bend import, and no longer runs `trace`; bolt.bend drops `trace`. Co-Authored-By: Claude Opus 5.5 --- LAWS.bend | 136 + PROOF.bend | 95 + README.md | 9 +- SPEC.md | 126 - bolt.bend | 4 - laws/AllowedAny.bend | 7 + laws/ArgLineMark.bend | 20 + laws/ArgvDropsProgram.bend | 35 + laws/BindValuesAppend.bend | 12 + laws/BindingsComeFromWords.bend | 187 + laws/BuiltinsListed.bend | 54 + laws/CheckReadsReportsBadReads.bend | 123 + laws/CheckReportsSpecErrors.bend | 28 + laws/ChoiceRefused.bend | 38 + laws/ChoicesRestrictValues.bend | 12 + laws/ClusterDead.bend | 20 + laws/ClusterDone.bend | 10 + laws/DeadWalk.bend | 17 + laws/DefaultsFillUnbound.bend | 138 + laws/DoubleDashEndsOptions.bend | 26 + laws/DropZero.bend | 6 + laws/EnterMissing.bend | 30 + laws/EnterStep.bend | 34 + laws/EnvFallsBetweenWordsAndDefault.bend | 191 + laws/EnvValueOutsideChoicesFails.bend | 201 + laws/EnvVarsAskedInOrder.bend | 57 + laws/ErrTextEmptyForRequests.bend | 26 + laws/ErrTextNamesArgumentForm.bend | 120 + laws/ErrTextShowsSuggestion.bend | 12 + laws/ErrTextShowsUsage.bend | 46 + laws/FailFrom.bend | 13 + laws/FailureIsFinal.bend | 17 + laws/FlagLongValued.bend | 38 + laws/FlagRefusesValue.bend | 7 + laws/GetNil.bend | 7 + laws/GetReadsOwnBindings.bend | 82 + laws/HelpEndsWithBuiltinRows.bend | 15 + laws/HelpFollowsPath.bend | 38 + laws/HelpListsEveryArgument.bend | 90 + laws/HelpPage.bend | 9 + laws/HelpShowsEnvVar.bend | 37 + laws/HelpUnknown.bend | 25 + laws/HelpWalk.bend | 26 + laws/HelpWordAsksForHelp.bend | 141 + laws/HitAppend.bend | 11 + laws/Kept.bend | 33 + laws/KeptAll.bend | 34 + laws/KeptSet.bend | 11 + laws/KeptUnset.bend | 11 + laws/LastSnoc.bend | 9 + laws/LetterFlagAgain.bend | 30 + laws/LetterFlagEq.bend | 30 + laws/LetterOptAgain.bend | 30 + laws/LetterUnknown.bend | 20 + laws/LetterValue.bend | 35 + laws/LetterValueBad.bend | 34 + laws/LetterValueEq.bend | 33 + laws/LongArgStep.bend | 22 + laws/LongBinds.bend | 48 + laws/LongNoName.bend | 28 + laws/LongOptionSplitsAtEquals.bend | 32 + laws/LongRefused.bend | 41 + laws/LooksFlagLong.bend | 7 + laws/NoPosLeft.bend | 26 + laws/OnNil.bend | 7 + laws/OnceDead.bend | 11 + laws/OptionTakesNextWord.bend | 9 + laws/PlainWordIsNeverOption.bend | 37 + laws/PosBinds.bend | 45 + laws/PosNext.bend | 39 + laws/PositionalsBindInOrder.bend | 103 + laws/PrefixEmpty.bend | 6 + laws/RawBinds.bend | 40 + laws/RawNext.bend | 35 + laws/RawNoPos.bend | 22 + laws/RawRefused.bend | 33 + laws/Refused.bend | 20 + laws/RepeatedLongFlag.bend | 38 + laws/RepeatedLongOpt.bend | 38 + laws/RepeatsRefusedManyCollects.bend | 13 + laws/RequiredMissing.bend | 34 + laws/RequiredMustBind.bend | 50 + laws/RestTakesRemainingWords.bend | 149 + laws/ShortClusterReadsLetters.bend | 91 + laws/SpecErrWhere.bend | 30 + laws/StepDies.bend | 17 + laws/SubcommandResultsNavigate.bend | 77 + laws/SubcommandSelects.bend | 45 + laws/SuggestionPicksClosestLong.bend | 135 + laws/UnknownLong.bend | 32 + laws/UnknownLongDies.bend | 28 + laws/UnknownLongStep.bend | 24 + laws/UnknownShort.bend | 29 + laws/UnknownShortStep.bend | 22 + laws/UnknownSpellingRefused.bend | 11 + laws/ValueAbsent.bend | 21 + laws/ValueBinds.bend | 34 + laws/ValueFlagShaped.bend | 26 + laws/ValueRefused.bend | 27 + laws/VersionOfNamesRelease.bend | 33 + laws/VersionWordAsksForVersion.bend | 73 + laws/WalkAppend.bend | 10 + laws/defs/ArgumentLists.bend | 262 + laws/defs/BlockingPositional.bend | 9 + laws/defs/FreeWalker.bend | 16 + laws/defs/HelpPages.bend | 39 + laws/defs/HelpPathNames.bend | 21 + laws/defs/ValuesAtPath.bend | 15 + laws/defs/VersionRequest.bend | 12 + laws/defs/WalkerFailure.bend | 11 + main.bend | 2 +- proofs/AllowedAny.bend | 6 + proofs/ArgLineMark.bend | 52 + proofs/ArgvDropsProgram.bend | 20 + proofs/BindValuesAppend.bend | 6 + proofs/BindingsComeFromWords.bend | 4769 ++++++++ proofs/BuiltinsListed.bend | 57 + proofs/CheckReadsReportsBadReads.bend | 118 + proofs/CheckReportsSpecErrors.bend | 212 + proofs/ChoiceRefused.bend | 81 + proofs/ClusterDead.bend | 6 + proofs/ClusterDone.bend | 6 + proofs/DeadWalk.bend | 6 + proofs/DefaultsFillUnbound.bend | 462 + proofs/DoubleDashEndsOptions.bend | 8 + proofs/DropZero.bend | 6 + proofs/EnterMissing.bend | 72 + proofs/EnterStep.bend | 65 + proofs/EnvFallsBetweenWordsAndDefault.bend | 190 + proofs/EnvValueOutsideChoicesFails.bend | 122 + proofs/EnvVarsAskedInOrder.bend | 15 + proofs/ErrTextEmptyForRequests.bend | 29 + proofs/ErrTextNamesArgumentForm.bend | 177 + proofs/ErrTextShowsSuggestion.bend | 144 + proofs/ErrTextShowsUsage.bend | 90 + proofs/FailFrom.bend | 6 + proofs/FailureIsFinal.bend | 20 + proofs/FlagLongValued.bend | 82 + proofs/GetNil.bend | 6 + proofs/GetReadsOwnBindings.bend | 59 + proofs/HelpFollowsPath.bend | 17 + proofs/HelpListsEveryArgument.bend | 144 + proofs/HelpPage.bend | 23 + proofs/HelpShowsEnvVar.bend | 9 + proofs/HelpUnknown.bend | 46 + proofs/HelpWalk.bend | 6 + proofs/HelpWordAsksForHelp.bend | 112 + proofs/HitAppend.bend | 6 + proofs/Kept.bend | 6 + proofs/KeptAll.bend | 6 + proofs/KeptSet.bend | 6 + proofs/KeptUnset.bend | 6 + proofs/LastSnoc.bend | 6 + proofs/LetterFlagAgain.bend | 44 + proofs/LetterFlagEq.bend | 42 + proofs/LetterOptAgain.bend | 43 + proofs/LetterUnknown.bend | 14 + proofs/LetterValue.bend | 123 + proofs/LetterValueBad.bend | 38 + proofs/LetterValueEq.bend | 118 + proofs/LongArgStep.bend | 6 + proofs/LongBinds.bend | 146 + proofs/LongNoName.bend | 146 + proofs/LongOptionSplitsAtEquals.bend | 175 + proofs/LongRefused.bend | 142 + proofs/LooksFlagLong.bend | 6 + proofs/NoPosLeft.bend | 51 + proofs/OnNil.bend | 6 + proofs/OnceDead.bend | 6 + proofs/PlainWordIsNeverOption.bend | 20 + proofs/PosBinds.bend | 48 + proofs/PosNext.bend | 41 + proofs/PositionalsBindInOrder.bend | 161 + proofs/PrefixEmpty.bend | 6 + proofs/RawBinds.bend | 43 + proofs/RawNext.bend | 36 + proofs/RawNoPos.bend | 10 + proofs/RawRefused.bend | 62 + proofs/Refused.bend | 6 + proofs/RepeatedLongFlag.bend | 83 + proofs/RepeatedLongOpt.bend | 83 + proofs/RequiredMissing.bend | 181 + proofs/RequiredMustBind.bend | 288 + proofs/RestTakesRemainingWords.bend | 334 + proofs/ShortClusterReadsLetters.bend | 139 + proofs/SpecErrWhere.bend | 23 + proofs/StepDies.bend | 6 + proofs/SubcommandResultsNavigate.bend | 59 + proofs/SubcommandSelects.bend | 106 + proofs/SuggestionPicksClosestLong.bend | 121 + proofs/UnknownLong.bend | 39 + proofs/UnknownLongDies.bend | 6 + proofs/UnknownLongStep.bend | 6 + proofs/UnknownShort.bend | 64 + proofs/UnknownShortStep.bend | 6 + proofs/ValueAbsent.bend | 23 + proofs/ValueBinds.bend | 59 + proofs/ValueFlagShaped.bend | 47 + proofs/ValueRefused.bend | 36 + proofs/VersionOfNamesRelease.bend | 47 + proofs/VersionWordAsksForVersion.bend | 128 + proofs/WalkAppend.bend | 6 + proofs/lemmas/AppendedBindings.bend | 30 + proofs/lemmas/BindingsKept.bend | 364 + proofs/lemmas/ClusterEnd.bend | 158 + proofs/lemmas/ClusterSteps.bend | 55 + proofs/lemmas/EnvChecksNoArgs.bend | 19 + proofs/lemmas/FailedWalkThrough.bend | 208 + proofs/lemmas/FailedWalkerStays.bend | 9 + proofs/lemmas/FinishedWalker.bend | 175 + proofs/lemmas/HelpPathWalk.bend | 161 + proofs/lemmas/LastOfSnoc.bend | 10 + proofs/lemmas/NameAppend.bend | 17 + proofs/lemmas/StringAppendEmpty.bend | 10 + proofs/lemmas/WalkSteps.bend | 291 + src/.split_dropped.txt | 2 + src/LAWS.bend | 4025 ------- src/PROOF.bend | 11527 ------------------- src/args.bend | 30 +- src/check.bend | 2 +- 220 files changed, 16888 insertions(+), 15690 deletions(-) create mode 100644 LAWS.bend create mode 100644 PROOF.bend delete mode 100644 SPEC.md create mode 100644 laws/AllowedAny.bend create mode 100644 laws/ArgLineMark.bend create mode 100644 laws/ArgvDropsProgram.bend create mode 100644 laws/BindValuesAppend.bend create mode 100644 laws/BindingsComeFromWords.bend create mode 100644 laws/BuiltinsListed.bend create mode 100644 laws/CheckReadsReportsBadReads.bend create mode 100644 laws/CheckReportsSpecErrors.bend create mode 100644 laws/ChoiceRefused.bend create mode 100644 laws/ChoicesRestrictValues.bend create mode 100644 laws/ClusterDead.bend create mode 100644 laws/ClusterDone.bend create mode 100644 laws/DeadWalk.bend create mode 100644 laws/DefaultsFillUnbound.bend create mode 100644 laws/DoubleDashEndsOptions.bend create mode 100644 laws/DropZero.bend create mode 100644 laws/EnterMissing.bend create mode 100644 laws/EnterStep.bend create mode 100644 laws/EnvFallsBetweenWordsAndDefault.bend create mode 100644 laws/EnvValueOutsideChoicesFails.bend create mode 100644 laws/EnvVarsAskedInOrder.bend create mode 100644 laws/ErrTextEmptyForRequests.bend create mode 100644 laws/ErrTextNamesArgumentForm.bend create mode 100644 laws/ErrTextShowsSuggestion.bend create mode 100644 laws/ErrTextShowsUsage.bend create mode 100644 laws/FailFrom.bend create mode 100644 laws/FailureIsFinal.bend create mode 100644 laws/FlagLongValued.bend create mode 100644 laws/FlagRefusesValue.bend create mode 100644 laws/GetNil.bend create mode 100644 laws/GetReadsOwnBindings.bend create mode 100644 laws/HelpEndsWithBuiltinRows.bend create mode 100644 laws/HelpFollowsPath.bend create mode 100644 laws/HelpListsEveryArgument.bend create mode 100644 laws/HelpPage.bend create mode 100644 laws/HelpShowsEnvVar.bend create mode 100644 laws/HelpUnknown.bend create mode 100644 laws/HelpWalk.bend create mode 100644 laws/HelpWordAsksForHelp.bend create mode 100644 laws/HitAppend.bend create mode 100644 laws/Kept.bend create mode 100644 laws/KeptAll.bend create mode 100644 laws/KeptSet.bend create mode 100644 laws/KeptUnset.bend create mode 100644 laws/LastSnoc.bend create mode 100644 laws/LetterFlagAgain.bend create mode 100644 laws/LetterFlagEq.bend create mode 100644 laws/LetterOptAgain.bend create mode 100644 laws/LetterUnknown.bend create mode 100644 laws/LetterValue.bend create mode 100644 laws/LetterValueBad.bend create mode 100644 laws/LetterValueEq.bend create mode 100644 laws/LongArgStep.bend create mode 100644 laws/LongBinds.bend create mode 100644 laws/LongNoName.bend create mode 100644 laws/LongOptionSplitsAtEquals.bend create mode 100644 laws/LongRefused.bend create mode 100644 laws/LooksFlagLong.bend create mode 100644 laws/NoPosLeft.bend create mode 100644 laws/OnNil.bend create mode 100644 laws/OnceDead.bend create mode 100644 laws/OptionTakesNextWord.bend create mode 100644 laws/PlainWordIsNeverOption.bend create mode 100644 laws/PosBinds.bend create mode 100644 laws/PosNext.bend create mode 100644 laws/PositionalsBindInOrder.bend create mode 100644 laws/PrefixEmpty.bend create mode 100644 laws/RawBinds.bend create mode 100644 laws/RawNext.bend create mode 100644 laws/RawNoPos.bend create mode 100644 laws/RawRefused.bend create mode 100644 laws/Refused.bend create mode 100644 laws/RepeatedLongFlag.bend create mode 100644 laws/RepeatedLongOpt.bend create mode 100644 laws/RepeatsRefusedManyCollects.bend create mode 100644 laws/RequiredMissing.bend create mode 100644 laws/RequiredMustBind.bend create mode 100644 laws/RestTakesRemainingWords.bend create mode 100644 laws/ShortClusterReadsLetters.bend create mode 100644 laws/SpecErrWhere.bend create mode 100644 laws/StepDies.bend create mode 100644 laws/SubcommandResultsNavigate.bend create mode 100644 laws/SubcommandSelects.bend create mode 100644 laws/SuggestionPicksClosestLong.bend create mode 100644 laws/UnknownLong.bend create mode 100644 laws/UnknownLongDies.bend create mode 100644 laws/UnknownLongStep.bend create mode 100644 laws/UnknownShort.bend create mode 100644 laws/UnknownShortStep.bend create mode 100644 laws/UnknownSpellingRefused.bend create mode 100644 laws/ValueAbsent.bend create mode 100644 laws/ValueBinds.bend create mode 100644 laws/ValueFlagShaped.bend create mode 100644 laws/ValueRefused.bend create mode 100644 laws/VersionOfNamesRelease.bend create mode 100644 laws/VersionWordAsksForVersion.bend create mode 100644 laws/WalkAppend.bend create mode 100644 laws/defs/ArgumentLists.bend create mode 100644 laws/defs/BlockingPositional.bend create mode 100644 laws/defs/FreeWalker.bend create mode 100644 laws/defs/HelpPages.bend create mode 100644 laws/defs/HelpPathNames.bend create mode 100644 laws/defs/ValuesAtPath.bend create mode 100644 laws/defs/VersionRequest.bend create mode 100644 laws/defs/WalkerFailure.bend create mode 100644 proofs/AllowedAny.bend create mode 100644 proofs/ArgLineMark.bend create mode 100644 proofs/ArgvDropsProgram.bend create mode 100644 proofs/BindValuesAppend.bend create mode 100644 proofs/BindingsComeFromWords.bend create mode 100644 proofs/BuiltinsListed.bend create mode 100644 proofs/CheckReadsReportsBadReads.bend create mode 100644 proofs/CheckReportsSpecErrors.bend create mode 100644 proofs/ChoiceRefused.bend create mode 100644 proofs/ClusterDead.bend create mode 100644 proofs/ClusterDone.bend create mode 100644 proofs/DeadWalk.bend create mode 100644 proofs/DefaultsFillUnbound.bend create mode 100644 proofs/DoubleDashEndsOptions.bend create mode 100644 proofs/DropZero.bend create mode 100644 proofs/EnterMissing.bend create mode 100644 proofs/EnterStep.bend create mode 100644 proofs/EnvFallsBetweenWordsAndDefault.bend create mode 100644 proofs/EnvValueOutsideChoicesFails.bend create mode 100644 proofs/EnvVarsAskedInOrder.bend create mode 100644 proofs/ErrTextEmptyForRequests.bend create mode 100644 proofs/ErrTextNamesArgumentForm.bend create mode 100644 proofs/ErrTextShowsSuggestion.bend create mode 100644 proofs/ErrTextShowsUsage.bend create mode 100644 proofs/FailFrom.bend create mode 100644 proofs/FailureIsFinal.bend create mode 100644 proofs/FlagLongValued.bend create mode 100644 proofs/GetNil.bend create mode 100644 proofs/GetReadsOwnBindings.bend create mode 100644 proofs/HelpFollowsPath.bend create mode 100644 proofs/HelpListsEveryArgument.bend create mode 100644 proofs/HelpPage.bend create mode 100644 proofs/HelpShowsEnvVar.bend create mode 100644 proofs/HelpUnknown.bend create mode 100644 proofs/HelpWalk.bend create mode 100644 proofs/HelpWordAsksForHelp.bend create mode 100644 proofs/HitAppend.bend create mode 100644 proofs/Kept.bend create mode 100644 proofs/KeptAll.bend create mode 100644 proofs/KeptSet.bend create mode 100644 proofs/KeptUnset.bend create mode 100644 proofs/LastSnoc.bend create mode 100644 proofs/LetterFlagAgain.bend create mode 100644 proofs/LetterFlagEq.bend create mode 100644 proofs/LetterOptAgain.bend create mode 100644 proofs/LetterUnknown.bend create mode 100644 proofs/LetterValue.bend create mode 100644 proofs/LetterValueBad.bend create mode 100644 proofs/LetterValueEq.bend create mode 100644 proofs/LongArgStep.bend create mode 100644 proofs/LongBinds.bend create mode 100644 proofs/LongNoName.bend create mode 100644 proofs/LongOptionSplitsAtEquals.bend create mode 100644 proofs/LongRefused.bend create mode 100644 proofs/LooksFlagLong.bend create mode 100644 proofs/NoPosLeft.bend create mode 100644 proofs/OnNil.bend create mode 100644 proofs/OnceDead.bend create mode 100644 proofs/PlainWordIsNeverOption.bend create mode 100644 proofs/PosBinds.bend create mode 100644 proofs/PosNext.bend create mode 100644 proofs/PositionalsBindInOrder.bend create mode 100644 proofs/PrefixEmpty.bend create mode 100644 proofs/RawBinds.bend create mode 100644 proofs/RawNext.bend create mode 100644 proofs/RawNoPos.bend create mode 100644 proofs/RawRefused.bend create mode 100644 proofs/Refused.bend create mode 100644 proofs/RepeatedLongFlag.bend create mode 100644 proofs/RepeatedLongOpt.bend create mode 100644 proofs/RequiredMissing.bend create mode 100644 proofs/RequiredMustBind.bend create mode 100644 proofs/RestTakesRemainingWords.bend create mode 100644 proofs/ShortClusterReadsLetters.bend create mode 100644 proofs/SpecErrWhere.bend create mode 100644 proofs/StepDies.bend create mode 100644 proofs/SubcommandResultsNavigate.bend create mode 100644 proofs/SubcommandSelects.bend create mode 100644 proofs/SuggestionPicksClosestLong.bend create mode 100644 proofs/UnknownLong.bend create mode 100644 proofs/UnknownLongDies.bend create mode 100644 proofs/UnknownLongStep.bend create mode 100644 proofs/UnknownShort.bend create mode 100644 proofs/UnknownShortStep.bend create mode 100644 proofs/ValueAbsent.bend create mode 100644 proofs/ValueBinds.bend create mode 100644 proofs/ValueFlagShaped.bend create mode 100644 proofs/ValueRefused.bend create mode 100644 proofs/VersionOfNamesRelease.bend create mode 100644 proofs/VersionWordAsksForVersion.bend create mode 100644 proofs/WalkAppend.bend create mode 100644 proofs/lemmas/AppendedBindings.bend create mode 100644 proofs/lemmas/BindingsKept.bend create mode 100644 proofs/lemmas/ClusterEnd.bend create mode 100644 proofs/lemmas/ClusterSteps.bend create mode 100644 proofs/lemmas/EnvChecksNoArgs.bend create mode 100644 proofs/lemmas/FailedWalkThrough.bend create mode 100644 proofs/lemmas/FailedWalkerStays.bend create mode 100644 proofs/lemmas/FinishedWalker.bend create mode 100644 proofs/lemmas/HelpPathWalk.bend create mode 100644 proofs/lemmas/LastOfSnoc.bend create mode 100644 proofs/lemmas/NameAppend.bend create mode 100644 proofs/lemmas/StringAppendEmpty.bend create mode 100644 proofs/lemmas/WalkSteps.bend create mode 100644 src/.split_dropped.txt delete mode 100644 src/LAWS.bend delete mode 100644 src/PROOF.bend diff --git a/LAWS.bend b/LAWS.bend new file mode 100644 index 0000000..fb93dc0 --- /dev/null +++ b/LAWS.bend @@ -0,0 +1,136 @@ +# shake's laws. Each guarantee is one file under laws/, its prose on top and +# its quantified laws under it, proved by the file of the same name under +# proofs/; LAWS.bend and PROOF.bend only import them. The gate: the first line +# `bend PROOF.bend` prints is exactly `ALL PROOFS CHECK`. Tests and fixtures +# are never evidence for a guarantee; what shake cannot prove is said where it +# is assumed, on the code. +# +# Every guarantee is about the interface in main.bend: its types, builders, +# `parse`, `parse_env`, `check`, `check_reads`, the readers, `help`, +# `err_text`, `help_path`, `err_path`, `version_of`, `suggestion`, `argv` and +# `env_vars`. Every module under src/ is internal and carries no promise. +# +# A spec is well-formed when `check` reports nothing for it +# (laws/CheckReportsSpecErrors.bend). The parse guarantees hold for +# well-formed specs; a spec that contradicts itself, such as two options +# spelled `-n`, is its author's bug and not an input a user can give. Every +# error a guarantee names but `NeedHelp` and `NeedVersion` also carries `at`, +# the command path selected where the parse failed +# (laws/ErrTextShowsUsage.bend); the guarantees write it only where it +# matters. A plain word is `-`, or a word that does not start with `-`. The +# current command is the command whose arguments `parse` matches words +# against: the root, then each subcommand it selects. The selected path is the +# names of the subcommands selected, in order. A successful parse answers the +# root command's Matched, as clap answers `ArgMatches`: the bindings that +# command made while it was current and, when a subcommand was selected under +# it, that subcommand's name and its own Matched, and so on down the selected +# path. +# +# The words `parse` receives are what the program passes it. In a compiled +# program they come from `argv`, which drops the program name `IO.args` starts +# with, after the runtime has taken its own words: those between `+RTS` and +# `-RTS`, and a `--RTS` (trusted in src/args.bend). A `--` the user types +# reaches `parse`. +# +# An argument's env var is the name `env` gives it; an argument built without +# `env` has none. `parse_env` also receives vars, a list of name and value +# pairs, which in a compiled program come from `env_vars` (trusted in +# src/args.bend). An argument's env value is the value of the first pair in +# vars named by its env var, when that value is not empty; an env var no pair +# names, or whose first pair's value is empty, gives no env value. +# +# The reasoning behind each guarantee is in docs/rfc/shake-spec.md and +# docs/rfc/shake-env-and-suggestions.md, which name them by the requirement +# IDs they had before laws/. +import Base +import ./laws/AllowedAny.bend as AllowedAny +import ./laws/ArgLineMark.bend as ArgLineMark +import ./laws/ArgvDropsProgram.bend as ArgvDropsProgram +import ./laws/BindValuesAppend.bend as BindValuesAppend +import ./laws/BindingsComeFromWords.bend as BindingsComeFromWords +import ./laws/BuiltinsListed.bend as BuiltinsListed +import ./laws/CheckReadsReportsBadReads.bend as CheckReadsReportsBadReads +import ./laws/CheckReportsSpecErrors.bend as CheckReportsSpecErrors +import ./laws/ChoiceRefused.bend as ChoiceRefused +import ./laws/ClusterDead.bend as ClusterDead +import ./laws/ClusterDone.bend as ClusterDone +import ./laws/DeadWalk.bend as DeadWalk +import ./laws/DefaultsFillUnbound.bend as DefaultsFillUnbound +import ./laws/DoubleDashEndsOptions.bend as DoubleDashEndsOptions +import ./laws/DropZero.bend as DropZero +import ./laws/EnterMissing.bend as EnterMissing +import ./laws/EnterStep.bend as EnterStep +import ./laws/EnvFallsBetweenWordsAndDefault.bend as EnvFallsBetweenWordsAndDefault +import ./laws/EnvValueOutsideChoicesFails.bend as EnvValueOutsideChoicesFails +import ./laws/EnvVarsAskedInOrder.bend as EnvVarsAskedInOrder +import ./laws/ErrTextEmptyForRequests.bend as ErrTextEmptyForRequests +import ./laws/ErrTextNamesArgumentForm.bend as ErrTextNamesArgumentForm +import ./laws/ErrTextShowsSuggestion.bend as ErrTextShowsSuggestion +import ./laws/ErrTextShowsUsage.bend as ErrTextShowsUsage +import ./laws/FailFrom.bend as FailFrom +import ./laws/FailureIsFinal.bend as FailureIsFinal +import ./laws/FlagLongValued.bend as FlagLongValued +import ./laws/GetNil.bend as GetNil +import ./laws/GetReadsOwnBindings.bend as GetReadsOwnBindings +import ./laws/HelpFollowsPath.bend as HelpFollowsPath +import ./laws/HelpListsEveryArgument.bend as HelpListsEveryArgument +import ./laws/HelpPage.bend as HelpPage +import ./laws/HelpShowsEnvVar.bend as HelpShowsEnvVar +import ./laws/HelpUnknown.bend as HelpUnknown +import ./laws/HelpWalk.bend as HelpWalk +import ./laws/HelpWordAsksForHelp.bend as HelpWordAsksForHelp +import ./laws/HitAppend.bend as HitAppend +import ./laws/Kept.bend as Kept +import ./laws/KeptAll.bend as KeptAll +import ./laws/KeptSet.bend as KeptSet +import ./laws/KeptUnset.bend as KeptUnset +import ./laws/LastSnoc.bend as LastSnoc +import ./laws/LetterFlagAgain.bend as LetterFlagAgain +import ./laws/LetterFlagEq.bend as LetterFlagEq +import ./laws/LetterOptAgain.bend as LetterOptAgain +import ./laws/LetterUnknown.bend as LetterUnknown +import ./laws/LetterValue.bend as LetterValue +import ./laws/LetterValueBad.bend as LetterValueBad +import ./laws/LetterValueEq.bend as LetterValueEq +import ./laws/LongArgStep.bend as LongArgStep +import ./laws/LongBinds.bend as LongBinds +import ./laws/LongNoName.bend as LongNoName +import ./laws/LongOptionSplitsAtEquals.bend as LongOptionSplitsAtEquals +import ./laws/LongRefused.bend as LongRefused +import ./laws/LooksFlagLong.bend as LooksFlagLong +import ./laws/NoPosLeft.bend as NoPosLeft +import ./laws/OnNil.bend as OnNil +import ./laws/OnceDead.bend as OnceDead +import ./laws/PlainWordIsNeverOption.bend as PlainWordIsNeverOption +import ./laws/PosBinds.bend as PosBinds +import ./laws/PosNext.bend as PosNext +import ./laws/PositionalsBindInOrder.bend as PositionalsBindInOrder +import ./laws/PrefixEmpty.bend as PrefixEmpty +import ./laws/RawBinds.bend as RawBinds +import ./laws/RawNext.bend as RawNext +import ./laws/RawNoPos.bend as RawNoPos +import ./laws/RawRefused.bend as RawRefused +import ./laws/Refused.bend as Refused +import ./laws/RepeatedLongFlag.bend as RepeatedLongFlag +import ./laws/RepeatedLongOpt.bend as RepeatedLongOpt +import ./laws/RequiredMissing.bend as RequiredMissing +import ./laws/RequiredMustBind.bend as RequiredMustBind +import ./laws/RestTakesRemainingWords.bend as RestTakesRemainingWords +import ./laws/ShortClusterReadsLetters.bend as ShortClusterReadsLetters +import ./laws/SpecErrWhere.bend as SpecErrWhere +import ./laws/StepDies.bend as StepDies +import ./laws/SubcommandResultsNavigate.bend as SubcommandResultsNavigate +import ./laws/SubcommandSelects.bend as SubcommandSelects +import ./laws/SuggestionPicksClosestLong.bend as SuggestionPicksClosestLong +import ./laws/UnknownLong.bend as UnknownLong +import ./laws/UnknownLongDies.bend as UnknownLongDies +import ./laws/UnknownLongStep.bend as UnknownLongStep +import ./laws/UnknownShort.bend as UnknownShort +import ./laws/UnknownShortStep.bend as UnknownShortStep +import ./laws/ValueAbsent.bend as ValueAbsent +import ./laws/ValueBinds.bend as ValueBinds +import ./laws/ValueFlagShaped.bend as ValueFlagShaped +import ./laws/ValueRefused.bend as ValueRefused +import ./laws/VersionOfNamesRelease.bend as VersionOfNamesRelease +import ./laws/VersionWordAsksForVersion.bend as VersionWordAsksForVersion +import ./laws/WalkAppend.bend as WalkAppend diff --git a/PROOF.bend b/PROOF.bend new file mode 100644 index 0000000..b41a1bd --- /dev/null +++ b/PROOF.bend @@ -0,0 +1,95 @@ +# shake's proofs: each file under proofs/ fills the laws of the file of the +# same name under laws/. +import Base +import ./LAWS.bend as Laws +import ./proofs/AllowedAny.bend as PAllowedAny +import ./proofs/ArgLineMark.bend as PArgLineMark +import ./proofs/ArgvDropsProgram.bend as PArgvDropsProgram +import ./proofs/BindValuesAppend.bend as PBindValuesAppend +import ./proofs/BindingsComeFromWords.bend as PBindingsComeFromWords +import ./proofs/BuiltinsListed.bend as PBuiltinsListed +import ./proofs/CheckReadsReportsBadReads.bend as PCheckReadsReportsBadReads +import ./proofs/CheckReportsSpecErrors.bend as PCheckReportsSpecErrors +import ./proofs/ChoiceRefused.bend as PChoiceRefused +import ./proofs/ClusterDead.bend as PClusterDead +import ./proofs/ClusterDone.bend as PClusterDone +import ./proofs/DeadWalk.bend as PDeadWalk +import ./proofs/DefaultsFillUnbound.bend as PDefaultsFillUnbound +import ./proofs/DoubleDashEndsOptions.bend as PDoubleDashEndsOptions +import ./proofs/DropZero.bend as PDropZero +import ./proofs/EnterMissing.bend as PEnterMissing +import ./proofs/EnterStep.bend as PEnterStep +import ./proofs/EnvFallsBetweenWordsAndDefault.bend as PEnvFallsBetweenWordsAndDefault +import ./proofs/EnvValueOutsideChoicesFails.bend as PEnvValueOutsideChoicesFails +import ./proofs/EnvVarsAskedInOrder.bend as PEnvVarsAskedInOrder +import ./proofs/ErrTextEmptyForRequests.bend as PErrTextEmptyForRequests +import ./proofs/ErrTextNamesArgumentForm.bend as PErrTextNamesArgumentForm +import ./proofs/ErrTextShowsSuggestion.bend as PErrTextShowsSuggestion +import ./proofs/ErrTextShowsUsage.bend as PErrTextShowsUsage +import ./proofs/FailFrom.bend as PFailFrom +import ./proofs/FailureIsFinal.bend as PFailureIsFinal +import ./proofs/FlagLongValued.bend as PFlagLongValued +import ./proofs/GetNil.bend as PGetNil +import ./proofs/GetReadsOwnBindings.bend as PGetReadsOwnBindings +import ./proofs/HelpFollowsPath.bend as PHelpFollowsPath +import ./proofs/HelpListsEveryArgument.bend as PHelpListsEveryArgument +import ./proofs/HelpPage.bend as PHelpPage +import ./proofs/HelpShowsEnvVar.bend as PHelpShowsEnvVar +import ./proofs/HelpUnknown.bend as PHelpUnknown +import ./proofs/HelpWalk.bend as PHelpWalk +import ./proofs/HelpWordAsksForHelp.bend as PHelpWordAsksForHelp +import ./proofs/HitAppend.bend as PHitAppend +import ./proofs/Kept.bend as PKept +import ./proofs/KeptAll.bend as PKeptAll +import ./proofs/KeptSet.bend as PKeptSet +import ./proofs/KeptUnset.bend as PKeptUnset +import ./proofs/LastSnoc.bend as PLastSnoc +import ./proofs/LetterFlagAgain.bend as PLetterFlagAgain +import ./proofs/LetterFlagEq.bend as PLetterFlagEq +import ./proofs/LetterOptAgain.bend as PLetterOptAgain +import ./proofs/LetterUnknown.bend as PLetterUnknown +import ./proofs/LetterValue.bend as PLetterValue +import ./proofs/LetterValueBad.bend as PLetterValueBad +import ./proofs/LetterValueEq.bend as PLetterValueEq +import ./proofs/LongArgStep.bend as PLongArgStep +import ./proofs/LongBinds.bend as PLongBinds +import ./proofs/LongNoName.bend as PLongNoName +import ./proofs/LongOptionSplitsAtEquals.bend as PLongOptionSplitsAtEquals +import ./proofs/LongRefused.bend as PLongRefused +import ./proofs/LooksFlagLong.bend as PLooksFlagLong +import ./proofs/NoPosLeft.bend as PNoPosLeft +import ./proofs/OnNil.bend as POnNil +import ./proofs/OnceDead.bend as POnceDead +import ./proofs/PlainWordIsNeverOption.bend as PPlainWordIsNeverOption +import ./proofs/PosBinds.bend as PPosBinds +import ./proofs/PosNext.bend as PPosNext +import ./proofs/PositionalsBindInOrder.bend as PPositionalsBindInOrder +import ./proofs/PrefixEmpty.bend as PPrefixEmpty +import ./proofs/RawBinds.bend as PRawBinds +import ./proofs/RawNext.bend as PRawNext +import ./proofs/RawNoPos.bend as PRawNoPos +import ./proofs/RawRefused.bend as PRawRefused +import ./proofs/Refused.bend as PRefused +import ./proofs/RepeatedLongFlag.bend as PRepeatedLongFlag +import ./proofs/RepeatedLongOpt.bend as PRepeatedLongOpt +import ./proofs/RequiredMissing.bend as PRequiredMissing +import ./proofs/RequiredMustBind.bend as PRequiredMustBind +import ./proofs/RestTakesRemainingWords.bend as PRestTakesRemainingWords +import ./proofs/ShortClusterReadsLetters.bend as PShortClusterReadsLetters +import ./proofs/SpecErrWhere.bend as PSpecErrWhere +import ./proofs/StepDies.bend as PStepDies +import ./proofs/SubcommandResultsNavigate.bend as PSubcommandResultsNavigate +import ./proofs/SubcommandSelects.bend as PSubcommandSelects +import ./proofs/SuggestionPicksClosestLong.bend as PSuggestionPicksClosestLong +import ./proofs/UnknownLong.bend as PUnknownLong +import ./proofs/UnknownLongDies.bend as PUnknownLongDies +import ./proofs/UnknownLongStep.bend as PUnknownLongStep +import ./proofs/UnknownShort.bend as PUnknownShort +import ./proofs/UnknownShortStep.bend as PUnknownShortStep +import ./proofs/ValueAbsent.bend as PValueAbsent +import ./proofs/ValueBinds.bend as PValueBinds +import ./proofs/ValueFlagShaped.bend as PValueFlagShaped +import ./proofs/ValueRefused.bend as PValueRefused +import ./proofs/VersionOfNamesRelease.bend as PVersionOfNamesRelease +import ./proofs/VersionWordAsksForVersion.bend as PVersionWordAsksForVersion +import ./proofs/WalkAppend.bend as PWalkAppend diff --git a/README.md b/README.md index 51af918..3f48e63 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,9 @@ does it for you. shake needs bend 2.0.32 or later for that reason. `suggestion`, `argv` and `env_vars`. Everything under `src/` is internal and may change in any release; import only `main.bend`. -[SPEC.md](SPEC.md) lists what shake guarantees, and which of it is proved. +[LAWS.bend](LAWS.bend) lists what shake guarantees: one file under +[laws/](laws/) each, proved by the file of the same name under +[proofs/](proofs/). `check(spec)` lists every way a spec contradicts itself (a rest positional that is not the last, a repeated name or spelling, a subcommand named @@ -174,8 +176,9 @@ bin/demo.bin add 2 3 --times 2 ## Layout - `main.bend`: the interface. -- `src/`: the implementation, with `src/LAWS.bend` stating the parser's laws - and `src/PROOF.bend` proving them. `nix flake check` runs the proof gate. +- `src/`: the implementation. +- `laws/` and `proofs/`: one file per guarantee and its proof, imported by + `LAWS.bend` and `PROOF.bend`. `nix flake check` runs the proof gate. - `examples/demo/`: a small program that uses only `main.bend`. - `ez.toml` and `ez.lock.toml`: the ledger and lock; bolt, the linter, is pinned there as `[tools.bolt]`, and `nix flake check` runs it. diff --git a/SPEC.md b/SPEC.md deleted file mode 100644 index 2748b77..0000000 --- a/SPEC.md +++ /dev/null @@ -1,126 +0,0 @@ -# shake specification - -This is the list of every behavior shake guarantees, each under a stable requirement ID. Every requirement is about the interface in `main.bend`: its types, builders, `parse`, `parse_env`, `check`, `check_reads`, the readers, `help`, `err_text`, `help_path`, `err_path`, `version_of`, `suggestion`, `argv` and `env_vars`. Every module under `src/` is internal and carries no promise. - -Every requirement has one of two levels. A **Proved** requirement holds for every input, and is backed by a quantified law (a `for` or `exs` binder) in `src/LAWS.bend` that passes the proof gate. A **Trusted** requirement is an assumption shake cannot check from inside its own gate, and it is listed in the trust boundary below. A Proved requirement whose laws have not all landed has status **pending**: we intend to prove it, and until then it is not guaranteed. The proof gate is this check: the first line `bend src/PROOF.bend` prints is exactly `ALL PROOFS CHECK`. Tests and fixtures are never evidence for a requirement. - -A spec is **well-formed** when `check` reports nothing for it (SHAKE-SPEC-1). The parse requirements hold for well-formed specs; a spec that contradicts itself, such as two options spelled `-n`, is its author's bug and not an input a user can give. Every error a row names but `NeedHelp` and `NeedVersion` also carries `at`, the command path selected where the parse failed (SHAKE-ERR-2); rows write it only where it matters. A **plain word** is `-`, or a word that does not start with `-`. The **current command** is the command whose arguments `parse` matches words against: the root, then each subcommand it selects. The **selected path** is the names of the subcommands selected, in order. A successful parse answers the root command's **Matched**, as clap answers `ArgMatches`: the bindings that command made while it was current and, when a subcommand was selected under it, that subcommand's name and its own Matched, and so on down the selected path. - -The words `parse` receives are what the program passes it. In a compiled program they come from `argv`, which drops the program name `IO.args` starts with, after the runtime has taken its own words: those between `+RTS` and `-RTS`, and a `--RTS` (SHAKE-TRUST-2). A `--` the user types reaches `parse`. - -An argument's **env var** is the name `env` gives it; an argument built without `env` has none. `parse_env` also receives **vars**, a list of name and value pairs, which in a compiled program come from `env_vars` (SHAKE-TRUST-5). An argument's **env value** is the value of the first pair in vars named by its env var, when that value is not empty; an env var no pair names, or whose first pair's value is empty, gives no env value. - -The reasoning behind each requirement, the verdict of each against the code at `b93357a`, and the decisions that shaped them are in [docs/rfc/shake-spec.md](docs/rfc/shake-spec.md). The RFC also records the audit behind them and the rollout that proved every row. The env and suggestion rows (SHAKE-PARSE-11, PARSE-12, HELP-3, ERR-3, ERR-4, ARGS-2, TRUST-5) are designed in [docs/rfc/shake-env-and-suggestions.md](docs/rfc/shake-env-and-suggestions.md). - -## Format - -A requirement table is any table whose header row is exactly `| ID | Requirement | Level | Status | Law |`. An ID is uppercase segments joined by hyphens, at least two (`[A-Z][A-Z0-9]*(-[A-Z0-9]+)+`), unique within the requirement tables, and never reused once released. Level is `Proved` or `Trusted`. Status is `proved` or `pending` for a Proved row and empty for a Trusted row. A Law cell holds ` ` entries, paths relative to this file, separated by `; `. A proved row names one or more laws, and together they prove it. A pending row may name laws that each prove part of it; the row stays pending until its requirement is proved in full, and "Left to prove" says what is missing. - -A law proves a requirement when a comment line `# `, alone on its line, sits in the unbroken comment block directly above its `law` line. A law may carry several tags, one per line: - -``` -# LAW: a request for help has no error text -# SHAKE-ERR-1 -law err_text_help: -``` - -A tag may name a proved or a pending requirement, never a Trusted one or an ID no requirement table lists. bolt's `trace` rule checks all of this over the whole tree, and its `closed` rule rejects a law with no binder; both are errors in `bolt.bend`. - -## Requirements - -### How a word is read (SHAKE-TOK) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-TOK-1 | A word that starts with `--`, is not exactly `--`, and has at least one char before its first `=` is a long option: the chars between `--` and the first `=` (or the end) are its spelling, and when there is an `=` everything after it, verbatim and possibly empty, is its value. A word `--=...` is refused as `UnknownFlag{word}`. | Proved | proved | src/LAWS.bend long_no_name; src/LAWS.bend long_binds; src/LAWS.bend cut_first_eq; src/LAWS.bend cut_no_eq | -| SHAKE-TOK-2 | A word that starts with `-`, is not `-` and does not start with `--` is a cluster of short options, read letter by letter: a letter that spells a flag binds it and the next letter goes on; a letter that spells an option takes the rest of the word as its value, after one `=` when the rest starts with it (verbatim, and possibly empty), or the next word when nothing is left; a letter no argument of the current command spells refuses the whole word as `UnknownFlag{word}`, except that a word `-h` may be a request for help (SHAKE-PARSE-8) and a word `-V` a request for the version (SHAKE-VER-1). | Proved | proved | src/LAWS.bend short_step; src/LAWS.bend letter_flag; src/LAWS.bend letter_value; src/LAWS.bend letter_value_eq; src/LAWS.bend letter_value_next; src/LAWS.bend letter_unknown | -| SHAKE-TOK-3 | A plain word is never read as an option. | Proved | proved | src/LAWS.bend plain_word_step; src/LAWS.bend dash_word_step; src/LAWS.bend value_binds; src/LAWS.bend value_refused; src/LAWS.bend raw_next; src/LAWS.bend help_walk | -| SHAKE-TOK-4 | When a word exactly `--` reaches `parse` where an option could stand, it binds nothing and every later word is read as a plain word bound to a positional, whatever its shape. | Proved | proved | src/LAWS.bend dd_step; src/LAWS.bend raw_next; src/LAWS.bend raw_binds; src/LAWS.bend raw_no_pos; src/LAWS.bend raw_refused | -| SHAKE-TOK-5 | A flag given a value (`--verbose=x`, or `-v=x` in a cluster) is refused as `Unexpected{word}`. | Proved | proved | src/LAWS.bend flag_long_valued; src/LAWS.bend letter_flag_eq | -| SHAKE-TOK-6 | An option given no value in its own word takes the next word as its value, unless that word starts with `-` and is not `-`, or there is none; then the parse fails with `NoValue{name}`. | Proved | proved | src/LAWS.bend value_flag_shaped; src/LAWS.bend value_absent; src/LAWS.bend value_binds | -| SHAKE-TOK-7 | A long or short spelling that no argument of the current command has is refused as `UnknownFlag{word}`, except a word `--help` or `-h` that SHAKE-PARSE-8 reads as a request for help, and a word `--version` or `-V` that SHAKE-VER-1 reads as a request for the version. Arguments of a parent command are not matched after a subcommand is selected. | Proved | proved | src/LAWS.bend unknown_long; src/LAWS.bend unknown_short; src/LAWS.bend enter_step; src/LAWS.bend letter_unknown | - -### What a parse binds (SHAKE-PARSE) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-PARSE-1 | For every well-formed spec and word list, when `parse` succeeds, every binding in the Matched of each command on the selected path is `true` for a flag given while that command was current, a value-carrying piece of the words given while that command was current (a plain word bound to a positional, the value part of an option word, or the word after an option), bound verbatim under the name of the argument it was given for, or the default of an argument of that command that the words did not bind. One word adds at most one binding other than `true` to the current command, and a word read while the parse can still succeed is never ignored: it binds a value, sets a flag, enters a subcommand, waits for an option's value, switches to positionals, starts a help path, or fails. | Proved | proved | src/LAWS.bend values_given; src/LAWS.bend values_here; src/LAWS.bend one_value_per_word; src/LAWS.bend word_used | -| SHAKE-PARSE-2 | Plain words that select no subcommand bind the current command's positionals in spec order, one word each; a plain word with no positional left is refused as `Unexpected{word}`. | Proved | proved | src/LAWS.bend no_pos_left; src/LAWS.bend pos_next; src/LAWS.bend pos_binds; src/LAWS.bend start_state; src/LAWS.bend enter_step; src/LAWS.bend pos_of_app; src/LAWS.bend pos_of_pos; src/LAWS.bend pos_of_opt; src/LAWS.bend help_word_next; src/LAWS.bend help_word_binds | -| SHAKE-PARSE-3 | A rest positional binds every remaining plain word that selects no subcommand, in order, each under the rest's name. | Proved | proved | src/LAWS.bend rest_next; src/LAWS.bend rest_binds; src/LAWS.bend rest_raw_next; src/LAWS.bend rest_raw_binds | -| SHAKE-PARSE-4 | Before `--`, a plain word that names a subcommand of the current command selects it: the selected path gains that name, its arguments become current, and the bindings made so far stay with the command that made them; the subcommand's own bindings start empty. When a pending positional of the current command is required, has no default and is not a rest, the parse fails instead with `Missing{name}` of the first such one; a rest positional does not block a subcommand, and SHAKE-PARSE-7 checks it when the words end. | Proved | proved | src/LAWS.bend enter_step; src/LAWS.bend enter_missing; src/LAWS.bend none_blocking; src/LAWS.bend first_blocking | -| SHAKE-PARSE-5 | A value given to an argument with a nonempty choices list binds only when it is in the list, and otherwise fails with `BadValue{name, value}`. An argument with no choices accepts every value. | Proved | proved | src/LAWS.bend choice_refused; src/LAWS.bend value_refused; src/LAWS.bend long_refused; src/LAWS.bend raw_refused; src/LAWS.bend letter_value_bad; src/LAWS.bend pos_next; src/LAWS.bend pos_binds; src/LAWS.bend raw_binds; src/LAWS.bend value_binds; src/LAWS.bend long_binds; src/LAWS.bend letter_value; src/LAWS.bend letter_value_eq | -| SHAKE-PARSE-6 | After the last word, each argument of every command on the selected path that the words left unbound in that command and that has a default is bound to its default in that command's Matched. A bound argument's value is never replaced, and no argument without a default gains a binding. | Proved | proved | src/LAWS.bend default_filled; src/LAWS.bend bound_kept; src/LAWS.bend no_default; src/LAWS.bend no_argument | -| SHAKE-PARSE-7 | A successful parse binds every required argument of every command on the selected path, in that command's Matched. Otherwise the parse fails with `Missing{name}`. | Proved | proved | src/LAWS.bend required_present; src/LAWS.bend required_missing | -| SHAKE-PARSE-8 | Before `--` and before any positional of the current command is bound, a plain word `help`, a word `--help` when no argument of the current command has the long spelling `help`, or a word `-h` when no argument of the current command has the short spelling `h` or the long spelling `help`, makes the parse fail with `NeedHelp{path}`, where `path` is the selected path followed by the remaining words, as long as each names a subcommand under the one before; the first remaining word that does not makes the parse fail with `Unexpected{word}`. A command that declares its own long `help` gets `--help` as that argument (SHAKE-TOK-1), and `-h` is read as any other short option word (SHAKE-TOK-2); `help` still asks for help. | Proved | proved | src/LAWS.bend help_unknown; src/LAWS.bend help_step; src/LAWS.bend help_walk; src/LAWS.bend help_path; src/LAWS.bend dash_help_step; src/LAWS.bend dash_help_path; src/LAWS.bend short_help_step; src/LAWS.bend short_help_path | -| SHAKE-PARSE-9 | Once a word makes the parse fail, the words after it do not change what `parse` answers. | Proved | proved | src/LAWS.bend fail_stays | -| SHAKE-PARSE-10 | A flag or an option built with `opt` that is given a second time while the same command is current, under any of its spellings, fails the parse with `Repeated{at, name}`; an argument of a parent command with the same name is a different argument (SHAKE-TOK-7). An option built with `many` binds every value given, in the order given, and `get_all` of its command's Matched reads them all. | Proved | proved | src/LAWS.bend repeated_long_flag; src/LAWS.bend repeated_long_opt; src/LAWS.bend letter_flag_again; src/LAWS.bend letter_opt_again; src/LAWS.bend long_binds; src/LAWS.bend letter_value; src/LAWS.bend letter_value_eq | -| SHAKE-PARSE-11 | `parse_env(spec, words, vars)` reads `words` exactly as `parse` reads them against `spec` with every argument's default, in every command, replaced by its **fallback**, and answers what that parse answers, except as SHAKE-PARSE-12 says. An argument's fallback is its env value when it has one and is not a flag; for a flag with an env value, `true` when that value, lowercased, is none of `0`, `false`, `no`, `off`, `n` and `f`, and no fallback otherwise; and for an argument with no env value, its default. So a value the words bind wins over the env value and the env value over the default (SHAKE-PARSE-6), an argument with a fallback is filled by it and needs nothing from the words to satisfy `required` (SHAKE-PARSE-7), a required positional with a fallback does not block a subcommand (SHAKE-PARSE-4), and `parse_env(spec, words, [])` is `parse(spec, words)`. | Proved | proved | src/LAWS.bend env_sets; src/LAWS.bend env_of_none; src/LAWS.bend env_of_nil; src/LAWS.bend env_of_hit; src/LAWS.bend env_of_empty; src/LAWS.bend env_of_skip; src/LAWS.bend fallback_env; src/LAWS.bend fallback_flag_on; src/LAWS.bend fallback_flag_off; src/LAWS.bend fallback_default; src/LAWS.bend resolve_spec; src/LAWS.bend resolve_args_con; src/LAWS.bend resolve_subs_con; src/LAWS.bend env_parse; src/LAWS.bend env_none | -| SHAKE-PARSE-12 | When the parse SHAKE-PARSE-11 describes succeeds and an argument other than a flag, of a command on the selected path, has an env value outside its nonempty choices that the command's Matched binds to it, `parse_env` fails instead with `BadValue{at, name, value}` of the first such argument, the root command first and each command's arguments in spec order, where `at` is the selected path. A word's value outside the choices never binds (SHAKE-PARSE-5), so only an argument filled from its env value can be bound to it. When that parse fails, `parse_env` fails with the same error. | Proved | proved | src/LAWS.bend env_check_fail; src/LAWS.bend env_check_good; src/LAWS.bend env_check_bad; src/LAWS.bend bad_go_leaf; src/LAWS.bend bad_go_root; src/LAWS.bend bad_go_down; src/LAWS.bend bad_args_here; src/LAWS.bend bad_args_next; src/LAWS.bend bad_args_nil; src/LAWS.bend bad_arg_some; src/LAWS.bend bad_arg_unset; src/LAWS.bend bad_arg_flag; src/LAWS.bend bad_arg_allowed; src/LAWS.bend bad_arg_unbound | - -### Requests for the version (SHAKE-VER) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-VER-1 | When the spec has a version, `parse` fails with `NeedVersion{}` wherever it would otherwise fail with `UnknownFlag{[], "--version"}`, and wherever it would otherwise fail with `UnknownFlag{[], "-V"}` when no argument of the root command has the long spelling `version`: that is, when the first word to fail the parse is `--version` or `-V` read while the root command is current (SHAKE-PARSE-9, TOK-7). Every other result of the parse is what it would be without this row, and `parse_env` reads the version as `parse` does (SHAKE-PARSE-11). | Proved | proved | src/LAWS.bend version_asked; src/LAWS.bend version_unasked; src/LAWS.bend version_other; src/LAWS.bend resolve_asks | -| SHAKE-VER-2 | `version_of(spec, err)` is `Some{name ++ " " ++ version}`, the root command's name and the spec's version, when `err` is `NeedVersion{}` and the spec has a version, and `None` otherwise. | Proved | proved | src/LAWS.bend version_of_some; src/LAWS.bend version_of_none; src/LAWS.bend version_of_other | - -### Checking a spec (SHAKE-SPEC) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-SPEC-1 | `check(spec)` reports exactly one error for each of: a rest positional that is not its command's last positional; an argument of a command with the name, short spelling or long spelling of an earlier argument of that command; a subcommand with the name of an earlier subcommand of the same command; a subcommand named `help`; a default outside its argument's nonempty choices; a required positional after an optional one. It reports nothing else. | Proved | proved | src/LAWS.bend check_listed | - -### Checking reads (SHAKE-READ) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-READ-1 | `check_reads(spec, reads)` reports exactly one error for each read in `reads` that is wrong, in the order of `reads`, and nothing for a read that is right: `NoCommand{path}` when a name of its path is not a subcommand of the command before it, the root first; otherwise `NoArg{path, name}` when the command its path names has no argument named `name`; otherwise, for a flag read (`read_flag`), `NotFlag{path, name}` when that argument is not a flag, and for a value read (`read_value`), `IsFlag{path, name}` when it is one. It reports nothing else. | Proved | proved | src/LAWS.bend reads_nil; src/LAWS.bend reads_con; src/LAWS.bend read_no_command; src/LAWS.bend read_no_arg; src/LAWS.bend read_flag; src/LAWS.bend read_value | - -### Reading a result (SHAKE-GET) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-GET-1 | For every Matched `m` and name: `get(m, name)` is `Some` of the value of the last binding of that name that `m`'s command made, or `None` when it made none; `get_all(m, name)` is the values of every binding of that name that `m`'s command made, in binding order; `on(m, name)` is true exactly when `get(m, name)` is `Some{"true"}`. A command's Matched reads only its own bindings: the subcommand selected under it changes none of these. | Proved | proved | src/LAWS.bend get_all_append; src/LAWS.bend get_all_hit; src/LAWS.bend get_all_miss; src/LAWS.bend get_last; src/LAWS.bend get_none; src/LAWS.bend on_some; src/LAWS.bend on_none; src/LAWS.bend get_all_node; src/LAWS.bend get_node | -| SHAKE-GET-2 | For every Matched `m`: `sub_name(m)` is `Some` of the name of the subcommand selected under `m`'s command, or `None` when none was; `sub_of(m, name)` is that subcommand's Matched when its name is `name`, and `None` otherwise; `path_of(m)` is the names of the subcommands selected below `m`'s command, in order, so the whole selected path for the Matched `parse` answers; `at(m, [])` is `Some{m}`, and `at(m, name <> rest)` is `at` of `sub_of(m, name)` and `rest`, or `None` when `sub_of` is `None`. | Proved | proved | src/LAWS.bend sub_name_leaf; src/LAWS.bend sub_name_node; src/LAWS.bend sub_of_hit; src/LAWS.bend sub_of_miss; src/LAWS.bend sub_of_leaf; src/LAWS.bend path_of_leaf; src/LAWS.bend path_of_node; src/LAWS.bend at_nil; src/LAWS.bend at_cons | - -### Help pages (SHAKE-HELP) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-HELP-1 | `help(spec, path)` renders the page of the command reached by following each name of `path` from the root through the subcommands, skipping a name that is not a subcommand where it stands. | Proved | proved | src/LAWS.bend help_page; src/LAWS.bend reach_child; src/LAWS.bend reach_skip | -| SHAKE-HELP-2 | A page lists each subcommand of its command exactly once, in spec order, followed by `help`. When its command has positionals, an Arguments block after the subcommands lists each exactly once, in spec order, by the mark the usage line gives it without its leading space, then its help text and the extras an option line has (SHAKE-HELP-3); a command with no positional has no Arguments block. The Options block lists each flag and option exactly once, in spec order, followed by the built-in rows of SHAKE-HELP-4. Its usage line names each positional exactly once, in spec order, as `` when required and `[NAME]` otherwise, with `...` after a rest positional. | Proved | proved | src/LAWS.bend cmds_listed; src/LAWS.bend opts_listed; src/LAWS.bend usage_listed; src/LAWS.bend args_listed; src/LAWS.bend arg_line_mark; src/LAWS.bend builtins_listed | -| SHAKE-HELP-3 | The Options or Arguments line of an argument that has an env var `NAME` shows `[env: NAME]` after its help text, before its `[default: ...]` and `[possible values: ...]`, as clap's does; the line of one with no env var is what it would be without this row. | Proved | proved | src/LAWS.bend help_env_shown; src/LAWS.bend help_env_none; src/LAWS.bend arg_line_mark | -| SHAKE-HELP-4 | The Options block of a page ends with a row `Print help` spelled as the requests for help SHAKE-PARSE-8 answers on its command: `-h, --help` when the command has neither the short spelling `h` nor the long spelling `help`, `--help` when it has `h` but not `help`, and no row when it has `help`. On the root page of a spec with a version a row `Print version` follows, spelled as the requests SHAKE-VER-1 answers: `-V, --version` when the root has neither the short spelling `V` nor the long spelling `version`, `--version` when it has `V` but not `version`, and no row when it has `version`. These rows are laid out as the other rows of the block, and make the usage line show `[OPTIONS]`. | Proved | proved | src/LAWS.bend builtins_listed | - -### Errors (SHAKE-ERR) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-ERR-1 | `err_text(spec, err)` is empty exactly when `help_path(err)` is `Some` or `err` is `NeedVersion{}`, that is, when the parse failed with a request for help or for the version. | Proved | proved | src/LAWS.bend err_text_help; src/LAWS.bend err_text_iff; src/LAWS.bend err_text_version | -| SHAKE-ERR-2 | For every error but a request for help or for the version, `err_path(err)` is the path of subcommands the parse had selected at the word that failed it, and `err_text(spec, err)` shows the usage line of the command at that path, as `help(spec, err_path(err))` does. `err_path(NeedVersion{})` is `[]`. | Proved | proved | src/LAWS.bend err_path_at; src/LAWS.bend err_text_usage; src/LAWS.bend help_page; src/LAWS.bend unknown_long; src/LAWS.bend unknown_short; src/LAWS.bend no_pos_left; src/LAWS.bend choice_refused; src/LAWS.bend flag_long_valued; src/LAWS.bend value_flag_shaped; src/LAWS.bend value_absent; src/LAWS.bend help_unknown; src/LAWS.bend long_no_name; src/LAWS.bend repeated_long_flag; src/LAWS.bend repeated_long_opt; src/LAWS.bend value_refused; src/LAWS.bend long_refused; src/LAWS.bend raw_no_pos; src/LAWS.bend raw_refused; src/LAWS.bend enter_missing; src/LAWS.bend required_missing; src/LAWS.bend letter_flag_eq; src/LAWS.bend letter_unknown; src/LAWS.bend letter_flag_again; src/LAWS.bend letter_opt_again; src/LAWS.bend letter_value_bad | -| SHAKE-ERR-3 | `suggestion(spec, err)` is `None` for every error but an `UnknownFlag{at, word}` whose word starts with `--` (SHAKE-TOK-1, TOK-7). For such a word, let `typed` be its chars after `--` up to its first `=`, and the **candidates** the long spellings of the flags and options of the command at `at`, reached as `help(spec, at)` reaches it (SHAKE-HELP-1), in spec order; a parent command's arguments, subcommands and `help` are not candidates. A candidate `c` is **close** when `0 < d` and `3 * d <= max(length(typed), length(c))`, where `d` is the optimal string alignment distance from `typed` to `c`: the fewest single-char insertions, deletions, substitutions and swaps of two adjacent chars that turn one into the other, editing no char twice. The suggestion is `Some{"--" ++ c}` for the close candidate with the least `d`, the first in spec order among equals, and `None` when no candidate is close. A short option word never gets one. | Proved | proved | src/LAWS.bend sug_other; src/LAWS.bend sug_short; src/LAWS.bend sug_long; src/LAWS.bend sug_found_some; src/LAWS.bend cands_some; src/LAWS.bend cands_none; src/LAWS.bend sug_close; src/LAWS.bend best_nil; src/LAWS.bend best_far; src/LAWS.bend best_only; src/LAWS.bend best_here; src/LAWS.bend best_there | -| SHAKE-ERR-4 | When `suggestion(spec, err)` is `Some{s}`, `err_text(spec, err)` contains `s`. | Proved | proved | src/LAWS.bend err_text_tip | -| SHAKE-ERR-5 | `err_text` names the argument of a `Missing{at, name}`, `NoValue{at, name}`, `BadValue{at, name, value}` or `Repeated{at, name}` error by the **display form** of the argument named `name` of the deepest command on the path `at` that has one, the root first and each name of `at` reached as `help` reaches it (SHAKE-HELP-1): for a flag, `--` and its long spelling, else `-` and its short spelling; for an option, the same followed by ` `, and by `...` for one built with `many`; for a positional, `` when it is required and `[NAME]` otherwise, followed by `...` for a rest; `NAME` being the argument's name in upper case. A flag or option with neither spelling (an empty spelling counts as none), and a name no command on the path has, is named by `name` itself. The message of a `BadValue` whose argument has nonempty choices `c1`, ..., `cn` ends with the line ` [possible values: c1, ..., cn]`. | Proved | proved | src/LAWS.bend err_find; src/LAWS.bend err_form; src/LAWS.bend err_missing_named; src/LAWS.bend err_novalue_named; src/LAWS.bend err_bad_named; src/LAWS.bend err_repeated_named | - -### The argument list (SHAKE-ARGS) - -| ID | Requirement | Level | Status | Law | -| :---- | :---- | :---- | :---- | :---- | -| SHAKE-ARGS-1 | The copy `argv` makes of the words `IO.args` gives drops the first, the program as invoked, and keeps all the others, in order and unchanged: for every list, reading the copy back gives the list after its first word, and nothing for an empty list. | Proved | proved | src/LAWS.bend copy_keeps; src/LAWS.bend words_keeps | -| SHAKE-ARGS-2 | The names `env_vars(spec)` asks `IO.get_env` for are the env vars of the arguments that have one, the root command's arguments first and then each subcommand's, depth first, each command's arguments in spec order. | Proved | proved | src/LAWS.bend env_names_root; src/LAWS.bend env_names_forest; src/LAWS.bend env_names_some; src/LAWS.bend env_names_none | - -## Left to prove - -No row is pending. The RFCs' Rollouts and [docs/rfc/shake-walker-proofs.md](docs/rfc/shake-walker-proofs.md) say in which phase each row's laws landed. Every walker law holds wherever the words before the word it is about leave the walker in the state its premise names. - -## Trust boundary - -These assumptions sit outside the proofs. They are the complete list of Trusted requirements, and a passing proof gate says nothing about them. - -| ID | Assumption | Why it is trusted | -| :---- | :---- | :---- | -| SHAKE-TRUST-1 | The Bend checker is sound: a proof it accepts proves its law. | It cannot be checked from inside Bend; this is EZ-TRUST-1. shake pins bend 2.0.38 through the flake. | -| SHAKE-TRUST-2 | A program compiled by bend 2.0.38 hands `IO.args` the program as invoked (the process's `argv[0]`) followed by the process's words after it, in order and unchanged, except for the runtime's words. A word `+RTS` opens a bracket of runtime options and a word `-RTS` closes it (a bracket left open runs to the end); neither word, nor any word inside a bracket, reaches `IO.args`. At the first word `--RTS`, inside a bracket or not, it stops examining words, drops that `--RTS` and passes every later word through unchanged. It reads the bracketed words, after the words of the env var `BENDRTS`, as runtime options: `--threads N` and `--gpu X` set the runtime, and it ends the process before `main` on `--help`, on `--gpu-build`, on a bad value and on any other word it reads there. Every other word reaches `IO.args`: `--`, `--help`, `--threads`, `--gpu`, `--gpu-build` and `--bend-help` included. Run as `bend file.bend [args]`, `IO.args` is the file as given, then the arguments, after the `bend` command has taken a first `--`. | It is the C `main` bend emits (`main` in the C that `bend file.bend -o file.c` writes, and `IO.args` in bend2/effs/args.c), read from bend 2.0.38's output and confirmed by compiling a program that prints its `IO.args` and the demo; 2.0.29 stopped taking `--help`, 2.0.32 put the program first and 2.0.38 moved the runtime's options inside `+RTS` … `-RTS`, so a plain `--` now reaches the program. It changes when bend does, so every bend bump rechecks it. | -| SHAKE-TRUST-3 | The proof-gate runner fails the build unless the first line of `bend src/PROOF.bend` is `ALL PROOFS CHECK`. | It is the flake's `proofs` check, run by `nix flake check` in CI, which runs every PROOF.bend on the flake's bend and compares the first line; `ez prove` does the same once ez runs on bend 2.0.38. | -| SHAKE-TRUST-4 | `argv` hands on exactly the list `IO.args` answers, through the copy SHAKE-ARGS-1 is about. | It is IO, which no law can reach: `argv` in `src/args.bend` is one `IO.bind` of `IO.args` into `words`, short enough to check by reading, and marked `# noqa: L001` for that reason. | -| SHAKE-TRUST-5 | `env_vars(spec)` answers, for each name it asks for (SHAKE-ARGS-2) and in that order, the pair of the name and the value `IO.get_env` answers for it, unchanged and empty included, and no pair for a name `IO.get_env` fails for. A program compiled by bend 2.0.38 answers `getenv`'s value for a variable that is set and fails for one that is not. | It is IO, which no law can reach: `env_vars` in `src/args.bend` is one `IO.bind` of `IO.get_env` per name, short enough to check by reading and marked `# noqa: L001`. What `IO.get_env` answers is `io_get_env_run` in bend2/effs/get_env.c, read from bend 2.0.38's source; every bend bump rechecks it, as for SHAKE-TRUST-2. | diff --git a/bolt.bend b/bolt.bend index f8db8b6..bc281fb 100644 --- a/bolt.bend +++ b/bolt.bend @@ -28,7 +28,3 @@ def closed() -> String: # an @unsafe def a law file reaches def unsafe() -> String: "error" - -# SPEC.md's rows and the laws' tags agree -def trace() -> String: - "error" diff --git a/laws/AllowedAny.bend b/laws/AllowedAny.bend new file mode 100644 index 0000000..138585c --- /dev/null +++ b/laws/AllowedAny.bend @@ -0,0 +1,7 @@ +import Base +import ../src/cli.bend as S + +# LAW: empty choices accept every value +law allowed_any: + for vv: String + {S.allowed([], vv) == True{} : Bool} diff --git a/laws/ArgLineMark.bend b/laws/ArgLineMark.bend new file mode 100644 index 0000000..fadd660 --- /dev/null +++ b/laws/ArgLineMark.bend @@ -0,0 +1,20 @@ +import Base +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# LAW: an Arguments line is the positional's usage mark without the space +# before it, padded, then its help text and the extras an Options line has +law arg_line_mark: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +fb: S.Fallback + for +cs: List<&2, String> + for +ww: Nat + {S.help.arg_line(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}, ww) + == " " ++ S.help.pad(String.drop(D1.mark(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}), 1n), ww) ++ " " ++ hp ++ + S.help.extra(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) + : String} diff --git a/laws/ArgvDropsProgram.bend b/laws/ArgvDropsProgram.bend new file mode 100644 index 0000000..e04cbcb --- /dev/null +++ b/laws/ArgvDropsProgram.bend @@ -0,0 +1,35 @@ +# The copy `argv` makes of the words `IO.args` gives drops the first, the +# program as invoked, and keeps all the others, in order and unchanged: for +# every list, reading the copy back gives the list after its first word, and +# nothing for an empty list. +import Base +import ../src/args.bend as Args + +# a `&2` word list read back as the `&1` list IO.args answers +def down(ys: List<&2, String>) -> List<&1, String>: + match ys: + case Nil{}: + Nil{} + case Con{+hh, tt}: + hh <> down(tt) + +# the words of an IO.args line after its first, the program name +def after_head(xs: List<&1, String>) -> List<&1, String>: + match xs: + case Nil{}: + Nil{} + case Con{_hh, tt}: + tt + +# LAW: `copy` keeps every word IO.args gives, all of them, in order and +# unchanged: read back, the copy is the list it was made from +law copy_keeps: + for xs: List<&1, String> + {down(Args.copy(xs)) == xs : List<&1, String>} + +# LAW: `words` drops the program name IO.args starts with and keeps every +# word after it, in order and unchanged; a line with no program name gives +# no arguments +law words_keeps: + for xs: List<&1, String> + {down(Args.words(xs)) == after_head(xs) : List<&1, String>} diff --git a/laws/BindValuesAppend.bend b/laws/BindValuesAppend.bend new file mode 100644 index 0000000..e229f85 --- /dev/null +++ b/laws/BindValuesAppend.bend @@ -0,0 +1,12 @@ +import Base +import ../src/cli.bend as S + +# LAW: the values bound to a name in two lists of bindings are the first's +# then the second's (a lemma over the bindings alone) +law bind_values_append: + for bs: List<&2, S.Bind> + for +cs: List<&2, S.Bind> + for +name: String + {S.get_all.bind(List.append(&2, S.Bind, bs, cs), name) + == List.append(&2, String, S.get_all.bind(bs, name), S.get_all.bind(cs, name)) + : List<&2, String>} diff --git a/laws/BindingsComeFromWords.bend b/laws/BindingsComeFromWords.bend new file mode 100644 index 0000000..229461f --- /dev/null +++ b/laws/BindingsComeFromWords.bend @@ -0,0 +1,187 @@ +# For every well-formed spec and word list, when `parse` succeeds, every +# binding in the Matched of each command on the selected path is `true` for a +# flag given while that command was current, a value-carrying piece of the +# words given while that command was current (a plain word bound to a +# positional, the value part of an option word, or the word after an option), +# bound verbatim under the name of the argument it was given for, or the +# default of an argument of that command that the words did not bind. One word +# adds at most one binding other than `true` to the current command, and a +# word read while the parse can still succeed is never ignored: it binds a +# value, sets a flag, enters a subcommand, waits for an option's value, +# switches to positionals, starts a help path, or fails. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ./defs/ArgumentLists.bend as D1 + +# whether every binding is given +def all_given(bs: List<&2, S.Bind>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Bool: + match bs: + case Nil{}: + True{} + case Con{hh, tt}: + Bool.and(D1.given(ws, args, hh), all_given(tt, ws, args)) + +# whether every binding of a command's own Matched is given, when there is one +def given_at.go(mm: Maybe<&2, Shake.Matched>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Maybe<&2, Bool>: + match mm: + case None{}: + None{} + case Some{found}: + Some{all_given(S.own(found), ws, args)} + +# whether every binding of the command at `path` in a Matched is given +def given_at( + found: Shake.Matched, + path: List<&2, String>, + +ws: List<&2, String>, + +args: List<&2, S.Arg> +) -> Maybe<&2, Bool>: + given_at.go(Shake.at(found, path), ws, args) + +# LAW: when the parse succeeds, every binding in the Matched of a command on +# the selected path is `true`, which a flag binds, a piece of one of the words, +# verbatim, or the default of one of that command's arguments: no value is +# invented, cut short or changed +law values_given: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +args: List<&2, S.Arg> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for +h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} + : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + {given_at(found, path, ws, args) == Some{True{}} : Maybe<&2, Bool>} + +# whether one of `args` is named `name` +def has_arg(args: List<&2, S.Arg>, +name: String) -> Bool: + match args: + case Nil{}: + False{} + case Con{hh, tt}: + S.Arg{nn, _s, _l, _k, _h, _r, _d, _c} = hh + Bool.or(String.eq(nn, name), has_arg(tt, name)) + +# whether a binding is named after one of `args` and is given: `true`, a +# piece of one of the words, or the default of one of `args` of its name +def named_given(+ws: List<&2, String>, +args: List<&2, S.Arg>, bb: S.Bind) -> Bool: + S.Bind{+name, +vv} = bb + Bool.and(has_arg(args, name), D1.given(ws, args, S.Bind{name, vv})) + +# whether every binding is named and given +def all_named(bs: List<&2, S.Bind>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Bool: + match bs: + case Nil{}: + True{} + case Con{hh, tt}: + Bool.and(named_given(ws, args, hh), all_named(tt, ws, args)) + +# whether every binding of a command's own Matched is named and given, when +# there is one +def named_at.go(mm: Maybe<&2, Shake.Matched>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Maybe<&2, Bool>: + match mm: + case None{}: + None{} + case Some{found}: + Some{all_named(S.own(found), ws, args)} + +# whether every binding of the command at `path` in a Matched is named and +# given +def named_at( + found: Shake.Matched, + path: List<&2, String>, + +ws: List<&2, String>, + +args: List<&2, S.Arg> +) -> Maybe<&2, Bool>: + named_at.go(Shake.at(found, path), ws, args) + +# LAW: when the parse succeeds, every binding in the Matched of a command on +# the selected path is named after one of that command's arguments, and is +# `true`, which a flag binds, a piece of the words read while that command +# was current (`mid`: from the word that selected it, or the start, to the +# word that selects a subcommand under it, or the end), verbatim, or the +# default of one of its arguments +law values_here: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +mid: List<&2, String> + for +post: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +args: List<&2, S.Arg> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, List.append(&2, String, pre, List.append(&2, String, mid, post))) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_pre: {Grow.view(S.parse.walk(pre, S.parse.start(spec))) == Grow.View{args, up, path, []} : Grow.View} + for h_mid: {Grow.view(S.parse.walk(List.append(&2, String, pre, mid), S.parse.start(spec))) + == Grow.View{args, up, path, bs} : Grow.View} + for +h_fr: {Grow.gw.frame(S.parse.walk(List.append(&2, String, pre, List.append(&2, String, mid, post)), + S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} + : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + {named_at(found, path, mid, args) == Some{True{}} : Maybe<&2, Bool>} + +# a count, with one more when a value is not `true` +def valued.at(flag: Bool, nn: Nat) -> Nat: + match flag: + case True{}: + nn + case False{}: + 1n+nn + +# how many bindings have a value other than `true`, the value a flag binds +def valued(bs: List<&2, S.Bind>) -> Nat: + match bs: + case Nil{}: + 0n + case Con{hh, tt}: + S.Bind{_n, vv} = hh + valued.at(String.eq(vv, "true"), valued(tt)) + +# whether `aa` is at most `bb` +def nat_le(aa: Nat, bb: Nat) -> Bool: + match aa: + case 0n: + True{} + case 1n+pp: + match bb: + case 0n: + False{} + case 1n+qq: + nat_le(pp, qq) + +# a walker's bindings for its current command +def binds_of(st: S.St) -> List<&2, S.Bind>: + S.St{_m, _a, _g, _s, _p, binds, _o, _r, _n} = st + binds + +# LAW: one word adds at most one binding to the current command other than +# `true`, which a flag binds: a word never binds two values, whatever the +# walker it is read by +law one_value_per_word: + for +tok: String + for +st: S.St + {nat_le(valued(binds_of(S.parse.step(tok, st))), 1n+valued(binds_of(st))) == True{} : Bool} + +# LAW: a word read by a walker that can still finish as a success changes it: +# it binds a value, sets a flag, enters a subcommand, waits for an option's +# value, switches to positionals, starts a help path, or fails. No word is +# ignored +law word_used: + for +tok: String + for +st: S.St + for h_live: {Grow.gw.live(st) == True{} : Bool} + for h_same: {S.parse.step(tok, st) == st : S.St} + Empty diff --git a/laws/BuiltinsListed.bend b/laws/BuiltinsListed.bend new file mode 100644 index 0000000..432111e --- /dev/null +++ b/laws/BuiltinsListed.bend @@ -0,0 +1,54 @@ +import Base +import ../src/cli.bend as S + +# a built-in flag a page lists, spelled `--name` and maybe a short letter +def builtin(+name: String, short: Maybe<&2, String>, +help: String) -> S.Arg: + S.Arg{name, short, Some{name}, S.Flag{}, help, False{}, S.Fallback{None{}, None{}}, []} + +# the help row, given what the command spells as long `help` and short `h` +def help_rows.of(long: Maybe<&2, S.Arg>, short: Maybe<&2, S.Arg>) -> List<&2, S.Arg>: + match long: + case None{}: + match short: + case None{}: + [builtin("help", Some{"h"}, "Print help")] + case Some{_a}: + [builtin("help", None{}, "Print help")] + case Some{_a}: + [] + +# the help row of a page: `-h, --help` when its command spells neither, +# `--help` when it spells `-h`, none when it spells `--help` +def help_rows(+args: List<&2, S.Arg>) -> List<&2, S.Arg>: + help_rows.of(S.by_long(args, "help"), S.by_short(args, "h")) + +# the version row, given what the command spells as long `version` and short +# `V` +def version_rows.of(long: Maybe<&2, S.Arg>, short: Maybe<&2, S.Arg>) -> List<&2, S.Arg>: + match long: + case None{}: + match short: + case None{}: + [builtin("version", Some{"V"}, "Print version")] + case Some{_a}: + [builtin("version", None{}, "Print version")] + case Some{_a}: + [] + +# the version row of a page: none without a version; else `-V, --version` +# when its command spells neither, `--version` when it spells `-V`, none +# when it spells `--version` +def version_rows(+args: List<&2, S.Arg>, version: Maybe<&2, String>) -> List<&2, S.Arg>: + match version: + case None{}: + [] + case Some{_v}: + version_rows.of(S.by_long(args, "version"), S.by_short(args, "V")) + +# LAW: the built-in flags a page lists after its command's own are its help +# row, then its version row +law builtins_listed: + for +args: List<&2, S.Arg> + for +version: Maybe<&2, String> + {S.help.builtins(args, version) == List.append(&2, S.Arg, help_rows(args), version_rows(args, version)) + : List<&2, S.Arg>} diff --git a/laws/CheckReadsReportsBadReads.bend b/laws/CheckReadsReportsBadReads.bend new file mode 100644 index 0000000..ee5ae7b --- /dev/null +++ b/laws/CheckReadsReportsBadReads.bend @@ -0,0 +1,123 @@ +# `check_reads(spec, reads)` reports exactly one error for each read in +# `reads` that is wrong, in the order of `reads`, and nothing for a read that +# is right: `NoCommand{path}` when a name of its path is not a subcommand of +# the command before it, the root first; otherwise `NoArg{path, name}` when +# the command its path names has no argument named `name`; otherwise, for a +# flag read (`read_flag`), `NotFlag{path, name}` when that argument is not a +# flag, and for a value read (`read_value`), `IsFlag{path, name}` when it is +# one. It reports nothing else. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/check.bend as K + +# the root command as a subcommand, to walk a path from +def root_sub(app: Shake.Cli) -> S.Sub: + S.Cli{+name, +about, _v, args, subs} = app + S.Sub{name, about, args, subs} + +# the subcommand named `name` of a found command +def sub_named(here: Maybe<&2, S.Sub>, +name: String) -> Maybe<&2, S.Sub>: + match here: + case None{}: + None{} + case Some{ss}: + S.Sub{_n, _a, _g, subs} = ss + S.find_sub(subs, name) + +# the command a path names below a found one, each name a subcommand of the +# command before it, or None +def named_cmd(path: List<&2, String>, here: Maybe<&2, S.Sub>) -> Maybe<&2, S.Sub>: + match path: + case Nil{}: + here + case Con{+hh, tt}: + named_cmd(tt, sub_named(here, hh)) + +# whether an argument kind is a flag +def is_flag(kk: S.ArgKind) -> Bool: + match kk: + case S.Flag{}: + True{} + case _other: + False{} + +# LAW: no reads, no reports +law reads_nil: + for +spec: Shake.Cli + {Shake.check_reads(spec, []) == [] : List<&2, K.ReadErr>} + +# LAW: the reports of each read, in the order of the reads +law reads_con: + for +spec: Shake.Cli + for rr: K.Read + for rs: List<&2, K.Read> + {Shake.check_reads(spec, rr <> rs) == List.append(&2, K.ReadErr, K.read(spec, rr), Shake.check_reads(spec, rs)) + : List<&2, K.ReadErr>} + +# LAW: a read whose path names no command is reported as that +law read_no_command: + for +spec: Shake.Cli + for +path: List<&2, String> + for +name: String + for want: K.Want + for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == None{} : Maybe<&2, S.Sub>} + {K.read(spec, K.Read{path, name, want}) == [K.NoCommand{path}] : List<&2, K.ReadErr>} + +# LAW: a read of a name no argument of its command has is reported as that +law read_no_arg: + for +spec: Shake.Cli + for +path: List<&2, String> + for +name: String + for want: K.Want + for sn: String + for sa: String + for +sargs: List<&2, S.Arg> + for ssubs: List<&2, S.Sub> + for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} + for h_arg: {S.by_name(sargs, name) == None{} : Maybe<&2, S.Arg>} + {K.read(spec, K.Read{path, name, want}) == [K.NoArg{path, name}] : List<&2, K.ReadErr>} + +# LAW: a flag read is reported exactly when its argument is not a flag +law read_flag: + for +spec: Shake.Cli + for +path: List<&2, String> + for +name: String + for sn: String + for sa: String + for +sargs: List<&2, S.Arg> + for ssubs: List<&2, S.Sub> + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for +kind: S.ArgKind + for hp: String + for rq: Bool + for fb: S.Fallback + for cs: List<&2, String> + for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} + for h_arg: {S.by_name(sargs, name) == Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}} : Maybe<&2, S.Arg>} + {K.read(spec, K.Read{path, name, K.WantFlag{}}) + == K.reads.one(Bool.not(is_flag(kind)), K.NotFlag{path, name}) : List<&2, K.ReadErr>} + +# LAW: a value read is reported exactly when its argument is a flag +law read_value: + for +spec: Shake.Cli + for +path: List<&2, String> + for +name: String + for sn: String + for sa: String + for +sargs: List<&2, S.Arg> + for ssubs: List<&2, S.Sub> + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for +kind: S.ArgKind + for hp: String + for rq: Bool + for fb: S.Fallback + for cs: List<&2, String> + for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} + for h_arg: {S.by_name(sargs, name) == Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}} : Maybe<&2, S.Arg>} + {K.read(spec, K.Read{path, name, K.WantValue{}}) == K.reads.one(is_flag(kind), K.IsFlag{path, name}) + : List<&2, K.ReadErr>} diff --git a/laws/CheckReportsSpecErrors.bend b/laws/CheckReportsSpecErrors.bend new file mode 100644 index 0000000..eb42422 --- /dev/null +++ b/laws/CheckReportsSpecErrors.bend @@ -0,0 +1,28 @@ +# `check(spec)` reports exactly one error for each of: a rest positional that +# is not its command's last positional; an argument of a command with the +# name, short spelling or long spelling of an earlier argument of that +# command; a subcommand with the name of an earlier subcommand of the same +# command; a subcommand named `help`; a default outside its argument's +# nonempty choices; a required positional after an optional one. It reports +# nothing else. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/check.bend as K +import ./defs/ArgumentLists.bend as D1 + +# LAW: `check` reports, for the root and then for every subcommand depth +# first, each at its own path: each argument whose name, short spelling or +# long spelling an earlier argument of its command has, each default outside +# nonempty choices, each rest positional before the last positional, each +# required positional after an optional one, each subcommand with an earlier +# sibling's name, and each subcommand named `help`; one report each and +# nothing else +law check_listed: + for +name: String + for +about: String + for +version: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + {Shake.check(S.Cli{name, about, version, args, subs}) == K.both(D1.cmd_reports([], args, subs), D1.below(subs, [])) + : List<&2, K.SpecErr>} diff --git a/laws/ChoiceRefused.bend b/laws/ChoiceRefused.bend new file mode 100644 index 0000000..edf6128 --- /dev/null +++ b/laws/ChoiceRefused.bend @@ -0,0 +1,38 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free with a positional +# next whose choices do not allow it, a plain word that names no subcommand +# fails the parse with BadValue of that positional and the word, whatever +# follows +law choice_refused: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + False{}, seen} : S.St} + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for h_help: {String.eq(tok, "help") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} + for h_bad: {S.allowed(cs, tok) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.BadValue{path, name, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/ChoicesRestrictValues.bend b/laws/ChoicesRestrictValues.bend new file mode 100644 index 0000000..ec4f591 --- /dev/null +++ b/laws/ChoicesRestrictValues.bend @@ -0,0 +1,12 @@ +# A value given to an argument with a nonempty choices list binds only when it +# is in the list, and otherwise fails with `BadValue{name, value}`. An +# argument with no choices accepts every value. +# +# also rests on: laws/ChoiceRefused.bend, laws/LetterValue.bend, +# laws/LetterValueBad.bend, laws/LetterValueEq.bend, laws/LongBinds.bend, +# laws/LongRefused.bend, laws/PosBinds.bend, laws/PosNext.bend, +# laws/RawBinds.bend, laws/RawRefused.bend, laws/ValueBinds.bend, +# laws/ValueRefused.bend +import Base + + diff --git a/laws/ClusterDead.bend b/laws/ClusterDead.bend new file mode 100644 index 0000000..07d7571 --- /dev/null +++ b/laws/ClusterDead.bend @@ -0,0 +1,20 @@ +import Base +import ../src/cli.bend as S + +# LAW: once a letter of a short cluster fails the walker, the letters after it +# change nothing: the word fails with the first letter's error +law cluster_dead: + for body: String + for +orig: String + for +err: S.ParseErr + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +done: Bool + {S.cluster(body, orig, S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen}, done) + == S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen} : S.St} diff --git a/laws/ClusterDone.bend b/laws/ClusterDone.bend new file mode 100644 index 0000000..fb8dabb --- /dev/null +++ b/laws/ClusterDone.bend @@ -0,0 +1,10 @@ +import Base +import ../src/cli.bend as S + +# LAW: once a short cluster has ended, the letters left change nothing (a +# lemma) +law cluster_done: + for body: String + for +orig: String + for +st: S.St + {S.cluster(body, orig, st, True{}) == st : S.St} diff --git a/laws/DeadWalk.bend b/laws/DeadWalk.bend new file mode 100644 index 0000000..bc39366 --- /dev/null +++ b/laws/DeadWalk.bend @@ -0,0 +1,17 @@ +import Base +import ../src/cli.bend as S + +# LAW: a failed walker stays as it is, whatever words follow (a lemma) +law dead_walk: + for ys: List<&2, String> + for +err: S.ParseErr + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +binds: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + {S.parse.walk(ys, S.St{S.Dead{err}, args, up, subs, path, binds, pos, raw, seen}) + == S.St{S.Dead{err}, args, up, subs, path, binds, pos, raw, seen} : S.St} diff --git a/laws/DefaultsFillUnbound.bend b/laws/DefaultsFillUnbound.bend new file mode 100644 index 0000000..ece6490 --- /dev/null +++ b/laws/DefaultsFillUnbound.bend @@ -0,0 +1,138 @@ +# After the last word, each argument of every command on the selected path +# that the words left unbound in that command and that has a default is bound +# to its default in that command's Matched. A bound argument's value is never +# replaced, and no argument without a default gains a binding. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ./defs/ValuesAtPath.bend as D1 + +# whether no argument of a list is named `name` +def none_named(args: List<&2, S.Arg>, +name: String) -> Bool: + match args: + case Nil{}: + True{} + case Con{hh, tt}: + S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c} = hh + Bool.and(Bool.not(String.eq(nn, name)), none_named(tt, name)) + +# LAW: when the parse succeeds, an argument with a default that its command +# left unbound reads its default, once, in that command's Matched +law default_filled: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +pa: List<&2, S.Arg> + for +qa: List<&2, S.Arg> + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +ev: Maybe<&2, String> + for +dd: String + for +cs: List<&2, String> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, + S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, cs} <> qa), + bs} <> up), + List.append(&2, String, path, ms)} : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + for h_pa: {none_named(pa, nn) == True{} : Bool} + for h_qa: {none_named(qa, nn) == True{} : Bool} + for h_free: {S.bound(bs, nn) == False{} : Bool} + {D1.all_at(found, path, nn) == Some{[dd]} : Maybe<&2, List<&2, String>>} + +# LAW: when the parse succeeds, an argument its command bound reads exactly +# the values it was given, in order, in that command's Matched: a default +# never replaces them nor adds to them +law bound_kept: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +pa: List<&2, S.Arg> + for +qa: List<&2, S.Arg> + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, + S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} <> qa), bs} <> up), + List.append(&2, String, path, ms)} : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + for h_pa: {none_named(pa, nn) == True{} : Bool} + for h_qa: {none_named(qa, nn) == True{} : Bool} + for h_bound: {S.bound(bs, nn) == True{} : Bool} + {D1.all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} : Maybe<&2, List<&2, String>>} + +# LAW: when the parse succeeds, an argument with no default reads only the +# values the words gave it in its command's Matched, none when they gave none +law no_default: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +pa: List<&2, S.Arg> + for +qa: List<&2, S.Arg> + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +ev: Maybe<&2, String> + for +cs: List<&2, String> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, + S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs} <> qa), + bs} <> up), + List.append(&2, String, path, ms)} : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + for h_pa: {none_named(pa, nn) == True{} : Bool} + for h_qa: {none_named(qa, nn) == True{} : Bool} + {D1.all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} : Maybe<&2, List<&2, String>>} + +# LAW: when the parse succeeds, a name no argument of a command has reads only +# what the words bound to it in that command's Matched +law no_argument: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +args: List<&2, S.Arg> + for +nn: String + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} + : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + for h_none: {none_named(args, nn) == True{} : Bool} + {D1.all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} : Maybe<&2, List<&2, String>>} diff --git a/laws/DoubleDashEndsOptions.bend b/laws/DoubleDashEndsOptions.bend new file mode 100644 index 0000000..b9b0310 --- /dev/null +++ b/laws/DoubleDashEndsOptions.bend @@ -0,0 +1,26 @@ +# When a word exactly `--` reaches `parse` where an option could stand, it +# binds nothing and every later word is read as a plain word bound to a +# positional, whatever its shape. +# +# also rests on: laws/RawBinds.bend, laws/RawNext.bend, laws/RawNoPos.bend, +# laws/RawRefused.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free and before `--`, a +# word `--` binds nothing and leaves it free after `--` +law dd_step: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + {S.parse.walk(List.append(&2, String, pre, ["--"]), S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, True{}, seen} : S.St} diff --git a/laws/DropZero.bend b/laws/DropZero.bend new file mode 100644 index 0000000..9ada8db --- /dev/null +++ b/laws/DropZero.bend @@ -0,0 +1,6 @@ +import Base + +# LAW: dropping nothing from a string leaves it (a lemma) +law drop_zero: + for ss: String + {String.drop(ss, 0n) == ss : String} diff --git a/laws/EnterMissing.bend b/laws/EnterMissing.bend new file mode 100644 index 0000000..94e2365 --- /dev/null +++ b/laws/EnterMissing.bend @@ -0,0 +1,30 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free before `--`, a plain +# word that names a subcommand of the current command while a positional +# blocks it fails the parse with Missing of that positional, whatever follows +law enter_missing: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +sub: S.Sub + for +nm: String + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for h_help: {String.eq(tok, "help") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_sub: {S.find_sub(subs, tok) == Some{sub} : Maybe<&2, S.Sub>} + for h_req: {S.parse.first_req(pos) == Some{nm} : Maybe<&2, String>} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.Missing{path, nm}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/EnterStep.bend b/laws/EnterStep.bend new file mode 100644 index 0000000..cf3fc50 --- /dev/null +++ b/laws/EnterStep.bend @@ -0,0 +1,34 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free before `--`, a plain +# word that names a subcommand of the current command, with nothing blocking +# it, selects it: the path gains the subcommand's name, its arguments become +# current with its positionals pending and no bindings of its own, and the +# command left keeps its arguments and bindings above it +law enter_step: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +name: String + for +about: String + for +next: List<&2, S.Arg> + for +kids: List<&2, S.Sub> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for h_help: {String.eq(tok, "help") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_sub: {S.find_sub(subs, tok) == Some{S.Sub{name, about, next, kids}} : Maybe<&2, S.Sub>} + for h_req: {S.parse.first_req(pos) == None{} : Maybe<&2, String>} + {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) + == S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), + False{}, False{}} : S.St} diff --git a/laws/EnvFallsBetweenWordsAndDefault.bend b/laws/EnvFallsBetweenWordsAndDefault.bend new file mode 100644 index 0000000..c6e89b2 --- /dev/null +++ b/laws/EnvFallsBetweenWordsAndDefault.bend @@ -0,0 +1,191 @@ +# `parse_env(spec, words, vars)` reads `words` exactly as `parse` reads them +# against `spec` with every argument's default, in every command, replaced by +# its **fallback**, and answers what that parse answers, except as +# laws/EnvValueOutsideChoicesFails.bend says. An argument's fallback is its +# env value when it has one and is not a flag; for a flag with an env value, +# `true` when that value, lowercased, is none of `0`, `false`, `no`, `off`, +# `n` and `f`, and no fallback otherwise; and for an argument with no env +# value, its default. So a value the words bind wins over the env value and +# the env value over the default (laws/DefaultsFillUnbound.bend), an argument +# with a fallback is filled by it and needs nothing from the words to satisfy +# `required` (laws/RequiredMustBind.bend), a required positional with a +# fallback does not block a subcommand (laws/SubcommandSelects.bend), and +# `parse_env(spec, words, [])` is `parse(spec, words)`. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: `env` gives an argument its env var and keeps the rest of it +law env_sets: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for ev: Maybe<&2, String> + for +dd: Maybe<&2, String> + for +cs: List<&2, String> + for +var: String + {Shake.env(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, var) + == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{Some{var}, dd}, cs} : S.Arg} + +# LAW: an argument with no env var has no env value +law env_of_none: + for vars: List<&2, Shake.Var> + {S.env.of(None{}, vars) == None{} : Maybe<&2, String>} + +# LAW: an env var no pair names has no env value +law env_of_nil: + for var: String + {S.env.of(Some{var}, []) == None{} : Maybe<&2, String>} + +# LAW: the first pair an env var names gives its env value, when its value +# is not empty +law env_of_hit: + for +var: String + for +nn: String + for +vv: String + for rest: List<&2, Shake.Var> + for h_eq: {String.eq(nn, var) == True{} : Bool} + for h_full: {String.is_empty(vv) == False{} : Bool} + {S.env.of(Some{var}, (nn, vv) <> rest) == Some{vv} : Maybe<&2, String>} + +# LAW: an empty value in the first pair an env var names gives no env value, +# whatever later pairs hold +law env_of_empty: + for +var: String + for +nn: String + for +vv: String + for rest: List<&2, Shake.Var> + for h_eq: {String.eq(nn, var) == True{} : Bool} + for h_empty: {String.is_empty(vv) == True{} : Bool} + {S.env.of(Some{var}, (nn, vv) <> rest) == None{} : Maybe<&2, String>} + +# LAW: a pair named otherwise is passed over +law env_of_skip: + for +var: String + for +nn: String + for +vv: String + for +rest: List<&2, Shake.Var> + for h_eq: {String.eq(nn, var) == False{} : Bool} + {S.env.of(Some{var}, (nn, vv) <> rest) == S.env.of(Some{var}, rest) : Maybe<&2, String>} + +# the env values that turn a flag off, lowercased +def off_words() -> List<&2, String>: + ["0", "false", "no", "off", "n", "f"] + +# LAW: an argument other than a flag that has an env value falls back to it +law fallback_env: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for +vv: String + for h_kind: {S.env.flag.is(kind) == False{} : Bool} + for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} + {S.resolve.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars) + == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{vv}}, cs} : S.Arg} + +# LAW: a flag with an env value that, lowercased, is not an off word falls +# back to `true` +law fallback_flag_on: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for +vv: String + for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} + for h_on: {S.allowed.ok(off_words(), String.to_lower(vv)) == False{} : Bool} + {S.resolve.arg(S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, dd}, cs}, vars) + == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Some{"true"}}, cs} : S.Arg} + +# LAW: a flag with an env value that, lowercased, is an off word has no +# fallback +law fallback_flag_off: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for +vv: String + for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} + for h_off: {S.allowed.ok(off_words(), String.to_lower(vv)) == True{} : Bool} + {S.resolve.arg(S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, dd}, cs}, vars) + == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, None{}}, cs} : S.Arg} + +# LAW: an argument with no env value falls back to its default +law fallback_default: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +ev: Maybe<&2, String> + for +dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for h_env: {S.env.of(ev, vars) == None{} : Maybe<&2, String>} + {S.resolve.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars) + == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs} : S.Arg} + +# LAW: a spec's arguments are resolved in the root command and below it +law resolve_spec: + for +nn: String + for +ab: String + for +ver: Maybe<&2, String> + for args: List<&2, S.Arg> + for subs: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + {S.resolve(S.Cli{nn, ab, ver, args, subs}, vars) + == S.Cli{nn, ab, ver, S.resolve.args(args, vars), S.resolve.subs(subs, vars)} : S.Cli} + +# LAW: a command's arguments are resolved one by one, in order +law resolve_args_con: + for aa: S.Arg + for tt: List<&2, S.Arg> + for +vars: List<&2, Shake.Var> + {S.resolve.args(aa <> tt, vars) == S.resolve.arg(aa, vars) <> S.resolve.args(tt, vars) : List<&2, S.Arg>} + +# LAW: a subcommand's arguments are resolved, and every subcommand under it +law resolve_subs_con: + for +nn: String + for +ab: String + for args: List<&2, S.Arg> + for kids: List<&2, S.Sub> + for tt: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + {S.resolve.subs(S.Sub{nn, ab, args, kids} <> tt, vars) + == S.Sub{nn, ab, S.resolve.args(args, vars), S.resolve.subs(kids, vars)} <> S.resolve.subs(tt, vars) + : List<&2, S.Sub>} + +# LAW: `parse_env` is `parse` against the spec with every default replaced by +# its fallback, then the check laws/EnvValueOutsideChoicesFails.bend describes +law env_parse: + for +spec: Shake.Cli + for ws: List<&2, String> + for +vars: List<&2, Shake.Var> + {Shake.parse_env(spec, ws, vars) == S.env.check(spec, vars, Shake.parse(S.resolve(spec, vars), ws)) + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: with no pairs, `parse_env` is `parse` +law env_none: + for +spec: Shake.Cli + for ws: List<&2, String> + {Shake.parse_env(spec, ws, []) == Shake.parse(spec, ws) : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/EnvValueOutsideChoicesFails.bend b/laws/EnvValueOutsideChoicesFails.bend new file mode 100644 index 0000000..3d13d53 --- /dev/null +++ b/laws/EnvValueOutsideChoicesFails.bend @@ -0,0 +1,201 @@ +# When the parse laws/EnvFallsBetweenWordsAndDefault.bend describes succeeds +# and an argument other than a flag, of a command on the selected path, has an +# env value outside its nonempty choices that the command's Matched binds to +# it, `parse_env` fails instead with `BadValue{at, name, value}` of the first +# such argument, the root command first and each command's arguments in spec +# order, where `at` is the selected path. A word's value outside the choices +# never binds (laws/ChoicesRestrictValues.bend), so only an argument filled +# from its env value can be bound to it. When that parse fails, `parse_env` +# fails with the same error. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: a failed parse fails `parse_env` with the same error +law env_check_fail: + for spec: Shake.Cli + for vars: List<&2, Shake.Var> + for ee: Shake.ParseErr + {S.env.check(spec, vars, Fail{ee}) == Fail{ee} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: a successful parse that bound no env value outside its choices is +# what `parse_env` answers +law env_check_good: + for nn: String + for ab: String + for ver: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + for +mm: Shake.Matched + for h_none: {S.env.bad.go(mm, args, subs, vars) == None{} : Maybe<&2, Shake.Var>} + {S.env.check(S.Cli{nn, ab, ver, args, subs}, vars, Done{mm}) == Done{mm} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: a successful parse that bound an env value outside its choices fails +# with BadValue at the selected path +law env_check_bad: + for nn: String + for ab: String + for ver: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + for +mm: Shake.Matched + for +name: String + for +vv: String + for h_bad: {S.env.bad.go(mm, args, subs, vars) == Some{(name, vv)} : Maybe<&2, Shake.Var>} + {S.env.check(S.Cli{nn, ab, ver, args, subs}, vars, Done{mm}) == Fail{S.BadValue{Shake.path_of(mm), name, vv}} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: the last command on the path is checked among its own arguments and +# bindings +law bad_go_leaf: + for +bs: List<&2, S.Bind> + for +args: List<&2, S.Arg> + for subs: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + {S.env.bad.go(S.Leaf{bs}, args, subs, vars) == S.env.bad.args(args, vars, bs) : Maybe<&2, Shake.Var>} + +# LAW: a command's own arguments come before the subcommand selected under it +law bad_go_root: + for +bs: List<&2, S.Bind> + for +name: String + for +next: Shake.Matched + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + for +pp: Shake.Var + for h_here: {S.env.bad.args(args, vars, bs) == Some{pp} : Maybe<&2, Shake.Var>} + {S.env.bad.go(S.Node{bs, name, next}, args, subs, vars) == Some{pp} : Maybe<&2, Shake.Var>} + +# LAW: with none among a command's own arguments, the subcommand selected +# under it is checked among its arguments and bindings +law bad_go_down: + for +bs: List<&2, S.Bind> + for +name: String + for +next: Shake.Matched + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + for +vars: List<&2, Shake.Var> + for h_none: {S.env.bad.args(args, vars, bs) == None{} : Maybe<&2, Shake.Var>} + {S.env.bad.go(S.Node{bs, name, next}, args, subs, vars) + == S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars) + : Maybe<&2, Shake.Var>} + +# LAW: within a command, the first argument in spec order is the one reported +law bad_args_here: + for +aa: S.Arg + for +tt: List<&2, S.Arg> + for +vars: List<&2, Shake.Var> + for +bs: List<&2, S.Bind> + for +pp: Shake.Var + for h_here: {S.env.bad.arg(aa, vars, bs) == Some{pp} : Maybe<&2, Shake.Var>} + {S.env.bad.args(aa <> tt, vars, bs) == Some{pp} : Maybe<&2, Shake.Var>} + +# LAW: an argument that is fine passes the check on to the next +law bad_args_next: + for +aa: S.Arg + for +tt: List<&2, S.Arg> + for +vars: List<&2, Shake.Var> + for +bs: List<&2, S.Bind> + for h_none: {S.env.bad.arg(aa, vars, bs) == None{} : Maybe<&2, Shake.Var>} + {S.env.bad.args(aa <> tt, vars, bs) == S.env.bad.args(tt, vars, bs) : Maybe<&2, Shake.Var>} + +# LAW: no command has a bad argument when there are no pairs +law bad_args_nil: + for args: List<&2, S.Arg> + for +bs: List<&2, S.Bind> + {S.env.bad.args(args, [], bs) == None{} : Maybe<&2, Shake.Var>} + +# LAW: an argument other than a flag whose env value is outside its choices +# and bound in its command is reported with that value +law bad_arg_some: + for +nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for +kind: S.ArgKind + for hp: String + for req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for +bs: List<&2, S.Bind> + for +vv: String + for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} + for h_kind: {S.env.flag.is(kind) == False{} : Bool} + for h_out: {S.allowed(cs, vv) == False{} : Bool} + for h_bound: {List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv) == True{} : Bool} + {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == Some{(nn, vv)} + : Maybe<&2, Shake.Var>} + +# LAW: an argument with no env value is never reported +law bad_arg_unset: + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for kind: S.ArgKind + for hp: String + for req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for bs: List<&2, S.Bind> + for h_env: {S.env.of(ev, vars) == None{} : Maybe<&2, String>} + {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} + : Maybe<&2, Shake.Var>} + +# LAW: a flag is never reported +law bad_arg_flag: + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for hp: String + for req: Bool + for ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for cs: List<&2, String> + for vars: List<&2, Shake.Var> + for bs: List<&2, S.Bind> + {S.env.bad.arg(S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} + : Maybe<&2, Shake.Var>} + +# LAW: an env value its choices allow is never reported +law bad_arg_allowed: + for +nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for +kind: S.ArgKind + for hp: String + for req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for +bs: List<&2, S.Bind> + for +vv: String + for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} + for h_in: {S.allowed(cs, vv) == True{} : Bool} + {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} + : Maybe<&2, Shake.Var>} + +# LAW: an env value its command does not bind is never reported +law bad_arg_unbound: + for +nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for +kind: S.ArgKind + for hp: String + for req: Bool + for +ev: Maybe<&2, String> + for dd: Maybe<&2, String> + for +cs: List<&2, String> + for +vars: List<&2, Shake.Var> + for +bs: List<&2, S.Bind> + for +vv: String + for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} + for h_free: {List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv) == False{} : Bool} + {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} + : Maybe<&2, Shake.Var>} diff --git a/laws/EnvVarsAskedInOrder.bend b/laws/EnvVarsAskedInOrder.bend new file mode 100644 index 0000000..bf13586 --- /dev/null +++ b/laws/EnvVarsAskedInOrder.bend @@ -0,0 +1,57 @@ +# The names `env_vars(spec)` asks `IO.get_env` for are the env vars of the +# arguments that have one, the root command's arguments first and then each +# subcommand's, depth first, each command's arguments in spec order. +import Base +import ../src/cli.bend as S + +# LAW: `env_vars` asks for the root command's env vars, then its +# subcommands' +law env_names_root: + for nn: String + for ab: String + for ver: Maybe<&2, String> + for args: List<&2, S.Arg> + for subs: List<&2, S.Sub> + {S.env.names(S.Cli{nn, ab, ver, args, subs}) + == List.append(&2, String, S.env.names.args(args), S.env.names.forest(subs)) : List<&2, String>} + +# LAW: a subcommand's env vars come before those of the subcommands under it, +# and those before its next sibling's +law env_names_forest: + for nn: String + for ab: String + for args: List<&2, S.Arg> + for kids: List<&2, S.Sub> + for tt: List<&2, S.Sub> + {S.env.names.forest(S.Sub{nn, ab, args, kids} <> tt) + == List.append(&2, String, S.env.names.args(args), + List.append(&2, String, S.env.names.forest(kids), S.env.names.forest(tt))) : List<&2, String>} + +# LAW: an argument with an env var names it, in spec order +law env_names_some: + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for kind: S.ArgKind + for hp: String + for req: Bool + for +var: String + for dd: Maybe<&2, String> + for cs: List<&2, String> + for tt: List<&2, S.Arg> + {S.env.names.args(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{Some{var}, dd}, cs} <> tt) + == var <> S.env.names.args(tt) : List<&2, String>} + +# LAW: an argument with no env var names none +law env_names_none: + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for kind: S.ArgKind + for hp: String + for req: Bool + for dd: Maybe<&2, String> + for cs: List<&2, String> + for tt: List<&2, S.Arg> + {S.env.names.args(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{None{}, dd}, cs} <> tt) + == S.env.names.args(tt) : List<&2, String>} diff --git a/laws/ErrTextEmptyForRequests.bend b/laws/ErrTextEmptyForRequests.bend new file mode 100644 index 0000000..2b09581 --- /dev/null +++ b/laws/ErrTextEmptyForRequests.bend @@ -0,0 +1,26 @@ +# `err_text(spec, err)` is empty exactly when `help_path(err)` is `Some` or +# `err` is `NeedVersion{}`, that is, when the parse failed with a request for +# help or for the version. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/VersionRequest.bend as D1 + +# LAW: a request for help has no error text, for every spec and path +law err_text_help: + for spec: Shake.Cli + for path: List<&2, String> + {Shake.err_text(spec, S.NeedHelp{path}) == "" : String} + +# LAW: a request for the version has no error text, for every spec +law err_text_version: + for spec: Shake.Cli + {Shake.err_text(spec, S.NeedVersion{}) == "" : String} + +# LAW: the error text is empty exactly when the parse asked for help or for +# the version +law err_text_iff: + for +spec: Shake.Cli + for +err: Shake.ParseErr + {String.is_empty(Shake.err_text(spec, err)) + == Bool.or(Maybe.is_some(&2, List<&2, String>, Shake.help_path(err)), D1.version.is(err)) : Bool} diff --git a/laws/ErrTextNamesArgumentForm.bend b/laws/ErrTextNamesArgumentForm.bend new file mode 100644 index 0000000..769a9ce --- /dev/null +++ b/laws/ErrTextNamesArgumentForm.bend @@ -0,0 +1,120 @@ +# `err_text` names the argument of a `Missing{at, name}`, `NoValue{at, name}`, +# `BadValue{at, name, value}` or `Repeated{at, name}` error by the **display +# form** of the argument named `name` of the deepest command on the path `at` +# that has one, the root first and each name of `at` reached as `help` reaches +# it (laws/HelpFollowsPath.bend): for a flag, `--` and its long spelling, else `-` and its +# short spelling; for an option, the same followed by ` `, and by `...` +# for one built with `many`; for a positional, `` when it is required +# and `[NAME]` otherwise, followed by `...` for a rest; `NAME` being the +# argument's name in upper case. A flag or option with neither spelling (an +# empty spelling counts as none), and a name no command on the path has, is +# named by `name` itself. The message of a `BadValue` whose argument has +# nonempty choices `c1`, ..., `cn` ends with the line ` [possible values: c1, +# ..., cn]`. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 +import ./defs/HelpPages.bend as D2 + +# the pages a path walks through from `how`, `how` first, each name moving as +# `help` moves +def pages(path: List<&2, String>, +how: S.How) -> List<&2, S.How>: + match path: + case Nil{}: + [how] + case Con{+hh, tt}: + how <> pages(tt, S.help.step(how, hh)) + +# a later find when there is one, else an earlier +def keep_later(here: Maybe<&2, S.Arg>, later: Maybe<&2, S.Arg>) -> Maybe<&2, S.Arg>: + match later: + case None{}: + here + case Some{aa}: + Some{aa} + +# the argument named `name` of the last page that has one +def deepest(hs: List<&2, S.How>, +name: String) -> Maybe<&2, S.Arg>: + match hs: + case Nil{}: + None{} + case Con{hh, tt}: + keep_later(S.by_name(S.help.how_args(hh), name), deepest(tt, name)) + +# clap's line of the values a found argument allows, when it has choices +def values_line(found: Maybe<&2, S.Arg>) -> String: + match found: + case None{}: + "" + case Some{aa}: + S.Arg{_n, _s, _l, _k, _h, _r, _d, cs} = aa + match cs: + case Nil{}: + "" + case Con{cc, rest}: + "\n [possible values: " ++ String.join(cc <> rest, ", ") ++ "]" + +# LAW: the argument an error is about is the one named so on the deepest +# command of its path that has one, the root first +law err_find: + for +spec: Shake.Cli + for +at: List<&2, String> + for +name: String + {S.err.find(spec, at, name) == deepest(pages(at, D2.root(spec)), name) : Maybe<&2, S.Arg>} + +# LAW: an argument's display form: a flag by `--long`, else `-s`, an option +# with its value mark after, a positional by `` or `[NAME]`, `...` +# after a rest +law err_form: + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +fb: S.Fallback + for +cs: List<&2, String> + {S.err.form(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) == D1.form_of(S.Arg{nn, sh, lo, kind, hp, req, fb, + cs}) : String} + +# LAW: a missing argument is named by its display form +law err_missing_named: + for +spec: Shake.Cli + for +at: List<&2, String> + for +name: String + {Shake.err_text(spec, S.Missing{at, name}) + == S.err.wrap(spec, at, "error: the following required argument was not provided:\n " ++ + D1.named(deepest(pages(at, D2.root(spec)), name), name)) : String} + +# LAW: an option missing its value is named by its display form +law err_novalue_named: + for +spec: Shake.Cli + for +at: List<&2, String> + for +name: String + {Shake.err_text(spec, S.NoValue{at, name}) + == S.err.wrap(spec, at, "error: a value is required for '" ++ D1.named(deepest(pages(at, D2.root(spec)), name), + name) ++ + "' but none was supplied") : String} + +# LAW: a value outside the choices names its argument by its display form, +# then lists the choices +law err_bad_named: + for +spec: Shake.Cli + for +at: List<&2, String> + for +name: String + for +value: String + {Shake.err_text(spec, S.BadValue{at, name, value}) + == S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ + D1.named(deepest(pages(at, D2.root(spec)), name), name) ++ "'" ++ values_line(deepest(pages(at, D2.root(spec)), + name))) + : String} + +# LAW: an argument given twice is named by its display form +law err_repeated_named: + for +spec: Shake.Cli + for +at: List<&2, String> + for +name: String + {Shake.err_text(spec, S.Repeated{at, name}) + == S.err.wrap(spec, at, "error: the argument '" ++ D1.named(deepest(pages(at, D2.root(spec)), name), name) ++ + "' cannot be used multiple times") : String} diff --git a/laws/ErrTextShowsSuggestion.bend b/laws/ErrTextShowsSuggestion.bend new file mode 100644 index 0000000..7be3fe4 --- /dev/null +++ b/laws/ErrTextShowsSuggestion.bend @@ -0,0 +1,12 @@ +# When `suggestion(spec, err)` is `Some{s}`, `err_text(spec, err)` contains +# `s`. +import Base +import ../main.bend as Shake + +# LAW: the error text shows the suggestion, when there is one +law err_text_tip: + for +spec: Shake.Cli + for +err: Shake.ParseErr + for +ss: String + for h_sug: {Shake.suggestion(spec, err) == Some{ss} : Maybe<&2, String>} + {String.contains(Shake.err_text(spec, err), ss) == True{} : Bool} diff --git a/laws/ErrTextShowsUsage.bend b/laws/ErrTextShowsUsage.bend new file mode 100644 index 0000000..483a0ba --- /dev/null +++ b/laws/ErrTextShowsUsage.bend @@ -0,0 +1,46 @@ +# For every error but a request for help or for the version, `err_path(err)` +# is the path of subcommands the parse had selected at the word that failed +# it, and `err_text(spec, err)` shows the usage line of the command at that +# path, as `help(spec, err_path(err))` does. `err_path(NeedVersion{})` is +# `[]`. +# +# also rests on: laws/ChoiceRefused.bend, laws/EnterMissing.bend, +# laws/FlagLongValued.bend, laws/HelpPage.bend, laws/HelpUnknown.bend, +# laws/LetterFlagAgain.bend, laws/LetterFlagEq.bend, laws/LetterOptAgain.bend, +# laws/LetterUnknown.bend, laws/LetterValueBad.bend, laws/LongNoName.bend, +# laws/LongRefused.bend, laws/NoPosLeft.bend, laws/RawNoPos.bend, +# laws/RawRefused.bend, laws/RepeatedLongFlag.bend, laws/RepeatedLongOpt.bend, +# laws/RequiredMissing.bend, laws/UnknownLong.bend, laws/UnknownShort.bend, +# laws/ValueAbsent.bend, laws/ValueFlagShaped.bend, laws/ValueRefused.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/HelpPages.bend as D1 +import ./defs/VersionRequest.bend as D2 + +# LAW: every error but a request for help names the command path where the +# parse failed, and `err_path` reads it; a request for the version names the +# root +law err_path_at: + for +at: List<&2, String> + for +word: String + for +name: String + for +value: String + {[Shake.err_path(S.UnknownFlag{at, word}), Shake.err_path(S.Missing{at, name}), Shake.err_path(S.NoValue{at, name}), + Shake.err_path(S.BadValue{at, name, value}), Shake.err_path(S.Unexpected{at, word}), + Shake.err_path(S.Repeated{at, name}), Shake.err_path(S.NeedVersion{})] + == [at, at, at, at, at, at, []] : List<&2, List<&2, String>>} + +# LAW: the text of every error but a request for help or for the version is a +# message, then the usage line of the command at its path, as that command's +# help page shows it, then where to look for more +law err_text_usage: + for +spec: Shake.Cli + for +err: Shake.ParseErr + for h_err: {Shake.help_path(err) == None{} : Maybe<&2, List<&2, String>>} + for h_ver: {D2.version.is(err) == False{} : Bool} + exs msg: String + {Shake.err_text(spec, err) + == msg ++ "\n\n" ++ D1.usage_line(D1.reach(Shake.err_path(err), + D1.root(spec))) ++ "\n\n" ++ S.err.hint(Shake.err_path(err)) + : String} diff --git a/laws/FailFrom.bend b/laws/FailFrom.bend new file mode 100644 index 0000000..5db721c --- /dev/null +++ b/laws/FailFrom.bend @@ -0,0 +1,13 @@ +import Base +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: a walker that has failed finishes as that failure, whatever words +# follow (a lemma) +law fail_from: + for st: S.St + for +more: List<&2, String> + for +err: S.ParseErr + for died: {D1.dead_of(st) == Some{err} : Maybe<&2, S.ParseErr>} + {S.parse.finish(S.parse.walk(more, st)) == Fail{err} + : Result<&2, &2, S.ParseErr, S.Matched>} diff --git a/laws/FailureIsFinal.bend b/laws/FailureIsFinal.bend new file mode 100644 index 0000000..27275c6 --- /dev/null +++ b/laws/FailureIsFinal.bend @@ -0,0 +1,17 @@ +# Once a word makes the parse fail, the words after it do not change what +# `parse` answers. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: once a word makes the parse fail, the words after it do not change +# what the parse answers +law fail_stays: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +more: List<&2, String> + for +err: Shake.ParseErr + for +died: {D1.dead_of(S.parse.walk(ws, S.parse.start(spec))) == Some{err} : Maybe<&2, S.ParseErr>} + {Shake.parse(spec, List.append(&2, String, ws, more)) == Shake.parse(spec, ws) + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/FlagLongValued.bend b/laws/FlagLongValued.bend new file mode 100644 index 0000000..1bfc47f --- /dev/null +++ b/laws/FlagLongValued.bend @@ -0,0 +1,38 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free, a long option +# that spells a flag not yet given and gives it a value fails the parse with +# Unexpected of the word, whatever follows +law flag_long_valued: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for +vv: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_once: {S.bound(bs, nn) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Fail{S.Unexpected{path, "--" ++ body}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/FlagRefusesValue.bend b/laws/FlagRefusesValue.bend new file mode 100644 index 0000000..e25a756 --- /dev/null +++ b/laws/FlagRefusesValue.bend @@ -0,0 +1,7 @@ +# A flag given a value (`--verbose=x`, or `-v=x` in a cluster) is refused as +# `Unexpected{word}`. +# +# also rests on: laws/FlagLongValued.bend, laws/LetterFlagEq.bend +import Base + + diff --git a/laws/GetNil.bend b/laws/GetNil.bend new file mode 100644 index 0000000..14a4d55 --- /dev/null +++ b/laws/GetNil.bend @@ -0,0 +1,7 @@ +import Base +import ../src/cli.bend as S + +# LAW: `get` on a Matched with no bindings is None +law get_nil: + for name: String + {S.get(S.Leaf{[]}, name) == None{} : Maybe<&2, String>} diff --git a/laws/GetReadsOwnBindings.bend b/laws/GetReadsOwnBindings.bend new file mode 100644 index 0000000..071991f --- /dev/null +++ b/laws/GetReadsOwnBindings.bend @@ -0,0 +1,82 @@ +# For every Matched `m` and name: `get(m, name)` is `Some` of the value of the +# last binding of that name that `m`'s command made, or `None` when it made +# none; `get_all(m, name)` is the values of every binding of that name that +# `m`'s command made, in binding order; `on(m, name)` is true exactly when +# `get(m, name)` is `Some{"true"}`. A command's Matched reads only its own +# bindings: the subcommand selected under it changes none of these. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: `get_all` of two lists of bindings is the first's values, then the +# second's +law get_all_append: + for +bs: List<&2, S.Bind> + for +cs: List<&2, S.Bind> + for +name: String + {Shake.get_all(S.Leaf{List.append(&2, S.Bind, bs, cs)}, name) + == List.append(&2, String, Shake.get_all(S.Leaf{bs}, name), + Shake.get_all(S.Leaf{cs}, name)) : List<&2, String>} + +# LAW: one binding of a name gives `get_all` its value +law get_all_hit: + for +name: String + for +vv: String + {Shake.get_all(S.Leaf{[S.Bind{name, vv}]}, name) == [vv] : List<&2, String>} + +# LAW: one binding of another name gives `get_all` nothing +law get_all_miss: + for +other: String + for +name: String + for +vv: String + for differ: {String.eq(other, name) == False{} : Bool} + {Shake.get_all(S.Leaf{[S.Bind{other, vv}]}, name) == [] : List<&2, String>} + +# LAW: `get` is the value of the last binding of the name +law get_last: + for +bs: List<&2, S.Bind> + for +cs: List<&2, S.Bind> + for +name: String + for +vv: String + for after: {Shake.get_all(S.Leaf{cs}, name) == [] : List<&2, String>} + {Shake.get(S.Leaf{List.append(&2, S.Bind, bs, S.Bind{name, vv} <> cs)}, name) + == Some{vv} : Maybe<&2, String>} + +# LAW: `get` is None when nothing binds the name +law get_none: + for +bs: List<&2, S.Bind> + for +name: String + for none: {Shake.get_all(S.Leaf{bs}, name) == [] : List<&2, String>} + {Shake.get(S.Leaf{bs}, name) == None{} : Maybe<&2, String>} + +# LAW: a flag is on exactly when `get` reads Some "true" +law on_some: + for +found: Shake.Matched + for +name: String + for +vv: String + for h_get: {Shake.get(found, name) == Some{vv} : Maybe<&2, String>} + {Shake.on(found, name) == String.eq(vv, "true") : Bool} + +# LAW: nothing is on that nothing bound +law on_none: + for +found: Shake.Matched + for +name: String + for h_get: {Shake.get(found, name) == None{} : Maybe<&2, String>} + {Shake.on(found, name) == False{} : Bool} + +# LAW: a command's `get_all` reads only its own bindings: the subcommand +# selected under it changes nothing +law get_all_node: + for +bs: List<&2, S.Bind> + for +nn: String + for +below: Shake.Matched + for +name: String + {Shake.get_all(S.Node{bs, nn, below}, name) == Shake.get_all(S.Leaf{bs}, name) : List<&2, String>} + +# LAW: a command's `get` reads only its own bindings +law get_node: + for +bs: List<&2, S.Bind> + for +nn: String + for +below: Shake.Matched + for +name: String + {Shake.get(S.Node{bs, nn, below}, name) == Shake.get(S.Leaf{bs}, name) : Maybe<&2, String>} diff --git a/laws/HelpEndsWithBuiltinRows.bend b/laws/HelpEndsWithBuiltinRows.bend new file mode 100644 index 0000000..5ac1f51 --- /dev/null +++ b/laws/HelpEndsWithBuiltinRows.bend @@ -0,0 +1,15 @@ +# The Options block of a page ends with a row `Print help` spelled as the +# requests for help laws/HelpWordAsksForHelp.bend answers on its command: `-h, --help` when +# the command has neither the short spelling `h` nor the long spelling `help`, +# `--help` when it has `h` but not `help`, and no row when it has `help`. On +# the root page of a spec with a version a row `Print version` follows, +# spelled as the requests laws/VersionWordAsksForVersion.bend answers: `-V, --version` when the root +# has neither the short spelling `V` nor the long spelling `version`, +# `--version` when it has `V` but not `version`, and no row when it has +# `version`. These rows are laid out as the other rows of the block, and make +# the usage line show `[OPTIONS]`. +# +# also rests on: laws/BuiltinsListed.bend +import Base + + diff --git a/laws/HelpFollowsPath.bend b/laws/HelpFollowsPath.bend new file mode 100644 index 0000000..a8abaa3 --- /dev/null +++ b/laws/HelpFollowsPath.bend @@ -0,0 +1,38 @@ +# `help(spec, path)` renders the page of the command reached by following each +# name of `path` from the root through the subcommands, skipping a name that +# is not a subcommand where it stands. +# +# also rests on: laws/HelpPage.bend +import Base +import ../src/cli.bend as S + +# LAW: a name that is a subcommand where the walk stands moves the page to +# that subcommand, its usage line gaining the name +law reach_child: + for +bin: String + for +about: String + for +version: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + for +usage: String + for +name: String + for +sn: String + for +sa: String + for +sargs: List<&2, S.Arg> + for +ssubs: List<&2, S.Sub> + for h_sub: {S.find_sub(subs, name) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} + {S.help.step(S.How{bin, about, version, args, subs, usage}, name) + == S.How{sn, sa, None{}, sargs, ssubs, usage ++ " " ++ name} : S.How} + +# LAW: a name that is no subcommand where the walk stands is skipped +law reach_skip: + for +bin: String + for +about: String + for +version: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + for +usage: String + for +name: String + for h_sub: {S.find_sub(subs, name) == None{} : Maybe<&2, S.Sub>} + {S.help.step(S.How{bin, about, version, args, subs, usage}, name) + == S.How{bin, about, version, args, subs, usage} : S.How} diff --git a/laws/HelpListsEveryArgument.bend b/laws/HelpListsEveryArgument.bend new file mode 100644 index 0000000..1dce1c0 --- /dev/null +++ b/laws/HelpListsEveryArgument.bend @@ -0,0 +1,90 @@ +# A page lists each subcommand of its command exactly once, in spec order, +# followed by `help`. When its command has positionals, an Arguments block +# after the subcommands lists each exactly once, in spec order, by the mark +# the usage line gives it without its leading space, then its help text and +# the extras an option line has (laws/HelpShowsEnvVar.bend); a command with no positional +# has no Arguments block. The Options block lists each flag and option exactly +# once, in spec order, followed by the built-in rows of laws/HelpEndsWithBuiltinRows.bend. Its +# usage line names each positional exactly once, in spec order, as `` +# when required and `[NAME]` otherwise, with `...` after a rest positional. +# +# also rests on: laws/ArgLineMark.bend, laws/BuiltinsListed.bend +import Base +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# the entry every Commands block ends with +def help_sub() -> S.Sub: + S.Sub{"help", "Print this message or the help of the given subcommand(s)", [], []} + +# one Commands line per subcommand, its name and what it is about, in order +def sub_lines(subs: List<&2, S.Sub>, +ww: Nat) -> String: + match subs: + case Nil{}: + "" + case Con{+hh, tt}: + S.help.cmd_line(S.help.sub_name(hh), S.help.sub_about(hh), ww) ++ sub_lines(tt, ww) + +# LAW: a page's Commands block has one line for each subcommand of its +# command, in spec order, then one for `help` +law cmds_listed: + for +subs: List<&2, S.Sub> + {S.help.cmds(subs) + == "Commands:\n" ++ sub_lines(List.append(&2, S.Sub, subs, [help_sub()]), S.help.cmd_width(subs, 0n)) ++ "\n" + : String} + +# whether an argument is a flag or an option +def is_opt(aa: S.Arg) -> Bool: + Bool.not(S.pos.is(aa)) + +# one Options line per argument, in order +def opt_lines(args: List<&2, S.Arg>, +ww: Nat) -> String: + match args: + case Nil{}: + "" + case Con{+hh, tt}: + S.help.opt_line(hh, ww) ++ "\n" ++ opt_lines(tt, ww) + +# LAW: a page's Options block has one line for each flag and option of its +# command, in spec order, and none for a positional +law opts_listed: + for +args: List<&2, S.Arg> + {S.help.opts(args) + == S.help.opts.go(opt_lines(List.filter(~S.Arg, ~is_opt, args), S.help.opt_width(args, 0n))) : String} + +# the marks of a list of positionals, in order +def marks(args: List<&2, S.Arg>) -> String: + match args: + case Nil{}: + "" + case Con{+hh, tt}: + D1.mark(hh) ++ marks(tt) + +# LAW: a usage line names each positional of its command once, in spec +# order, as `` when required and `[NAME]` otherwise, with `...` after a +# rest positional +law usage_listed: + for +name: String + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + {S.help.usage(name, args, subs) + == "Usage: " ++ name ++ S.help.opts_hint(args) ++ marks(List.filter(~S.Arg, ~S.pos.is, + args)) ++ S.help.cmd_hint(subs) + : String} + +# one Arguments line per argument, in order +def arg_lines(args: List<&2, S.Arg>, +ww: Nat) -> String: + match args: + case Nil{}: + "" + case Con{+hh, tt}: + S.help.arg_line(hh, ww) ++ "\n" ++ arg_lines(tt, ww) + +# LAW: a page's Arguments block has one line for each positional of its +# command, in spec order, and none for a flag or an option; with no +# positional it is empty +law args_listed: + for +args: List<&2, S.Arg> + {S.help.args(args) + == S.help.args.go(arg_lines(List.filter(~S.Arg, ~S.pos.is, args), S.help.arg_width(S.pos_of(args), 0n))) + : String} diff --git a/laws/HelpPage.bend b/laws/HelpPage.bend new file mode 100644 index 0000000..247b61d --- /dev/null +++ b/laws/HelpPage.bend @@ -0,0 +1,9 @@ +import Base +import ../main.bend as Shake +import ./defs/HelpPages.bend as D1 + +# LAW: `help` renders the page of the command a path reaches from the root +law help_page: + for spec: Shake.Cli + for +path: List<&2, String> + {Shake.help(spec, path) == D1.page(D1.reach(path, D1.root(spec))) : String} diff --git a/laws/HelpShowsEnvVar.bend b/laws/HelpShowsEnvVar.bend new file mode 100644 index 0000000..f4d0ee5 --- /dev/null +++ b/laws/HelpShowsEnvVar.bend @@ -0,0 +1,37 @@ +# The Options or Arguments line of an argument that has an env var `NAME` +# shows `[env: NAME]` after its help text, before its `[default: ...]` and +# `[possible values: ...]`, as clap's does; the line of one with no env var is +# what it would be without this row. +# +# also rests on: laws/ArgLineMark.bend +import Base +import ../src/cli.bend as S + +# LAW: an option line shows the env var after the help text, before the +# default and the possible values +law help_env_shown: + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for kind: S.ArgKind + for hp: String + for req: Bool + for +var: String + for +dd: Maybe<&2, String> + for +cs: List<&2, String> + {S.help.extra(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{Some{var}, dd}, cs}) + == (" [env: " ++ var ++ "]") ++ S.help.extra.go(S.text.of(dd, ""), cs) : String} + +# LAW: an option line with no env var shows only the default and the possible +# values, as before +law help_env_none: + for nn: String + for sh: Maybe<&2, String> + for lo: Maybe<&2, String> + for kind: S.ArgKind + for hp: String + for req: Bool + for +dd: Maybe<&2, String> + for +cs: List<&2, String> + {S.help.extra(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{None{}, dd}, cs}) + == S.help.extra.go(S.text.of(dd, ""), cs) : String} diff --git a/laws/HelpUnknown.bend b/laws/HelpUnknown.bend new file mode 100644 index 0000000..1f07bc5 --- /dev/null +++ b/laws/HelpUnknown.bend @@ -0,0 +1,25 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker reading a help path, a +# word that names no subcommand where the path stands fails the parse with +# Unexpected of the word, whatever follows +law help_unknown: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen} : S.St} + for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.Unexpected{path, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/HelpWalk.bend b/laws/HelpWalk.bend new file mode 100644 index 0000000..ddad4c8 --- /dev/null +++ b/laws/HelpWalk.bend @@ -0,0 +1,26 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/HelpPathNames.bend as D1 + +# LAW: wherever the words before them leave the walker reading a help path, +# words that each name a subcommand under the one before extend the path, in +# order +law help_walk: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +ns: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen} : S.St} + for h_ns: {D1.help_names(ns, subs) == True{} : Bool} + exs kids: List<&2, S.Sub> + {S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)) + == S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen} : S.St} diff --git a/laws/HelpWordAsksForHelp.bend b/laws/HelpWordAsksForHelp.bend new file mode 100644 index 0000000..70a9d04 --- /dev/null +++ b/laws/HelpWordAsksForHelp.bend @@ -0,0 +1,141 @@ +# Before `--` and before any positional of the current command is bound, a +# plain word `help`, a word `--help` when no argument of the current command +# has the long spelling `help`, or a word `-h` when no argument of the current +# command has the short spelling `h` or the long spelling `help`, makes the +# parse fail with `NeedHelp{path}`, where `path` is the selected path followed +# by the remaining words, as long as each names a subcommand under the one +# before; the first remaining word that does not makes the parse fail with +# `Unexpected{word}`. A command that declares its own long `help` gets +# `--help` as that argument (laws/LongOptionSplitsAtEquals.bend), and `-h` is +# read as any other short option word (laws/ShortClusterReadsLetters.bend); +# `help` still asks for help. +# +# also rests on: laws/HelpUnknown.bend, laws/HelpWalk.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/HelpPathNames.bend as D1 + +# LAW: wherever the words before it leave the walker free, before `--` and +# before any positional of the current command is bound, a word `help` starts +# a help path at the selected path +law help_step: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + {S.parse.walk(List.append(&2, String, pre, ["help"]), S.parse.start(spec)) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + +# LAW: wherever the words before it leave the walker free, before `--` and +# before any positional of the current command is bound, a word `help` +# followed by words that each name a subcommand under the one before, until +# the words end, fails the parse with NeedHelp of the selected path followed +# by those words +law help_path: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +ns: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + for h_ns: {D1.help_names(ns, subs) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["help"]), ns)) + == Fail{S.NeedHelp{List.append(&2, String, path, ns)}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: wherever the words before it leave the walker free, before `--` and +# before any positional of the current command is bound, a word `--help` +# starts a help path at the selected path, as `help` does, when no argument of +# the current command spells long `help` +law dash_help_step: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + for h_none: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} + {S.parse.walk(List.append(&2, String, pre, ["--help"]), S.parse.start(spec)) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + +# LAW: wherever the words before it leave the walker free, before `--` and +# before any positional of the current command is bound, a word `--help`, +# when no argument of the current command spells long `help`, followed by +# words that each name a subcommand under the one before, until the words +# end, fails the parse with NeedHelp of the selected path followed by those +# words, as `help` does +law dash_help_path: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +ns: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + for h_none: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} + for h_ns: {D1.help_names(ns, subs) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--help"]), ns)) + == Fail{S.NeedHelp{List.append(&2, String, path, ns)}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: wherever the words before it leave the walker free, before `--` and +# before any positional of the current command is bound, a word `-h` starts a +# help path at the selected path, as `help` does, when no argument of the +# current command spells short `h` or long `help` +law short_help_step: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + for h_short: {S.by_short(args, "h") == None{} : Maybe<&2, S.Arg>} + for h_long: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} + {S.parse.walk(List.append(&2, String, pre, ["-h"]), S.parse.start(spec)) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + +# LAW: wherever the words before it leave the walker free, before `--` and +# before any positional of the current command is bound, a word `-h`, when +# no argument of the current command spells short `h` or long `help`, +# followed by words that each name a subcommand under the one before, until +# the words end, fails the parse with NeedHelp of the selected path followed +# by those words, as `help` does +law short_help_path: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +ns: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + for h_short: {S.by_short(args, "h") == None{} : Maybe<&2, S.Arg>} + for h_long: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} + for h_ns: {D1.help_names(ns, subs) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["-h"]), ns)) + == Fail{S.NeedHelp{List.append(&2, String, path, ns)}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/HitAppend.bend b/laws/HitAppend.bend new file mode 100644 index 0000000..6372561 --- /dev/null +++ b/laws/HitAppend.bend @@ -0,0 +1,11 @@ +import Base +import ../src/cli.bend as S + +# LAW: a flag binding's hit keeps whatever list follows it (a lemma) +law hit_append: + for bb: Bool + for +vv: String + for xs: List<&2, String> + for ys: List<&2, String> + {S.get_all.bind.hit(bb, vv, List.append(&2, String, xs, ys)) + == List.append(&2, String, S.get_all.bind.hit(bb, vv, xs), ys) : List<&2, String>} diff --git a/laws/Kept.bend b/laws/Kept.bend new file mode 100644 index 0000000..39c835a --- /dev/null +++ b/laws/Kept.bend @@ -0,0 +1,33 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ./defs/ValuesAtPath.bend as D1 + +# LAW: when the words before `tok` reach a state where `tok` stays in the +# command at `path` and puts `Bind{name, vv}` in front of the bindings `bs` it +# made, and the parse succeeds, `get_all` of the name in that command's Matched +# reads the values bound to it before `tok`, then `vv`, then any bound after, +# whatever follows (a lemma: every binding row is its step fact through this, +# as every refusal row is through `refused`) +law kept: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +st: S.St + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +name: String + for +vv: String + for +bs: List<&2, S.Bind> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} + for h_step: {Grow.view(S.parse.step(tok, st)) == Grow.View{args, up, path, S.Bind{name, vv} <> bs} : Grow.View} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), vv <> after)} + : Maybe<&2, List<&2, String>>} diff --git a/laws/KeptAll.bend b/laws/KeptAll.bend new file mode 100644 index 0000000..9d2aac8 --- /dev/null +++ b/laws/KeptAll.bend @@ -0,0 +1,34 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ./defs/ValuesAtPath.bend as D1 + +# LAW: when the words `mid`, read where the words before them leave the +# walker, stay in the command at `path` and put bindings `xs` in front of the +# bindings `bs` it made, and the parse succeeds, `get_all` of a name in that +# command's Matched reads its values in `bs`, then its values in `xs`, both in +# the order given, then any it bound after, whatever follows (a lemma) +law kept_all: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +mid: List<&2, String> + for +more: List<&2, String> + for +st: S.St + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +xs: List<&2, S.Bind> + for +bs: List<&2, S.Bind> + for +name: String + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} + for h_mid: {Grow.view(S.parse.walk(mid, st)) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} + : Grow.View} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, mid), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), + List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after))} + : Maybe<&2, List<&2, String>>} diff --git a/laws/KeptSet.bend b/laws/KeptSet.bend new file mode 100644 index 0000000..b616632 --- /dev/null +++ b/laws/KeptSet.bend @@ -0,0 +1,11 @@ +import Base +import ../main.bend as Shake +import ../src/args.bend as Args + +# LAW: a name `IO.get_env` found a value for is kept with that value, in front +# of the rest (a lemma for ../laws/.bend's reading) +law kept_set: + for +nn: String + for vv: String + for rest: List<&2, Shake.Var> + {Args.kept(nn, Done{vv}, rest) == (nn, vv) <> rest : List<&2, Shake.Var>} diff --git a/laws/KeptUnset.bend b/laws/KeptUnset.bend new file mode 100644 index 0000000..d65f629 --- /dev/null +++ b/laws/KeptUnset.bend @@ -0,0 +1,11 @@ +import Base +import ../main.bend as Shake +import ../src/args.bend as Args + +# LAW: a name `IO.get_env` failed for gives no pair (a lemma for +# ../laws/.bend's reading) +law kept_unset: + for nn: String + for ee: U32 & String + for rest: List<&2, Shake.Var> + {Args.kept(nn, Fail{ee}, rest) == rest : List<&2, Shake.Var>} diff --git a/laws/LastSnoc.bend b/laws/LastSnoc.bend new file mode 100644 index 0000000..f072aa3 --- /dev/null +++ b/laws/LastSnoc.bend @@ -0,0 +1,9 @@ +import Base +import ../src/cli.bend as S + +# LAW: the last of a list that ends in `vv` is `vv`, whatever the default +law last_snoc: + for xs: List<&2, String> + for +vv: String + for dflt: Maybe<&2, String> + {S.get.last(List.append(&2, String, xs, [vv]), dflt) == Some{vv} : Maybe<&2, String>} diff --git a/laws/LetterFlagAgain.bend b/laws/LetterFlagAgain.bend new file mode 100644 index 0000000..94987fb --- /dev/null +++ b/laws/LetterFlagAgain.bend @@ -0,0 +1,30 @@ +import Base +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: a letter that spells a flag already given refuses the whole word as +# Repeated +law letter_flag_again: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_again: {S.bound(bs, nn) == True{} : Bool} + {D1.dead_of(S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{})) + == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} diff --git a/laws/LetterFlagEq.bend b/laws/LetterFlagEq.bend new file mode 100644 index 0000000..0949687 --- /dev/null +++ b/laws/LetterFlagEq.bend @@ -0,0 +1,30 @@ +import Base +import ../src/cli.bend as S + +# LAW: a letter that spells a flag not yet given, with `=` after it, refuses +# the whole word as Unexpected +law letter_flag_eq: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_once: {S.bound(bs, nn) == False{} : Bool} + for h_eq: {String.starts_with(rest, "=") == True{} : Bool} + {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.St{S.Dead{S.Unexpected{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} diff --git a/laws/LetterOptAgain.bend b/laws/LetterOptAgain.bend new file mode 100644 index 0000000..d52fa47 --- /dev/null +++ b/laws/LetterOptAgain.bend @@ -0,0 +1,30 @@ +import Base +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: a letter that spells an option built with `opt` already given refuses +# the whole word as Repeated +law letter_opt_again: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_again: {S.bound(bs, nn) == True{} : Bool} + {D1.dead_of(S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{})) + == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} diff --git a/laws/LetterUnknown.bend b/laws/LetterUnknown.bend new file mode 100644 index 0000000..d742fca --- /dev/null +++ b/laws/LetterUnknown.bend @@ -0,0 +1,20 @@ +import Base +import ../src/cli.bend as S + +# LAW: a letter no argument of the current command spells refuses the whole +# word as UnknownFlag, whatever letters came before it +law letter_unknown: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>} + {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.St{S.Dead{S.UnknownFlag{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} diff --git a/laws/LetterValue.bend b/laws/LetterValue.bend new file mode 100644 index 0000000..4554131 --- /dev/null +++ b/laws/LetterValue.bend @@ -0,0 +1,35 @@ +import Base +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# LAW: a letter that spells an option taking a value now, with more chars +# after it and no `=`, takes those chars as its value, verbatim, when its +# choices allow them, and the cluster ends +law letter_value: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool} + for h_noeq: {String.starts_with(rest, "=") == False{} : Bool} + for h_some: {String.is_empty(rest) == False{} : Bool} + for h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == True{} : Bool} + {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} diff --git a/laws/LetterValueBad.bend b/laws/LetterValueBad.bend new file mode 100644 index 0000000..02416f9 --- /dev/null +++ b/laws/LetterValueBad.bend @@ -0,0 +1,34 @@ +import Base +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# LAW: a letter that spells an option taking a value now, with a glued value +# its choices do not allow, refuses the whole word as BadValue +law letter_value_bad: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool} + for h_noeq: {String.starts_with(rest, "=") == False{} : Bool} + for h_some: {String.is_empty(rest) == False{} : Bool} + for h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == False{} : Bool} + {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} diff --git a/laws/LetterValueEq.bend b/laws/LetterValueEq.bend new file mode 100644 index 0000000..706c334 --- /dev/null +++ b/laws/LetterValueEq.bend @@ -0,0 +1,33 @@ +import Base +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# LAW: a letter that spells an option taking a value now, followed by `=`, +# takes everything after the `=` as its value, verbatim and possibly empty, +# when its choices allow it +law letter_value_eq: + for +cc: Char + for +vv: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool} + for h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} + {S.cluster(SCon{cc, "=" ++ vv}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} diff --git a/laws/LongArgStep.bend b/laws/LongArgStep.bend new file mode 100644 index 0000000..87cc7a0 --- /dev/null +++ b/laws/LongArgStep.bend @@ -0,0 +1,22 @@ +import Base +import ../src/cli.bend as S + +# LAW: a free walker hands a long option word to `take_long.found`, with the +# argument its name spells and the value after `=` (a step lemma) +law long_arg_step: + for +body: String + for +name: String + for +val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + {S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, + False{}, seen) : S.St} diff --git a/laws/LongBinds.bend b/laws/LongBinds.bend new file mode 100644 index 0000000..3596c9b --- /dev/null +++ b/laws/LongBinds.bend @@ -0,0 +1,48 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 +import ./defs/ValuesAtPath.bend as D2 + +# LAW: wherever the words before it leave the walker free, a long option +# `--name=vv` that spells an option built with `opt` not yet given, or one +# built with `many`, and whose choices allow `vv`, binds `vv`: when the parse +# succeeds, `get_all` of the option reads the values given it before, then +# `vv`, whatever follows +law long_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for +vv: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>} + for h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool} + for h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D2.all_at(found, path, nn) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nn), vv <> after)} + : Maybe<&2, List<&2, String>>} diff --git a/laws/LongNoName.bend b/laws/LongNoName.bend new file mode 100644 index 0000000..8d7d30c --- /dev/null +++ b/laws/LongNoName.bend @@ -0,0 +1,28 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free, a word `--=...`, +# with nothing between `--` and `=`, fails the parse with UnknownFlag of the +# word, whatever follows +law long_no_name: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for +val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for +h_empty: {String.is_empty(name) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Fail{S.UnknownFlag{path, "--" ++ body}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/LongOptionSplitsAtEquals.bend b/laws/LongOptionSplitsAtEquals.bend new file mode 100644 index 0000000..0c310b5 --- /dev/null +++ b/laws/LongOptionSplitsAtEquals.bend @@ -0,0 +1,32 @@ +# A word that starts with `--`, is not exactly `--`, and has at least one char +# before its first `=` is a long option: the chars between `--` and the first +# `=` (or the end) are its spelling, and when there is an `=` everything after +# it, verbatim and possibly empty, is its value. A word `--=...` is refused as +# `UnknownFlag{word}`. +# +# also rests on: laws/LongBinds.bend, laws/LongNoName.bend +import Base +import ../src/cli.bend as S + +# whether a string has no `=` +def no_eq(ss: String) -> Bool: + match ss: + case SNil{}: + True{} + case SCon{+cc, tt}: + Bool.and(Bool.not(Char.is_eq(cc, '=')), no_eq(tt)) + +# LAW: the `=` cut of a long option's body splits it at its first `=`: the +# chars before it are the spelling, and everything after it, verbatim and +# possibly empty, is the value +law cut_first_eq: + for +name: String + for +vv: String + for h_name: {no_eq(name) == True{} : Bool} + {S.cut_eq(name ++ ("=" ++ vv)) == (name, Some{vv}) : String & Maybe<&2, String>} + +# LAW: a long option's body with no `=` is all spelling, with no value +law cut_no_eq: + for +name: String + for h_name: {no_eq(name) == True{} : Bool} + {S.cut_eq(name) == (name, None{}) : String & Maybe<&2, String>} diff --git a/laws/LongRefused.bend b/laws/LongRefused.bend new file mode 100644 index 0000000..64961b2 --- /dev/null +++ b/laws/LongRefused.bend @@ -0,0 +1,41 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# LAW: wherever the words before it leave the walker free, a long option +# `--name=vv` that spells an option built with `opt` not yet given, or one +# built with `many`, and whose choices do not allow `vv`, fails the parse with +# BadValue of the option and `vv`, whatever follows +law long_refused: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for +vv: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>} + for h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool} + for h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Fail{S.BadValue{path, nn, vv}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/LooksFlagLong.bend b/laws/LooksFlagLong.bend new file mode 100644 index 0000000..0b0c419 --- /dev/null +++ b/laws/LooksFlagLong.bend @@ -0,0 +1,7 @@ +import Base +import ../src/cli.bend as S + +# LAW: a word that starts `--` is flag-shaped +law looks_flag_long: + for ss: String + {S.looks_flag("--" ++ ss) == True{} : Bool} diff --git a/laws/NoPosLeft.bend b/laws/NoPosLeft.bend new file mode 100644 index 0000000..111b30a --- /dev/null +++ b/laws/NoPosLeft.bend @@ -0,0 +1,26 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free with no positional +# left, a plain word that names no subcommand fails the parse with Unexpected +# of the word, whatever follows +law no_pos_left: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, [], False{}, seen} : S.St} + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for h_help: {String.eq(tok, "help") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.Unexpected{path, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/OnNil.bend b/laws/OnNil.bend new file mode 100644 index 0000000..e843599 --- /dev/null +++ b/laws/OnNil.bend @@ -0,0 +1,7 @@ +import Base +import ../src/cli.bend as S + +# LAW: no flag is on in a Matched with no bindings +law on_nil: + for name: String + {S.on(S.Leaf{[]}, name) == False{} : Bool} diff --git a/laws/OnceDead.bend b/laws/OnceDead.bend new file mode 100644 index 0000000..c480905 --- /dev/null +++ b/laws/OnceDead.bend @@ -0,0 +1,11 @@ +import Base +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: the once-check, told the name was given before, fails with Repeated +# whatever the step it guards would have done (a lemma) +law once_dead: + for st: S.St + for +nn: String + for +path: List<&2, String> + {D1.dead_of(S.parse.once(True{}, st, nn, path)) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} diff --git a/laws/OptionTakesNextWord.bend b/laws/OptionTakesNextWord.bend new file mode 100644 index 0000000..132f3e2 --- /dev/null +++ b/laws/OptionTakesNextWord.bend @@ -0,0 +1,9 @@ +# An option given no value in its own word takes the next word as its value, +# unless that word starts with `-` and is not `-`, or there is none; then the +# parse fails with `NoValue{name}`. +# +# also rests on: laws/ValueAbsent.bend, laws/ValueBinds.bend, +# laws/ValueFlagShaped.bend +import Base + + diff --git a/laws/PlainWordIsNeverOption.bend b/laws/PlainWordIsNeverOption.bend new file mode 100644 index 0000000..5580faf --- /dev/null +++ b/laws/PlainWordIsNeverOption.bend @@ -0,0 +1,37 @@ +# A plain word is never read as an option. +# +# also rests on: laws/HelpWalk.bend, laws/RawNext.bend, laws/ValueBinds.bend, +# laws/ValueRefused.bend +import Base +import ../src/cli.bend as S + +# LAW: in a free walker before `--`, a word that does not start with `-`, other +# than a `help` that asks for help, is read as a subcommand or a positional, +# never as an option +law plain_word_step: + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_help: {Bool.and(String.eq(tok, "help"), Bool.not(seen)) == False{} : Bool} + {S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) : S.St} + +# LAW: in a free walker before `--`, the word `-` is read as a subcommand or a +# positional, never as an option +law dash_word_step: + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + {S.parse.step("-", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.parse.word.go(S.find_sub(subs, "-"), "-", args, up, subs, path, bs, pos, False{}, seen) : S.St} diff --git a/laws/PosBinds.bend b/laws/PosBinds.bend new file mode 100644 index 0000000..c882dd9 --- /dev/null +++ b/laws/PosBinds.bend @@ -0,0 +1,45 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ValuesAtPath.bend as D1 + +# LAW: wherever the words before it leave the walker free with a positional +# next, a plain word that names no subcommand and that the positional's +# choices allow is its value: when the parse succeeds, `get_all` of the +# positional reads the values bound to it before, then the word, whatever +# follows +law pos_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + False{}, seen} : S.St} + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for h_help: {String.eq(tok, "help") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} + for h_ok: {S.allowed(cs, tok) == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), tok <> after)} + : Maybe<&2, List<&2, String>>} diff --git a/laws/PosNext.bend b/laws/PosNext.bend new file mode 100644 index 0000000..0be1098 --- /dev/null +++ b/laws/PosNext.bend @@ -0,0 +1,39 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free with a positional +# next, a plain word that names no subcommand and that the positional's +# choices allow binds to it, and the walker goes on free with the positionals +# after it pending (a rest positional stays pending) +law pos_next: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + False{}, seen} : S.St} + for h_dd: {String.eq(tok, "--") == False{} : Bool} + for h_help: {String.eq(tok, "help") == False{} : Bool} + for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} + for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} + for h_ok: {S.allowed(cs, tok) == True{} : Bool} + {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St} diff --git a/laws/PositionalsBindInOrder.bend b/laws/PositionalsBindInOrder.bend new file mode 100644 index 0000000..101619f --- /dev/null +++ b/laws/PositionalsBindInOrder.bend @@ -0,0 +1,103 @@ +# Plain words that select no subcommand bind the current command's positionals +# in spec order, one word each; a plain word with no positional left is +# refused as `Unexpected{word}`. +# +# also rests on: laws/EnterStep.bend, laws/NoPosLeft.bend, laws/PosBinds.bend, +# laws/PosNext.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ValuesAtPath.bend as D1 + +# LAW: the walk starts free at the root, with no path, no bindings, the root's +# arguments current and its positionals pending +law start_state: + for +name: String + for +about: String + for +version: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +subs: List<&2, S.Sub> + {S.parse.walk([], S.parse.start(Shake.app(name, about, version, args, subs))) + == S.St{S.Free{}, args, [], subs, [], [], S.pos_of(args), False{}, False{}} : S.St} + +# LAW: the positionals pending for a list of arguments are those of its first +# part, then those of the rest, in spec order +law pos_of_app: + for xs: List<&2, S.Arg> + for +ys: List<&2, S.Arg> + {S.pos_of(List.append(&2, S.Arg, xs, ys)) == List.append(&2, S.Arg, S.pos_of(xs), S.pos_of(ys)) : List<&2, S.Arg>} + +# LAW: a positional or rest positional is pending for itself +law pos_of_pos: + for +aa: S.Arg + for h_pos: {S.pos.is(aa) == True{} : Bool} + {S.pos_of([aa]) == [aa] : List<&2, S.Arg>} + +# LAW: a flag or an option is not pending as a positional +law pos_of_opt: + for +aa: S.Arg + for h_opt: {S.pos.is(aa) == False{} : Bool} + {S.pos_of([aa]) == [] : List<&2, S.Arg>} + +# LAW: once a positional of the current command is bound, a word `help` +# before `--` is a plain word like any other: with a positional pending whose +# choices allow it, and no subcommand named `help`, it binds that positional +law help_word_next: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + False{}, True{}} : S.St} + for h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>} + for h_ok: {S.allowed(cs, "help") == True{} : Bool} + {S.parse.walk(List.append(&2, String, pre, ["help"]), S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St} + +# LAW: ... and when the parse succeeds, `get_all` of that positional in its +# command's Matched reads the values bound to it before, then `help` +law help_word_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + False{}, True{}} : S.St} + for h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>} + for h_ok: {S.allowed(cs, "help") == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["help"]), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), "help" <> after)} + : Maybe<&2, List<&2, String>>} diff --git a/laws/PrefixEmpty.bend b/laws/PrefixEmpty.bend new file mode 100644 index 0000000..66dd952 --- /dev/null +++ b/laws/PrefixEmpty.bend @@ -0,0 +1,6 @@ +import Base + +# LAW: every string starts with the empty string (a lemma) +law prefix_empty: + for ss: String + {String.starts_with(ss, "") == True{} : Bool} diff --git a/laws/RawBinds.bend b/laws/RawBinds.bend new file mode 100644 index 0000000..75bb0f5 --- /dev/null +++ b/laws/RawBinds.bend @@ -0,0 +1,40 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ValuesAtPath.bend as D1 + +# LAW: wherever the words before it leave the walker free after `--` with a +# positional next, any word its choices allow, whatever its shape, is its +# value: when the parse succeeds, `get_all` of the positional reads the values +# bound to it before, then the word, whatever follows +law raw_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + True{}, seen} : S.St} + for h_ok: {S.allowed(cs, tok) == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), tok <> after)} + : Maybe<&2, List<&2, String>>} diff --git a/laws/RawNext.bend b/laws/RawNext.bend new file mode 100644 index 0000000..824d498 --- /dev/null +++ b/laws/RawNext.bend @@ -0,0 +1,35 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free after `--`, any +# word, whatever its shape, is read as a plain word bound to the next +# positional: one its choices allow binds, and the walker goes on free after +# `--` with the positionals after it pending +law raw_next: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + True{}, seen} : S.St} + for h_ok: {S.allowed(cs, tok) == True{} : Bool} + {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{}} : S.St} diff --git a/laws/RawNoPos.bend b/laws/RawNoPos.bend new file mode 100644 index 0000000..fca47ac --- /dev/null +++ b/laws/RawNoPos.bend @@ -0,0 +1,22 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free after `--` with no +# positional left, any word fails the parse with Unexpected of the word, +# whatever follows +law raw_no_pos: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, [], True{}, seen} : S.St} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.Unexpected{path, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/RawRefused.bend b/laws/RawRefused.bend new file mode 100644 index 0000000..257b059 --- /dev/null +++ b/laws/RawRefused.bend @@ -0,0 +1,33 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free after `--` with a +# positional next whose choices do not allow it, any word fails the parse +# with BadValue, whatever follows +law raw_refused: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +name: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +rest: List<&2, S.Arg> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + True{}, seen} : S.St} + for h_bad: {S.allowed(cs, tok) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.BadValue{path, name, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/Refused.bend b/laws/Refused.bend new file mode 100644 index 0000000..53238f9 --- /dev/null +++ b/laws/Refused.bend @@ -0,0 +1,20 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: when the words before `tok` reach a state where `tok` fails the walk, +# the parse fails with that error, read as a request for the version when it +# is one, whatever follows (a lemma: every refusal row is its step fact +# through this) +law refused: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +st: S.St + for +err: S.ParseErr + for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} + for h_step: {D1.dead_of(S.parse.step(tok, st)) == Some{err} : Maybe<&2, S.ParseErr>} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.version.err(spec, err)} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/RepeatedLongFlag.bend b/laws/RepeatedLongFlag.bend new file mode 100644 index 0000000..b9df427 --- /dev/null +++ b/laws/RepeatedLongFlag.bend @@ -0,0 +1,38 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free, a long option that +# spells a flag already given fails the parse with Repeated of its name, +# whatever follows +law repeated_long_flag: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for +val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_again: {S.bound(bs, nn) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Fail{S.Repeated{path, nn}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/RepeatedLongOpt.bend b/laws/RepeatedLongOpt.bend new file mode 100644 index 0000000..f198168 --- /dev/null +++ b/laws/RepeatedLongOpt.bend @@ -0,0 +1,38 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free, a long option that +# spells an option built with `opt` already given fails the parse with Repeated of its name, +# whatever follows +law repeated_long_opt: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for +val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_again: {S.bound(bs, nn) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Fail{S.Repeated{path, nn}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/RepeatsRefusedManyCollects.bend b/laws/RepeatsRefusedManyCollects.bend new file mode 100644 index 0000000..798be42 --- /dev/null +++ b/laws/RepeatsRefusedManyCollects.bend @@ -0,0 +1,13 @@ +# A flag or an option built with `opt` that is given a second time while the +# same command is current, under any of its spellings, fails the parse with +# `Repeated{at, name}`; an argument of a parent command with the same name is +# a different argument (laws/UnknownSpellingRefused.bend). An option built with `many` binds every +# value given, in the order given, and `get_all` of its command's Matched +# reads them all. +# +# also rests on: laws/LetterFlagAgain.bend, laws/LetterOptAgain.bend, +# laws/LetterValue.bend, laws/LetterValueEq.bend, laws/LongBinds.bend, +# laws/RepeatedLongFlag.bend, laws/RepeatedLongOpt.bend +import Base + + diff --git a/laws/RequiredMissing.bend b/laws/RequiredMissing.bend new file mode 100644 index 0000000..e33de1c --- /dev/null +++ b/laws/RequiredMissing.bend @@ -0,0 +1,34 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ./defs/FreeWalker.bend as D1 + +# LAW: when the words end with the walker free and a required argument of a +# command on the selected path unbound and without a default, the parse fails +# with Missing at the selected path +law required_missing: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +qs: List<&2, S.Level> + for +pa: List<&2, S.Arg> + for +qa: List<&2, S.Arg> + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +dflt: S.Fallback + for +cs: List<&2, String> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for h_free: {D1.free(S.parse.walk(ws, S.parse.start(spec))) == True{} : Bool} + for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, + S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), bs} <> up), path} + : Grow.Frame} + for h_unbound: {S.bound(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), + bs), nn) == False{} : Bool} + exs name: String + {Shake.parse(spec, ws) == Fail{S.Missing{path, name}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/RequiredMustBind.bend b/laws/RequiredMustBind.bend new file mode 100644 index 0000000..6f0114b --- /dev/null +++ b/laws/RequiredMustBind.bend @@ -0,0 +1,50 @@ +# A successful parse binds every required argument of every command on the +# selected path, in that command's Matched. Otherwise the parse fails with +# `Missing{name}`. +# +# also rests on: laws/RequiredMissing.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow + +# whether a name has values in the command at `path` in a Matched, when the +# path reaches one +def present_at.go(mm: Maybe<&2, Shake.Matched>, +name: String) -> Maybe<&2, Bool>: + match mm: + case None{}: + None{} + case Some{found}: + Some{Bool.not(List.is_empty(&2, String, Shake.get_all(found, name)))} + +# whether a name has values in the command at `path` in a Matched +def present_at(found: Shake.Matched, path: List<&2, String>, +name: String) -> Maybe<&2, Bool>: + present_at.go(Shake.at(found, path), name) + +# LAW: when the parse succeeds, every required argument of every command on +# the selected path has a value in that command's Matched +law required_present: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +found: Shake.Matched + for +qs: List<&2, S.Level> + for +pa: List<&2, S.Arg> + for +qa: List<&2, S.Arg> + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +dflt: S.Fallback + for +cs: List<&2, String> + for +bs: List<&2, S.Bind> + for +up: List<&2, S.Level> + for +path: List<&2, String> + for +ms: List<&2, String> + for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, + S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), bs} <> up), + List.append(&2, String, path, ms)} : Grow.Frame} + for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} + {present_at(found, path, nn) == Some{True{}} : Maybe<&2, Bool>} diff --git a/laws/RestTakesRemainingWords.bend b/laws/RestTakesRemainingWords.bend new file mode 100644 index 0000000..c958787 --- /dev/null +++ b/laws/RestTakesRemainingWords.bend @@ -0,0 +1,149 @@ +# A rest positional binds every remaining plain word that selects no +# subcommand, in order, each under the rest's name. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ./defs/ValuesAtPath.bend as D1 + +# whether a choices list allows every word +def all_allowed(+cs: List<&2, String>, ws: List<&2, String>) -> Bool: + match ws: + case Nil{}: + True{} + case Con{+ww, tt}: + Bool.and(S.allowed(cs, ww), all_allowed(cs, tt)) + +# LAW: wherever the words before them leave the walker free before `--`, with +# only a rest positional left, plain words that name no subcommand and that its +# choices allow each bind it, in order, and the walker stays free with the +# rest still pending +law rest_next: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +fs: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +nm: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], False{}, seen} + : S.St} + for h_plain: {Walk.all_plain(fs) == True{} : Bool} + for h_sub: {Walk.wk.unfound(subs, fs) == True{} : Bool} + for h_ok: {all_allowed(cs, fs) == True{} : Bool} + {S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), + [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], False{}, Walk.wk.seen(fs, seen)} : S.St} + +# LAW: wherever the words before them leave the walker free before `--`, with +# only a rest positional left, plain words that name no subcommand and that its +# choices allow are its values: when the parse succeeds, `get_all` of the rest +# in its command's Matched reads the values bound to it before, then the words, +# in order, whatever follows +law rest_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +fs: List<&2, String> + for +more: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +nm: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], False{}, seen} + : S.St} + for h_plain: {Walk.all_plain(fs) == True{} : Bool} + for h_sub: {Walk.wk.unfound(subs, fs) == True{} : Bool} + for h_ok: {all_allowed(cs, fs) == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, fs), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, nm) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nm), List.append(&2, String, + fs, after))} + : Maybe<&2, List<&2, String>>} + +# LAW: wherever the words before them leave the walker free after `--`, with +# only a rest positional left, words its choices allow, whatever their shape, +# each bind it, in order, and the walker stays free after `--` with the rest +# still pending +law rest_raw_next: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +fs: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +nm: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], True{}, seen} + : S.St} + for h_ok: {all_allowed(cs, fs) == True{} : Bool} + {S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), + [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], True{}, Walk.wk.seen(fs, seen)} : S.St} + +# LAW: wherever the words before them leave the walker free after `--`, with +# only a rest positional left, words its choices allow, whatever their shape, +# are its values: when the parse succeeds, `get_all` of the rest in its +# command's Matched reads the values bound to it before, then the words, in +# order, whatever follows +law rest_raw_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +fs: List<&2, String> + for +more: List<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +seen: Bool + for +nm: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], True{}, seen} + : S.St} + for h_ok: {all_allowed(cs, fs) == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, fs), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, nm) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nm), List.append(&2, String, + fs, after))} + : Maybe<&2, List<&2, String>>} diff --git a/laws/ShortClusterReadsLetters.bend b/laws/ShortClusterReadsLetters.bend new file mode 100644 index 0000000..0734127 --- /dev/null +++ b/laws/ShortClusterReadsLetters.bend @@ -0,0 +1,91 @@ +# A word that starts with `-`, is not `-` and does not start with `--` is a +# cluster of short options, read letter by letter: a letter that spells a flag +# binds it and the next letter goes on; a letter that spells an option takes +# the rest of the word as its value, after one `=` when the rest starts with +# it (verbatim, and possibly empty), or the next word when nothing is left; a +# letter no argument of the current command spells refuses the whole word as +# `UnknownFlag{word}`, except that a word `-h` may be a request for help +# (laws/HelpWordAsksForHelp.bend) and a word `-V` a request for the version +# (laws/VersionWordAsksForVersion.bend). +# +# also rests on: laws/LetterUnknown.bend, laws/LetterValue.bend, +# laws/LetterValueEq.bend +import Base +import ../src/cli.bend as S +import ./defs/ArgumentLists.bend as D1 + +# LAW: in a free walker before `--`, a word that starts with `-`, is not `-` +# and does not start with `--` is read as a cluster of short options, letter +# by letter, from the walker as it stood +law short_step: + for +cc: Char + for +rest: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool} + for h_dash: {Char.is_eq(cc, '-') == False{} : Bool} + for h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} + {S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.cluster(SCon{cc, rest}, "-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + False{}) : S.St} + +# LAW: a letter that spells a flag not yet given, with no `=` after it, binds +# it, and the next letter goes on +law letter_flag: + for +cc: Char + for +rest: String + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_once: {S.bound(bs, nn) == False{} : Bool} + for h_noeq: {String.starts_with(rest, "=") == False{} : Bool} + {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.cluster(rest, orig, S.St{S.Free{}, args, up, subs, path, S.Bind{nn, "true"} <> bs, pos, raw, seen}, False{}) + : S.St} + +# LAW: a letter that spells an option taking a value now, last in its word, +# leaves the option waiting for the next word +law letter_value_next: + for +cc: Char + for +orig: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +nn: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +req: Bool + for +dflt: S.Fallback + for +cs: List<&2, String> + for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} + : Maybe<&2, S.Arg>} + for h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool} + {S.cluster(SCon{cc, SNil{}}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} diff --git a/laws/SpecErrWhere.bend b/laws/SpecErrWhere.bend new file mode 100644 index 0000000..c50dcae --- /dev/null +++ b/laws/SpecErrWhere.bend @@ -0,0 +1,30 @@ +import Base +import ../main.bend as Shake +import ../src/check.bend as K + +# the command path a report of `check` is at +def spec_path(err: K.SpecErr) -> List<&2, String>: + match err: + case K.RestNotLast{path, _n}: + path + case K.SameName{path, _n}: + path + case K.SameShort{path, _s}: + path + case K.SameLong{path, _l}: + path + case K.SameSub{path, _n}: + path + case K.HelpSub{path}: + path + case K.DefaultNotChoice{path, _n, _v}: + path + case K.RequiredAfterOptional{path, _n}: + path + +# LAW: the text of every report of `check` says first where it is: `spec: ` +# at the root, else the command's path (a lemma for laws/CheckReportsSpecErrors.bend's reports) +law spec_err_where: + for +err: K.SpecErr + exs msg: String + {Shake.spec_err_text(err) == K.at(spec_path(err)) ++ msg : String} diff --git a/laws/StepDies.bend b/laws/StepDies.bend new file mode 100644 index 0000000..8aad214 --- /dev/null +++ b/laws/StepDies.bend @@ -0,0 +1,17 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: when the words before `tok` reach a state where `tok` fails the walk, +# the walk through `tok` has failed with that error (a lemma) +law step_dies: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +tok: String + for +st: S.St + for +err: S.ParseErr + for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} + for h_step: {D1.dead_of(S.parse.step(tok, st)) == Some{err} : Maybe<&2, S.ParseErr>} + {D1.dead_of(S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec))) + == Some{err} : Maybe<&2, S.ParseErr>} diff --git a/laws/SubcommandResultsNavigate.bend b/laws/SubcommandResultsNavigate.bend new file mode 100644 index 0000000..7221b0a --- /dev/null +++ b/laws/SubcommandResultsNavigate.bend @@ -0,0 +1,77 @@ +# For every Matched `m`: `sub_name(m)` is `Some` of the name of the subcommand +# selected under `m`'s command, or `None` when none was; `sub_of(m, name)` is +# that subcommand's Matched when its name is `name`, and `None` otherwise; +# `path_of(m)` is the names of the subcommands selected below `m`'s command, +# in order, so the whole selected path for the Matched `parse` answers; `at(m, +# [])` is `Some{m}`, and `at(m, name <> rest)` is `at` of `sub_of(m, name)` +# and `rest`, or `None` when `sub_of` is `None`. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: no subcommand was selected under a command with no Matched below it +law sub_name_leaf: + for +bs: List<&2, S.Bind> + {Shake.sub_name(S.Leaf{bs}) == None{} : Maybe<&2, String>} + +# LAW: `sub_name` is the name of the subcommand selected +law sub_name_node: + for +bs: List<&2, S.Bind> + for +nn: String + for +below: Shake.Matched + {Shake.sub_name(S.Node{bs, nn, below}) == Some{nn} : Maybe<&2, String>} + +# LAW: `sub_of` the subcommand selected is its Matched +law sub_of_hit: + for +bs: List<&2, S.Bind> + for +nn: String + for +below: Shake.Matched + {Shake.sub_of(S.Node{bs, nn, below}, nn) == Some{below} : Maybe<&2, Shake.Matched>} + +# LAW: `sub_of` any other name is None +law sub_of_miss: + for +bs: List<&2, S.Bind> + for +nn: String + for +below: Shake.Matched + for +name: String + for differ: {String.eq(nn, name) == False{} : Bool} + {Shake.sub_of(S.Node{bs, nn, below}, name) == None{} : Maybe<&2, Shake.Matched>} + +# LAW: `sub_of` is None when no subcommand was selected +law sub_of_leaf: + for +bs: List<&2, S.Bind> + for +name: String + {Shake.sub_of(S.Leaf{bs}, name) == None{} : Maybe<&2, Shake.Matched>} + +# LAW: no names are selected below a command with no subcommand selected +law path_of_leaf: + for +bs: List<&2, S.Bind> + {Shake.path_of(S.Leaf{bs}) == [] : List<&2, String>} + +# LAW: the names selected below a command are its subcommand's name, then the +# names selected below that +law path_of_node: + for +bs: List<&2, S.Bind> + for +nn: String + for +below: Shake.Matched + {Shake.path_of(S.Node{bs, nn, below}) == nn <> Shake.path_of(below) : List<&2, String>} + +# `at` of what `sub_of` found, or None when it found nothing +def at.via(mm: Maybe<&2, Shake.Matched>, rest: List<&2, String>) -> Maybe<&2, Shake.Matched>: + match mm: + case None{}: + None{} + case Some{below}: + Shake.at(below, rest) + +# LAW: `at` an empty path is the Matched itself +law at_nil: + for +found: Shake.Matched + {Shake.at(found, []) == Some{found} : Maybe<&2, Shake.Matched>} + +# LAW: `at` a path follows its first name with `sub_of`, then the rest +law at_cons: + for +found: Shake.Matched + for +name: String + for +rest: List<&2, String> + {Shake.at(found, name <> rest) == at.via(Shake.sub_of(found, name), rest) : Maybe<&2, Shake.Matched>} diff --git a/laws/SubcommandSelects.bend b/laws/SubcommandSelects.bend new file mode 100644 index 0000000..ec205ef --- /dev/null +++ b/laws/SubcommandSelects.bend @@ -0,0 +1,45 @@ +# Before `--`, a plain word that names a subcommand of the current command +# selects it: the selected path gains that name, its arguments become current, +# and the bindings made so far stay with the command that made them; the +# subcommand's own bindings start empty. When a pending positional of the +# current command is required, has no default and is not a rest, the parse +# fails instead with `Missing{name}` of the first such one; a rest positional +# does not block a subcommand, and laws/RequiredMustBind.bend checks it when the words end. +# +# also rests on: laws/EnterMissing.bend, laws/EnterStep.bend +import Base +import ../src/cli.bend as S +import ./defs/BlockingPositional.bend as D1 + +# whether no pending positional blocks a subcommand +def none_block(pos: List<&2, S.Arg>) -> Bool: + match pos: + case Nil{}: + True{} + case Con{hh, tt}: + Bool.and(Bool.not(D1.blocks(hh)), none_block(tt)) + +# LAW: with no pending positional that is required, has no default and is not +# a rest, nothing blocks a subcommand +law none_blocking: + for pos: List<&2, S.Arg> + for +h_none: {none_block(pos) == True{} : Bool} + {S.parse.first_req(pos) == None{} : Maybe<&2, String>} + +# LAW: the positional that blocks a subcommand is the first pending one that is +# required, has no default and is not a rest +law first_blocking: + for pa: List<&2, S.Arg> + for +nm: String + for +sh: Maybe<&2, String> + for +lo: Maybe<&2, String> + for +kind: S.ArgKind + for +hp: String + for +ev: Maybe<&2, String> + for +cs: List<&2, String> + for +qa: List<&2, S.Arg> + for +h_pa: {none_block(pa) == True{} : Bool} + for h_kind: {S.rest.is.kind(kind) == False{} : Bool} + {S.parse.first_req(List.append(&2, S.Arg, pa, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, None{}}, + cs} <> qa)) == Some{nm} + : Maybe<&2, String>} diff --git a/laws/SuggestionPicksClosestLong.bend b/laws/SuggestionPicksClosestLong.bend new file mode 100644 index 0000000..28a36b7 --- /dev/null +++ b/laws/SuggestionPicksClosestLong.bend @@ -0,0 +1,135 @@ +# `suggestion(spec, err)` is `None` for every error but an `UnknownFlag{at, +# word}` whose word starts with `--` (laws/LongOptionSplitsAtEquals.bend, +# laws/UnknownSpellingRefused.bend). For such a word, let `typed` be its chars +# after `--` up to its first `=`, and the **candidates** the long spellings of +# the flags and options of the command at `at`, reached as `help(spec, at)` +# reaches it (laws/HelpFollowsPath.bend), in spec order; a parent command's +# arguments, subcommands and `help` are not candidates. A candidate `c` is +# **close** when `0 < d` and `3 * d <= max(length(typed), length(c))`, where +# `d` is the optimal string alignment distance from `typed` to `c`: the fewest +# single-char insertions, deletions, substitutions and swaps of two adjacent +# chars that turn one into the other, editing no char twice. The suggestion is +# `Some{"--" ++ c}` for the close candidate with the least `d`, the first in +# spec order among equals, and `None` when no candidate is close. A short +# option word never gets one. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/HelpPages.bend as D1 + +# the arguments of a page's command +def how_args(how: S.How) -> List<&2, S.Arg>: + S.How{_b, _a, _v, args, _s, _u} = how + args + +# LAW: only an unknown option can get a suggestion +law sug_other: + for spec: Shake.Cli + for +err: Shake.ParseErr + for h_err: {D1.unknown.is(err) == False{} : Bool} + {Shake.suggestion(spec, err) == None{} : Maybe<&2, String>} + +# LAW: a word that does not start with `--` gets no suggestion +law sug_short: + for +spec: Shake.Cli + for +at: List<&2, String> + for +word: String + for h_word: {String.starts_with(word, "--") == False{} : Bool} + {Shake.suggestion(spec, S.UnknownFlag{at, word}) == None{} : Maybe<&2, String>} + +# LAW: a long option word's suggestion is the closest close candidate to its +# spelling up to `=`, among the long spellings of the command at `at`, reached +# as `help` reaches it, as a long option word +law sug_long: + for +spec: Shake.Cli + for +at: List<&2, String> + for +body: String + for +name: String + for val: Maybe<&2, String> + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + {Shake.suggestion(spec, S.UnknownFlag{at, "--" ++ body}) + == S.sug.found(S.sug.best(S.sug.cands(how_args(D1.reach(at, D1.root(spec)))), name)) : Maybe<&2, String>} + +# LAW: a candidate is suggested as its long option word +law sug_found_some: + for cc: String + {S.sug.found(Some{cc}) == Some{"--" ++ cc} : Maybe<&2, String>} + +# LAW: an argument with a long spelling is a candidate, in spec order +law cands_some: + for nn: String + for sh: Maybe<&2, String> + for ll: String + for kind: S.ArgKind + for hp: String + for req: Bool + for fb: S.Fallback + for cs: List<&2, String> + for tt: List<&2, S.Arg> + {S.sug.cands(S.Arg{nn, sh, Some{ll}, kind, hp, req, fb, cs} <> tt) == ll <> S.sug.cands(tt) : List<&2, String>} + +# LAW: an argument with no long spelling is not a candidate +law cands_none: + for nn: String + for sh: Maybe<&2, String> + for kind: S.ArgKind + for hp: String + for req: Bool + for fb: S.Fallback + for cs: List<&2, String> + for tt: List<&2, S.Arg> + {S.sug.cands(S.Arg{nn, sh, None{}, kind, hp, req, fb, cs} <> tt) == S.sug.cands(tt) : List<&2, String>} + +# LAW: a candidate is close when it is some edits away, and three times as +# many is at most the longer length +law sug_close: + for +typed: String + for +cc: String + {S.sug.close(typed, cc) + == Bool.and(Nat.is_lt(0n, S.osa(typed, cc)), + Nat.is_le(Nat.mul(3n, S.osa(typed, cc)), Nat.max(String.length(typed), String.length(cc)))) : Bool} + +# LAW: no candidates, no closest one +law best_nil: + for typed: String + {S.sug.best([], typed) == None{} : Maybe<&2, String>} + +# LAW: a candidate that is not close is passed over +law best_far: + for +cc: String + for +tt: List<&2, String> + for +typed: String + for h_far: {S.sug.close(typed, cc) == False{} : Bool} + {S.sug.best(cc <> tt, typed) == S.sug.best(tt, typed) : Maybe<&2, String>} + +# LAW: a close candidate with none close after it is the closest +law best_only: + for +cc: String + for +tt: List<&2, String> + for +typed: String + for h_close: {S.sug.close(typed, cc) == True{} : Bool} + for h_rest: {S.sug.best(tt, typed) == None{} : Maybe<&2, String>} + {S.sug.best(cc <> tt, typed) == Some{cc} : Maybe<&2, String>} + +# LAW: a close candidate no farther than the closest after it is the closest: +# the first wins a tie +law best_here: + for +cc: String + for +tt: List<&2, String> + for +typed: String + for +ww: String + for h_close: {S.sug.close(typed, cc) == True{} : Bool} + for h_rest: {S.sug.best(tt, typed) == Some{ww} : Maybe<&2, String>} + for h_le: {Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)) == True{} : Bool} + {S.sug.best(cc <> tt, typed) == Some{cc} : Maybe<&2, String>} + +# LAW: a close candidate farther than the closest after it is passed over +law best_there: + for +cc: String + for +tt: List<&2, String> + for +typed: String + for +ww: String + for h_close: {S.sug.close(typed, cc) == True{} : Bool} + for h_rest: {S.sug.best(tt, typed) == Some{ww} : Maybe<&2, String>} + for h_le: {Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)) == False{} : Bool} + {S.sug.best(cc <> tt, typed) == Some{ww} : Maybe<&2, String>} diff --git a/laws/UnknownLong.bend b/laws/UnknownLong.bend new file mode 100644 index 0000000..a084256 --- /dev/null +++ b/laws/UnknownLong.bend @@ -0,0 +1,32 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the parse, in any command and with +# any bindings, a long option that no argument of that command spells, other +# than a bare `--help` that asks for help, fails the parse with UnknownFlag of +# the word, whatever follows +law unknown_long: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +body: String + for +name: String + for val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_none: {S.by_long(args, name) == None{} : Maybe<&2, S.Arg>} + for h_lh: {S.long_help(name, val, seen) == False{} : Bool} + for h_ver: {S.version.asks(spec, path, "--" ++ body) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) + == Fail{S.UnknownFlag{path, "--" ++ body}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/UnknownLongDies.bend b/laws/UnknownLongDies.bend new file mode 100644 index 0000000..a64016b --- /dev/null +++ b/laws/UnknownLongDies.bend @@ -0,0 +1,28 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/WalkerFailure.bend as D1 + +# LAW: a walk that reaches an unknown long option dies there (a lemma) +law unknown_long_dies: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +body: String + for +name: String + for val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_none: {S.by_long(args, name) == None{} : Maybe<&2, S.Arg>} + for h_lh: {S.long_help(name, val, seen) == False{} : Bool} + {D1.dead_of(S.parse.walk(List.append(&2, String, pre, ["--" ++ body]), S.parse.start(spec))) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} diff --git a/laws/UnknownLongStep.bend b/laws/UnknownLongStep.bend new file mode 100644 index 0000000..79adb1d --- /dev/null +++ b/laws/UnknownLongStep.bend @@ -0,0 +1,24 @@ +import Base +import ../src/cli.bend as S + +# LAW: in a free walker, a long option whose spelling no argument of the +# current command has kills the walk with UnknownFlag of the whole word (a +# step lemma) +law unknown_long_step: + for +body: String + for +name: String + for val: Maybe<&2, String> + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} + for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} + for h_name: {String.is_empty(name) == False{} : Bool} + for h_none: {S.by_long(args, name) == None{} : Maybe<&2, S.Arg>} + for h_lh: {S.long_help(name, val, seen) == False{} : Bool} + {S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} diff --git a/laws/UnknownShort.bend b/laws/UnknownShort.bend new file mode 100644 index 0000000..9291864 --- /dev/null +++ b/laws/UnknownShort.bend @@ -0,0 +1,29 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave the walker free, a short option +# word whose first letter no argument of that command has fails the parse +# with UnknownFlag of the word, whatever follows +law unknown_short: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +cc: Char + for +rest: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + for h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool} + for h_dash: {Char.is_eq(cc, '-') == False{} : Bool} + for h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>} + for h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} + for h_ver: {S.version.asks(spec, path, "-" ++ SCon{cc, rest}) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["-" ++ SCon{cc, rest}]), more)) + == Fail{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/UnknownShortStep.bend b/laws/UnknownShortStep.bend new file mode 100644 index 0000000..4e37774 --- /dev/null +++ b/laws/UnknownShortStep.bend @@ -0,0 +1,22 @@ +import Base +import ../src/cli.bend as S + +# LAW: in a free walker, a short option word whose first letter no argument +# of the current command has kills the walk with UnknownFlag of the whole +# word (a step lemma) +law unknown_short_step: + for +cc: Char + for +rest: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +seen: Bool + for h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool} + for h_dash: {Char.is_eq(cc, '-') == False{} : Bool} + for h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>} + for h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} + {S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} diff --git a/laws/UnknownSpellingRefused.bend b/laws/UnknownSpellingRefused.bend new file mode 100644 index 0000000..6876f77 --- /dev/null +++ b/laws/UnknownSpellingRefused.bend @@ -0,0 +1,11 @@ +# A long or short spelling that no argument of the current command has is +# refused as `UnknownFlag{word}`, except a word `--help` or `-h` that +# laws/HelpWordAsksForHelp.bend reads as a request for help, and a word `--version` or `-V` +# that laws/VersionWordAsksForVersion.bend reads as a request for the version. Arguments of a parent +# command are not matched after a subcommand is selected. +# +# also rests on: laws/EnterStep.bend, laws/LetterUnknown.bend, +# laws/UnknownLong.bend, laws/UnknownShort.bend +import Base + + diff --git a/laws/ValueAbsent.bend b/laws/ValueAbsent.bend new file mode 100644 index 0000000..efc30c9 --- /dev/null +++ b/laws/ValueAbsent.bend @@ -0,0 +1,21 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: when the words end with an option still waiting for its value, the +# parse fails with Missing of the option +law value_absent: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +name: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for h_at: {S.parse.walk(ws, S.parse.start(spec)) + == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} + {Shake.parse(spec, ws) == Fail{S.NoValue{path, name}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/ValueBinds.bend b/laws/ValueBinds.bend new file mode 100644 index 0000000..78dbefb --- /dev/null +++ b/laws/ValueBinds.bend @@ -0,0 +1,34 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/ValuesAtPath.bend as D1 + +# LAW: wherever the words before it leave an option waiting for its value, a +# word that is not flag-shaped and that the option's choices allow is its +# value: when the parse succeeds, `get_all` of the option reads the values +# given it before, then the word, whatever follows +law value_binds: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +name: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for +found: Shake.Matched + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} + for h_flag: {S.looks_flag(tok) == False{} : Bool} + for h_ok: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == True{} : Bool} + for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + exs after: List<&2, String> + {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), tok <> after)} + : Maybe<&2, List<&2, String>>} diff --git a/laws/ValueFlagShaped.bend b/laws/ValueFlagShaped.bend new file mode 100644 index 0000000..d09ed58 --- /dev/null +++ b/laws/ValueFlagShaped.bend @@ -0,0 +1,26 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave an option waiting for its value, a +# word that starts with `-` and is not `-` fails the parse with Missing of the +# option, whatever follows +law value_flag_shaped: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +name: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} + for h_flag: {S.looks_flag(tok) == True{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.NoValue{path, name}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/ValueRefused.bend b/laws/ValueRefused.bend new file mode 100644 index 0000000..8d53916 --- /dev/null +++ b/laws/ValueRefused.bend @@ -0,0 +1,27 @@ +import Base +import ../main.bend as Shake +import ../src/cli.bend as S + +# LAW: wherever the words before it leave an option waiting for its value, a +# word that is not flag-shaped and that the option's choices do not allow +# fails the parse with BadValue of the option and the word, whatever follows +law value_refused: + for +spec: Shake.Cli + for +pre: List<&2, String> + for +more: List<&2, String> + for +tok: String + for +name: String + for +args: List<&2, S.Arg> + for +up: List<&2, S.Level> + for +subs: List<&2, S.Sub> + for +path: List<&2, String> + for +bs: List<&2, S.Bind> + for +pos: List<&2, S.Arg> + for +raw: Bool + for +seen: Bool + for h_at: {S.parse.walk(pre, S.parse.start(spec)) + == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} + for h_flag: {S.looks_flag(tok) == False{} : Bool} + for h_bad: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == False{} : Bool} + {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) + == Fail{S.BadValue{path, name, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} diff --git a/laws/VersionOfNamesRelease.bend b/laws/VersionOfNamesRelease.bend new file mode 100644 index 0000000..12bd1ce --- /dev/null +++ b/laws/VersionOfNamesRelease.bend @@ -0,0 +1,33 @@ +# `version_of(spec, err)` is `Some{name ++ " " ++ version}`, the root +# command's name and the spec's version, when `err` is `NeedVersion{}` and the +# spec has a version, and `None` otherwise. +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/VersionRequest.bend as D1 + +# LAW: a request for the version reads as the root's name and the version +law version_of_some: + for +name: String + for about: String + for +ver: String + for args: List<&2, S.Arg> + for subs: List<&2, S.Sub> + {Shake.version_of(S.Cli{name, about, Some{ver}, args, subs}, S.NeedVersion{}) == Some{name ++ " " ++ ver} + : Maybe<&2, String>} + +# LAW: a spec with no version reads no version, whatever the error +law version_of_none: + for +name: String + for about: String + for args: List<&2, S.Arg> + for subs: List<&2, S.Sub> + for err: Shake.ParseErr + {Shake.version_of(S.Cli{name, about, None{}, args, subs}, err) == None{} : Maybe<&2, String>} + +# LAW: an error that is no request for the version reads no version +law version_of_other: + for +spec: Shake.Cli + for err: Shake.ParseErr + for h_err: {D1.version.is(err) == False{} : Bool} + {Shake.version_of(spec, err) == None{} : Maybe<&2, String>} diff --git a/laws/VersionWordAsksForVersion.bend b/laws/VersionWordAsksForVersion.bend new file mode 100644 index 0000000..bae8981 --- /dev/null +++ b/laws/VersionWordAsksForVersion.bend @@ -0,0 +1,73 @@ +# When the spec has a version, `parse` fails with `NeedVersion{}` wherever it +# would otherwise fail with `UnknownFlag{[], "--version"}`, and wherever it +# would otherwise fail with `UnknownFlag{[], "-V"}` when no argument of the +# root command has the long spelling `version`: that is, when the first word +# to fail the parse is `--version` or `-V` read while the root command is +# current (laws/FailureIsFinal.bend, laws/UnknownSpellingRefused.bend). Every +# other result of the parse is what it would be without this row, and +# `parse_env` reads the version as `parse` does +# (laws/EnvFallsBetweenWordsAndDefault.bend). +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ./defs/HelpPages.bend as D1 + +# whether a word refused at `at` asks for the version: `--version`, or `-V` +# when the root does not spell `--version`, refused at the root of a spec +# with a version +def asks_version(spec: Shake.Cli, +at: List<&2, String>, +word: String) -> Bool: + S.Cli{_n, _a, version, args, _s} = spec + Bool.and(Bool.or(String.eq(word, "--version"), + Bool.and(String.eq(word, "-V"), Maybe.is_none(&2, S.Arg, S.by_long(args, "version")))), + Bool.and(List.is_empty(&2, String, at), Maybe.is_some(&2, String, version))) + +# whether a parse's answer refuses an unknown option +def unknown.res(rr: Result<&2, &2, Shake.ParseErr, Shake.Matched>) -> Bool: + match rr: + case Done{_m}: + False{} + case Fail{ee}: + D1.unknown.is(ee) + +# LAW: where the walk would refuse a word that asks for the version, the +# parse fails with a request for the version +law version_asked: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +at: List<&2, String> + for +word: String + for h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == Fail{S.UnknownFlag{at, word}} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_asks: {asks_version(spec, at, word) == True{} : Bool} + {Shake.parse(spec, ws) == Fail{S.NeedVersion{}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: where the walk would refuse a word that does not ask for the version, +# the parse refuses it +law version_unasked: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +at: List<&2, String> + for +word: String + for h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == Fail{S.UnknownFlag{at, word}} + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_asks: {asks_version(spec, at, word) == False{} : Bool} + {Shake.parse(spec, ws) == Fail{S.UnknownFlag{at, word}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: every other answer of the walk is the parse's +law version_other: + for +spec: Shake.Cli + for +ws: List<&2, String> + for +rr: Result<&2, &2, Shake.ParseErr, Shake.Matched> + for h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == rr + : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + for h_rr: {unknown.res(rr) == False{} : Bool} + {Shake.parse(spec, ws) == rr : Result<&2, &2, Shake.ParseErr, Shake.Matched>} + +# LAW: resolving the env vars keeps the version and every long spelling, so +# `parse_env`'s parse asks for the version where `parse`'s does +law resolve_asks: + for +spec: Shake.Cli + for +vars: List<&2, Shake.Var> + for +at: List<&2, String> + for +word: String + {asks_version(S.resolve(spec, vars), at, word) == asks_version(spec, at, word) : Bool} diff --git a/laws/WalkAppend.bend b/laws/WalkAppend.bend new file mode 100644 index 0000000..0e374ed --- /dev/null +++ b/laws/WalkAppend.bend @@ -0,0 +1,10 @@ +import Base +import ../src/cli.bend as S + +# LAW: walking two word lists is walking the first, then the second (a lemma) +law walk_append: + for xs: List<&2, String> + for ys: List<&2, String> + for st: S.St + {S.parse.walk(List.append(&2, String, xs, ys), st) + == S.parse.walk(ys, S.parse.walk(xs, st)) : S.St} diff --git a/laws/defs/ArgumentLists.bend b/laws/defs/ArgumentLists.bend new file mode 100644 index 0000000..d38b499 --- /dev/null +++ b/laws/defs/ArgumentLists.bend @@ -0,0 +1,262 @@ +# the names, spellings and reports of a command's arguments +import Base +import ../../src/cli.bend as S +import ../../src/check.bend as K + +# whether an option of this kind binds a value given it now: one built with +# `opt` when it was not given before, one built with `many` always +def takes(kk: S.ArgKind, given: Bool) -> Bool: + match kk: + case S.Opt{}: + Bool.not(given) + case S.Many{}: + True{} + case _other: + False{} + +# a flag's or an option's spelling: `--long`, else `-s`, then its value +# mark; its name when it has neither +def spelled(+short: String, +long: String, +name: String, +mark: String) -> String: + Bool.pick(String, String.is_empty(long), + Bool.pick(String, String.is_empty(short), name, "-" ++ short ++ mark), + "--" ++ long ++ mark) + +# a positional's form: `` when required, `[NAME]` otherwise +def pos_form(req: Bool, +nn: String) -> String: + match req: + case True{}: + "<" ++ nn ++ ">" + case False{}: + "[" ++ nn ++ "]" + +# an argument of this kind as an error names it +def form_of.kind(kk: S.ArgKind, +name: String, +sh: String, +lo: String, req: Bool) -> String: + match kk: + case S.Flag{}: + spelled(sh, lo, name, "") + case S.Opt{}: + spelled(sh, lo, name, " <" ++ String.to_upper(name) ++ ">") + case S.Many{}: + spelled(sh, lo, name, " <" ++ String.to_upper(name) ++ ">...") + case S.Pos{}: + pos_form(req, String.to_upper(name)) + case S.Rest{}: + pos_form(req, String.to_upper(name)) ++ "..." + +# an argument's display form +def form_of(aa: S.Arg) -> String: + S.Arg{+name, short, long, kind, _h, req, _d, _c} = aa + form_of.kind(kind, name, S.text.of(short, ""), S.text.of(long, ""), req) + +# a found argument's display form, else the name the error carries +def named(found: Maybe<&2, S.Arg>, +name: String) -> String: + match found: + case None{}: + name + case Some{aa}: + form_of(aa) + +# a positional's mark: `` when required, `[NAME]` otherwise +def mark.req(req: Bool, +nn: String) -> String: + match req: + case True{}: + " <" ++ nn ++ ">" + case False{}: + " [" ++ nn ++ "]" + +# `...` after a rest positional +def mark.rest(kk: S.ArgKind) -> String: + match kk: + case S.Rest{}: + "..." + case S.Pos{}: + "" + case S.Flag{}: + "" + case S.Opt{}: + "" + case S.Many{}: + "" + +# a positional as a usage line names it +def mark(aa: S.Arg) -> String: + S.Arg{+name, _s, _l, kind, _h, required, _d, _c} = aa + mark.req(required, String.to_upper(name)) ++ mark.rest(kind) + +# the names of some arguments +def arg_names(args: List<&2, S.Arg>) -> List<&2, String>: + match args: + case Nil{}: + [] + case Con{hh, tt}: + S.Arg{name, _s, _l, _k, _h, _r, _d, _c} = hh + name <> arg_names(tt) + +# the short spellings of some arguments, of those that have one +def arg_shorts(args: List<&2, S.Arg>) -> List<&2, String>: + match args: + case Nil{}: + [] + case Con{hh, tt}: + S.Arg{_n, short, _l, _k, _h, _r, _d, _c} = hh + K.spelled.add(short, arg_shorts(tt)) + +# the long spellings of some arguments, of those that have one +def arg_longs(args: List<&2, S.Arg>) -> List<&2, String>: + match args: + case Nil{}: + [] + case Con{hh, tt}: + S.Arg{_n, _s, long, _k, _h, _r, _d, _c} = hh + K.spelled.add(long, arg_longs(tt)) + +# a report of a default that is outside nonempty choices +def off_choice( + +path: List<&2, String>, + +name: String, + +cs: List<&2, String>, + dflt: Maybe<&2, String> +) -> List<&2, K.SpecErr>: + match dflt: + case None{}: + [] + case Some{+vv}: + K.one(Bool.and(Bool.not(List.is_empty(&2, String, cs)), Bool.not(List.contains(~String, ~String.eq, cs, vv))), + K.DefaultNotChoice{path, name, vv}) + +# the reports about one argument of a command, against the arguments before +# it, then `after`: its name, short and long spellings where an earlier one +# has them, and its default +def arg_reports( + +path: List<&2, String>, + +before: List<&2, S.Arg>, + aa: S.Arg, + after: List<&2, K.SpecErr> +) -> List<&2, K.SpecErr>: + S.Arg{+name, +short, +long, _k, _h, _r, S.Fallback{_e, dflt}, +cs} = aa + K.both(K.one(List.contains(~String, ~String.eq, arg_names(before), name), K.SameName{path, name}), + K.both(K.one(K.spelled.seen(short, arg_shorts(before)), K.SameShort{path, S.text.of(short, "")}), + K.both(K.one(K.spelled.seen(long, arg_longs(before)), K.SameLong{path, S.text.of(long, "")}), + K.both(off_choice(path, name, cs, dflt), after)))) + +# the reports about each argument, in spec order, given those before them +def args_reports( + args: List<&2, S.Arg>, + +path: List<&2, String>, + +before: List<&2, S.Arg> +) -> List<&2, K.SpecErr>: + match args: + case Nil{}: + [] + case Con{+hh, tt}: + arg_reports(path, before, hh, args_reports(tt, path, hh <> before)) + +# whether one of some arguments is optional +def any_optional(args: List<&2, S.Arg>) -> Bool: + match args: + case Nil{}: + False{} + case Con{hh, tt}: + S.Arg{_n, _s, _l, _k, _h, required, _d, _c} = hh + Bool.or(any_optional(tt), Bool.not(required)) + +# the reports about each positional, in spec order, given those before them: a +# rest positional with one after it, and a required one after an optional one +def pos_reports( + pos: List<&2, S.Arg>, + +path: List<&2, String>, + +before: List<&2, S.Arg> +) -> List<&2, K.SpecErr>: + match pos: + case Nil{}: + [] + case Con{+hh, +tt}: + S.Arg{+name, _s, _l, kind, _h, required, _d, _c} = hh + K.both(K.one(Bool.and(S.rest.is.kind(kind), Bool.not(List.is_empty(&2, S.Arg, tt))), K.RestNotLast{path, name}), + K.both(K.one(Bool.and(required, any_optional(before)), K.RequiredAfterOptional{path, name}), + pos_reports(tt, path, hh <> before))) + +# the names of some subcommands +def sub_names(subs: List<&2, S.Sub>) -> List<&2, String>: + match subs: + case Nil{}: + [] + case Con{hh, tt}: + S.Sub{name, _a, _g, _c} = hh + name <> sub_names(tt) + +# the reports about each subcommand, in spec order, given those before them: +# a name an earlier one has, and the name `help` +def sub_reports( + subs: List<&2, S.Sub>, + +path: List<&2, String>, + +before: List<&2, S.Sub> +) -> List<&2, K.SpecErr>: + match subs: + case Nil{}: + [] + case Con{+hh, tt}: + S.Sub{+name, _a, _g, _c} = hh + K.both(K.one(List.contains(~String, ~String.eq, sub_names(before), name), K.SameSub{path, name}), + K.both(K.one(String.eq(name, "help"), K.HelpSub{path}), sub_reports(tt, path, hh <> before))) + +# one command's reports, at its path: its arguments', its positionals', its +# subcommands' +def cmd_reports( + +path: List<&2, String>, + +args: List<&2, S.Arg>, + subs: List<&2, S.Sub> +) -> List<&2, K.SpecErr>: + K.both(args_reports(args, path, []), K.both(pos_reports(S.pos_of(args), path, []), sub_reports(subs, path, []))) + +# the reports of every command under some subcommands of the command at +# `path`, depth first, each at its own path +def below(subs: List<&2, S.Sub>, +path: List<&2, String>) -> List<&2, K.SpecErr>: + match subs: + case Nil{}: + [] + case Con{hh, tt}: + S.Sub{+name, _a, +args, +kids} = hh + +here = List.append(&2, String, path, [name]) + K.both(cmd_reports(here, args, kids), K.both(below(kids, here), below(tt, path))) + +# whether `vv` is what is left of the word `wd` once some chars, maybe none, are +# dropped from its front +def ends(wd: String, +vv: String) -> Bool: + match wd: + case SNil{}: + String.eq(vv, "") + case SCon{+hh, +tt}: + Bool.or(String.eq(vv, SCon{hh, tt}), ends(tt, vv)) + +# whether `vv` is a piece of one of the words: what is left of it once some +# chars are dropped from its front +def piece(ws: List<&2, String>, +vv: String) -> Bool: + match ws: + case Nil{}: + False{} + case Con{+ww, tt}: + Bool.or(ends(ww, vv), piece(tt, vv)) + +# whether a default is `vv` +def default_is(dd: Maybe<&2, String>, +vv: String) -> Bool: + match dd: + case None{}: + False{} + case Some{dv}: + String.eq(dv, vv) + +# whether `vv` is the default of one of `args` named `name` +def is_default(args: List<&2, S.Arg>, +name: String, +vv: String) -> Bool: + match args: + case Nil{}: + False{} + case Con{hh, tt}: + S.Arg{nn, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c} = hh + Bool.or(Bool.and(String.eq(nn, name), default_is(dd, vv)), is_default(tt, name, vv)) + +# whether a binding's value is `true`, which a flag binds, a piece of one of +# the words, or the default of one of `args` of its name +def given(+ws: List<&2, String>, +args: List<&2, S.Arg>, bb: S.Bind) -> Bool: + S.Bind{+name, +vv} = bb + Bool.or(String.eq(vv, "true"), Bool.or(piece(ws, vv), is_default(args, name, vv))) diff --git a/laws/defs/BlockingPositional.bend b/laws/defs/BlockingPositional.bend new file mode 100644 index 0000000..7a17d9f --- /dev/null +++ b/laws/defs/BlockingPositional.bend @@ -0,0 +1,9 @@ +# whether a pending positional blocks a subcommand +import Base +import ../../src/cli.bend as S + +# whether a pending positional blocks a subcommand: required, with no +# default, and not a rest +def blocks(aa: S.Arg) -> Bool: + S.Arg{_n, _s, _l, kind, _h, req, S.Fallback{_e, dflt}, _c} = aa + Bool.and(Bool.not(S.rest.is.kind(kind)), Bool.and(req, Maybe.is_none(&2, String, dflt))) diff --git a/laws/defs/FreeWalker.bend b/laws/defs/FreeWalker.bend new file mode 100644 index 0000000..a1ae5f6 --- /dev/null +++ b/laws/defs/FreeWalker.bend @@ -0,0 +1,16 @@ +# whether a walker or a mode is free to read an option +import Base +import ../../src/cli.bend as S + +# whether a mode is free +def free.mode(mode: S.Mode) -> Bool: + match mode: + case S.Free{}: + True{} + case _other: + False{} + +# whether a walker is free +def free(st: S.St) -> Bool: + S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st + free.mode(mode) diff --git a/laws/defs/HelpPages.bend b/laws/defs/HelpPages.bend new file mode 100644 index 0000000..5db91e8 --- /dev/null +++ b/laws/defs/HelpPages.bend @@ -0,0 +1,39 @@ +# help pages as help renders them: reaching one, its usage line, its root +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S + +# the page a help walk reaches from `how`: each name that is a subcommand +# where it stands moves to it, and any other name is skipped +def reach(path: List<&2, String>, how: S.How) -> S.How: + match path: + case Nil{}: + how + case Con{+hh, tt}: + reach(tt, S.help.step(how, hh)) + +# the root's page +def root(app: Shake.Cli) -> S.How: + S.Cli{+name, about, version, args, subs} = app + S.How{name, about, version, args, subs, name} + +# the usage line of a page +def usage_line(how: S.How) -> String: + S.How{_b, _a, version, +args, +subs, +usage} = how + S.help.usage(usage, S.help.with(args, version), subs) + +# a page as `help` renders it: its title, what it is about, its usage line +# (`usage_line`), its subcommands, its positionals and its options, the +# built-in flags after the command's own +def page(how: S.How) -> String: + S.How{+bin, +about, +version, +args, +subs, +usage} = how + S.help.page(S.title.go(bin, version), about, S.help.usage(usage, S.help.with(args, version), subs), + S.help.with(args, version), subs) + +# whether an error is an unknown option +def unknown.is(err: Shake.ParseErr) -> Bool: + match err: + case S.UnknownFlag{_at, _word}: + True{} + case _other: + False{} diff --git a/laws/defs/HelpPathNames.bend b/laws/defs/HelpPathNames.bend new file mode 100644 index 0000000..30367f6 --- /dev/null +++ b/laws/defs/HelpPathNames.bend @@ -0,0 +1,21 @@ +# whether each word of a help path names a subcommand under the one before +import Base +import ../../src/cli.bend as S + +# the subcommands under a found one, or none +def kids_of(mm: Maybe<&2, S.Sub>) -> List<&2, S.Sub>: + match mm: + case None{}: + [] + case Some{ss}: + S.Sub{_n, _a, _g, kids} = ss + kids + +# whether each word names a subcommand under the one before, starting from +# `subs` +def help_names(ns: List<&2, String>, +subs: List<&2, S.Sub>) -> Bool: + match ns: + case Nil{}: + True{} + case Con{+nn, tt}: + Bool.and(Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), help_names(tt, kids_of(S.find_sub(subs, nn)))) diff --git a/laws/defs/ValuesAtPath.bend b/laws/defs/ValuesAtPath.bend new file mode 100644 index 0000000..4883ae2 --- /dev/null +++ b/laws/defs/ValuesAtPath.bend @@ -0,0 +1,15 @@ +# the values a Matched holds for a name at a command path +import Base +import ../../main.bend as Shake + +# `get_all` of the command a path reaches in a Matched, when it reaches one +def all_at.go(mm: Maybe<&2, Shake.Matched>, +name: String) -> Maybe<&2, List<&2, String>>: + match mm: + case None{}: + None{} + case Some{found}: + Some{Shake.get_all(found, name)} + +# `get_all` of the command at `path` in a Matched +def all_at(found: Shake.Matched, path: List<&2, String>, +name: String) -> Maybe<&2, List<&2, String>>: + all_at.go(Shake.at(found, path), name) diff --git a/laws/defs/VersionRequest.bend b/laws/defs/VersionRequest.bend new file mode 100644 index 0000000..8aa48af --- /dev/null +++ b/laws/defs/VersionRequest.bend @@ -0,0 +1,12 @@ +# whether an error is a request for the version +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S + +# whether an error is a request for the version +def version.is(err: Shake.ParseErr) -> Bool: + match err: + case S.NeedVersion{}: + True{} + case _other: + False{} diff --git a/laws/defs/WalkerFailure.bend b/laws/defs/WalkerFailure.bend new file mode 100644 index 0000000..67d6070 --- /dev/null +++ b/laws/defs/WalkerFailure.bend @@ -0,0 +1,11 @@ +# the error a walker has failed with, if any +import Base +import ../../src/cli.bend as S + +# the error a walker has failed with, or None while it is still going +def dead_of(st: S.St) -> Maybe<&2, S.ParseErr>: + match st: + case S.St{S.Dead{err}, _a, _g, _s, _p, _b, _o, _r, _n}: + Some{err} + case S.St{_mode, _a, _g, _s, _p, _b, _o, _r, _n}: + None{} diff --git a/main.bend b/main.bend index 18355ee..e89eeb5 100644 --- a/main.bend +++ b/main.bend @@ -132,7 +132,7 @@ def env(arg: S.Arg, +var: String) -> S.Arg: # last, a repeated argument name or spelling, a repeated subcommand name, a # subcommand named `help`, a default outside its choices, a required # positional after an optional one. Empty for a well-formed spec, which is -# what SPEC.md's parse guarantees are about; `parse` does not call it +# what the parse guarantees are about (LAWS.bend); `parse` does not call it def check(spec: S.Cli) -> List<&2, K.SpecErr>: K.check(spec) diff --git a/proofs/AllowedAny.bend b/proofs/AllowedAny.bend new file mode 100644 index 0000000..909a5f9 --- /dev/null +++ b/proofs/AllowedAny.bend @@ -0,0 +1,6 @@ +# proofs of laws/AllowedAny.bend +import Base +import ../laws/AllowedAny.bend as Laws + +def Laws.allowed_any(_vv): + {==} diff --git a/proofs/ArgLineMark.bend b/proofs/ArgLineMark.bend new file mode 100644 index 0000000..7feb439 --- /dev/null +++ b/proofs/ArgLineMark.bend @@ -0,0 +1,52 @@ +# proofs of laws/ArgLineMark.bend +import Base +import ../src/cli.bend as S +import ../laws/ArgLineMark.bend as Laws +import ../laws/defs/ArgumentLists.bend as D1 + +# a usage mark's brackets, as the law's mark writes them +def ha.wrap(req: Bool, +nn: String) -> {S.help.pos.wrap(req, nn) == D1.mark.req(req, nn) : String}: + match req: + case True{}: + {==} + case False{}: + {==} + +# a usage mark's dots, as the law's mark writes them +def ha.dots(kind: S.ArgKind) -> {S.help.pos.dots(S.rest.is.kind(kind)) == D1.mark.rest(kind) : String}: + match kind: + case S.Flag{}: + {==} + case S.Opt{}: + {==} + case S.Pos{}: + {==} + case S.Rest{}: + {==} + case S.Many{}: + {==} + +# a positional's usage hint is the law's mark +def ha.mark( + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +fb: S.Fallback, + +cs: List<&2, String> +) -> {S.help.pos.one(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) == D1.mark(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) + : String}: + +up = String.to_upper(nn) + Equal.trans(String, S.help.pos.wrap(req, up) ++ S.help.pos.dots(S.rest.is.kind(kind)), + D1.mark.req(req, up) ++ S.help.pos.dots(S.rest.is.kind(kind)), D1.mark.req(req, up) ++ D1.mark.rest(kind), + Equal.cong(String, String, xs => xs ++ S.help.pos.dots(S.rest.is.kind(kind)), S.help.pos.wrap(req, up), + D1.mark.req(req, up), ha.wrap(req, up)), + Equal.cong(String, String, xs => D1.mark.req(req, up) ++ xs, S.help.pos.dots(S.rest.is.kind(kind)), + D1.mark.rest(kind), ha.dots(kind))) + +def Laws.arg_line_mark(nn, sh, lo, kind, hp, req, fb, cs, ww): + +aa = {S.Arg{nn, sh, lo, kind, hp, req, fb, cs} : S.Arg} + Equal.cong(String, String, mm => " " ++ S.help.pad(String.drop(mm, 1n), ww) ++ " " ++ hp ++ S.help.extra(aa), + S.help.pos.one(aa), D1.mark(aa), ha.mark(nn, sh, lo, kind, hp, req, fb, cs)) diff --git a/proofs/ArgvDropsProgram.bend b/proofs/ArgvDropsProgram.bend new file mode 100644 index 0000000..dd01a05 --- /dev/null +++ b/proofs/ArgvDropsProgram.bend @@ -0,0 +1,20 @@ +# proofs of laws/ArgvDropsProgram.bend +import Base +import ../src/args.bend as Args +import ../laws/ArgvDropsProgram.bend as Laws + +def Laws.copy_keeps(xs): + match xs: + case Nil{}: + {==} + case Con{+hh, tt}: + ih = Equal.sym(List<&1, String>, Laws.down(Args.copy(tt)), tt, Laws.copy_keeps(tt)) + %ih : {hh <> _ == hh <> tt : List<&1, String>} + {==} + +def Laws.words_keeps(xs): + match xs: + case Nil{}: + {==} + case Con{_hh, tt}: + Laws.copy_keeps(tt) diff --git a/proofs/BindValuesAppend.bend b/proofs/BindValuesAppend.bend new file mode 100644 index 0000000..3d39845 --- /dev/null +++ b/proofs/BindValuesAppend.bend @@ -0,0 +1,6 @@ +# proofs of laws/BindValuesAppend.bend +import Base +import ../laws/BindValuesAppend.bend as Laws +import ./lemmas/AppendedBindings.bend as M1 + + diff --git a/proofs/BindingsComeFromWords.bend b/proofs/BindingsComeFromWords.bend new file mode 100644 index 0000000..75da525 --- /dev/null +++ b/proofs/BindingsComeFromWords.bend @@ -0,0 +1,4769 @@ +# proofs of laws/BindingsComeFromWords.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../src/walk.bend as Walk +import ../src/grow.bend as Grow +import ../laws/BindingsComeFromWords.bend as Laws +import ../laws/WalkAppend.bend as L1 +import ../laws/defs/ArgumentLists.bend as D1 +import ./lemmas/FinishedWalker.bend as M1 +import ./lemmas/NameAppend.bend as M2 +import ./lemmas/WalkSteps.bend as M3 + +# ---- laws/BindingsComeFromWords.bend: every value bound is a piece of a word, `true` or a default + +# anything or true is true +def gv.or_t(bb: Bool) -> {Bool.or(bb, True{}) == True{} : Bool}: + match bb: + case True{}: + {==} + case False{}: + {==} + +# both halves of a true `and` are true +def gv.and_tt( + aa: Bool, + +bb: Bool, + ha: {aa == True{} : Bool}, + hb: {bb == True{} : Bool} +) -> {Bool.and(aa, bb) == True{} : Bool}: + match aa: + case True{}: + hb + case False{}: + Empty.absurd({Bool.and(False{}, bb) == True{} : Bool}, Walk.wk.false_true(ha)) + +# the left half of a true `and` +def gv.and_l(aa: Bool, bb: Bool, +hh: {Bool.and(aa, bb) == True{} : Bool}) -> {aa == True{} : Bool}: + match aa: + case True{}: + {==} + case False{}: + Empty.absurd({False{} == True{} : Bool}, Walk.wk.false_true(hh)) + +# the right half of a true `and` +def gv.and_r(aa: Bool, +bb: Bool, +hh: {Bool.and(aa, bb) == True{} : Bool}) -> {bb == True{} : Bool}: + match aa: + case True{}: + hh + case False{}: + Empty.absurd({bb == True{} : Bool}, Walk.wk.false_true(hh)) + +# a word is what is left of itself +def gv.ends_self(ww: String) -> {D1.ends(ww, ww) == True{} : Bool}: + match ww: + case SNil{}: + {==} + case SCon{+hh, +tt}: + ee = Equal.sym(Bool, String.eq(SCon{hh, tt}, SCon{hh, tt}), True{}, Eq.string_eq_self(SCon{hh, tt})) + %ee : {Bool.or(_, D1.ends(tt, SCon{hh, tt})) == True{} : Bool} + {==} + +# what is left of a word's tail is left of the word +def gv.ends_tail( + +cc: Char, + +tt: String, + +vv: String, + +hh: {D1.ends(tt, vv) == True{} : Bool} +) -> {D1.ends(SCon{cc, tt}, vv) == True{} : Bool}: + ee = Equal.sym(Bool, D1.ends(tt, vv), True{}, hh) + %ee : {Bool.or(String.eq(vv, SCon{cc, tt}), _) == True{} : Bool} + gv.or_t(String.eq(vv, SCon{cc, tt})) + +# dropping nothing leaves the string +def gv.drop0(ss: String) -> {String.drop(ss, 0n) == ss : String}: + match ss: + case SNil{}: + {==} + case SCon{_c, _t}: + {==} + +# whether every piece of `ww` is a piece of one of the words +def gv.sufs(ww: String, +ws: List<&2, String>) -> Bool: + match ww: + case SNil{}: + D1.piece(ws, "") + case SCon{+cc, +tt}: + Bool.and(D1.piece(ws, SCon{cc, tt}), gv.sufs(tt, ws)) + +# every piece of `ww` is a piece of one of the words +def gv.In(+ww: String, +ws: List<&2, String>) -> Type: + {gv.sufs(ww, ws) == True{} : Bool} + +# a word's tail is within the words when the word is +def gv.in_tail(+cc: Char, +tt: String, +ws: List<&2, String>, hh: gv.In(SCon{cc, tt}, ws)) -> gv.In(tt, ws): + gv.and_r(D1.piece(ws, SCon{cc, tt}), gv.sufs(tt, ws), hh) + +# a word with chars dropped from its front is within the words when it is +def gv.in_drop(ww: String, kk: Nat, +ws: List<&2, String>, hh: gv.In(ww, ws)) -> gv.In(String.drop(ww, kk), ws): + match ww kk: + case SNil{} _: + hh + case SCon{+cc, +tt} 0n: + hh + case SCon{+cc, +tt} 1n+pp: + gv.in_drop(tt, pp, ws, gv.in_tail(cc, tt, ws, hh)) + +# a word within the words is a piece of them +def gv.in_piece(ww: String, +ws: List<&2, String>, hh: gv.In(ww, ws)) -> {D1.piece(ws, ww) == True{} : Bool}: + match ww: + case SNil{}: + hh + case SCon{+cc, +tt}: + gv.and_l(D1.piece(ws, SCon{cc, tt}), gv.sufs(tt, ws), hh) + +# what is left of a word once some chars are dropped is left of it +def gv.ends_drop(ww: String, kk: Nat) -> {D1.ends(ww, String.drop(ww, kk)) == True{} : Bool}: + match ww kk: + case SNil{} _: + {==} + case SCon{+cc, +tt} 0n: + gv.ends_self(SCon{cc, tt}) + case SCon{+cc, +tt} 1n+(+pp): + gv.ends_tail(cc, tt, String.drop(tt, pp), gv.ends_drop(tt, pp)) + +# what is left of a word of a list is a piece of the list +def gv.mid_piece( + pre: List<&2, String>, + +tok: String, + +rest: List<&2, String>, + +vv: String, + +hv: {D1.ends(tok, vv) == True{} : Bool} +) -> {D1.piece(List.append(&2, String, pre, tok <> rest), vv) == True{} : Bool}: + match pre: + case Nil{}: + ee = Equal.sym(Bool, D1.ends(tok, vv), True{}, hv) + %ee : {Bool.or(_, D1.piece(rest, vv)) == True{} : Bool} + {==} + case Con{+pp, +pt}: + ih = gv.mid_piece(pt, tok, rest, vv, hv) + ee = Equal.sym(Bool, D1.piece(List.append(&2, String, pt, tok <> rest), vv), True{}, ih) + %ee : {Bool.or(D1.ends(pp, vv), _) == True{} : Bool} + gv.or_t(D1.ends(pp, vv)) + +# the tail of a string, empty for the empty one +def gv.tail(ss: String) -> String: + match ss: + case SNil{}: + SNil{} + case SCon{_c, tt}: + tt + +# whether a string is empty, as a type: the empty one is not a char in front +def gv.snil_ty(ss: String) -> Type: + match ss: + case SNil{}: + Unit + case SCon{_c, _t}: + Empty + +# the empty string is never a char in front of one +def gv.snil_scon(+cc: Char, +tt: String, ee: {SNil{} == SCon{cc, tt} : String}) -> Empty: + %ee : gv.snil_ty(_) + Unit{} + +# dropping one more char leaves the tail of what was left +def gv.drop_succ( + ww: String, + kk: Nat, + +cc: Char, + +tt: String, + +ee: {String.drop(ww, kk) == SCon{cc, tt} : String} +) -> {String.drop(ww, 1n+kk) == tt : String}: + match ww kk: + case SNil{} _: + Empty.absurd({SNil{} == tt : String}, gv.snil_scon(cc, tt, ee)) + case SCon{+dd, +uu} 0n: + Equal.trans(String, String.drop(uu, 0n), uu, tt, gv.drop0(uu), + Equal.cong(String, String, ss => gv.tail(ss), SCon{dd, uu}, SCon{cc, tt}, ee)) + case SCon{+dd, +uu} 1n+pp: + gv.drop_succ(uu, pp, cc, tt, ee) + +# what is left of a word once some chars are dropped, named +def gv.ends_at( + +tok: String, + +kk: Nat, + +xx: String, + +ee: {String.drop(tok, kk) == xx : String} +) -> {D1.ends(tok, xx) == True{} : Bool}: + %ee : {D1.ends(tok, _) == True{} : Bool} + gv.ends_drop(tok, kk) + +# every suffix of a word of a list is within the list +def gv.sufs_in( + xx: String, + +tok: String, + +kk: Nat, + +pre: List<&2, String>, + +rest: List<&2, String>, + +ee: {String.drop(tok, kk) == xx : String} +) -> gv.In(xx, List.append(&2, String, pre, tok <> rest)): + match xx: + case SNil{}: + gv.mid_piece(pre, tok, rest, SNil{}, gv.ends_at(tok, kk, SNil{}, ee)) + case SCon{+cc, +tt}: + gv.and_tt(D1.piece(List.append(&2, String, pre, tok <> rest), SCon{cc, tt}), + gv.sufs(tt, List.append(&2, String, pre, tok <> rest)), + gv.mid_piece(pre, tok, rest, SCon{cc, tt}, gv.ends_at(tok, kk, SCon{cc, tt}, ee)), + gv.sufs_in(tt, tok, 1n+kk, pre, rest, gv.drop_succ(tok, kk, cc, tt, ee))) + +# a word of a list is within it +def gv.in_mid( + +pre: List<&2, String>, + +tok: String, + +rest: List<&2, String> +) -> gv.In(tok, List.append(&2, String, pre, tok <> rest)): + gv.sufs_in(tok, tok, 0n, pre, rest, gv.drop0(tok)) + +# a value is `true` or a piece of one of the words +def gv.ok(+ws: List<&2, String>, +vv: String) -> Bool: + Bool.or(String.eq(vv, "true"), D1.piece(ws, vv)) + +# a value within the words is one +def gv.ok_in(+ws: List<&2, String>, +vv: String, hh: gv.In(vv, ws)) -> {gv.ok(ws, vv) == True{} : Bool}: + ee = Equal.sym(Bool, D1.piece(ws, vv), True{}, gv.in_piece(vv, ws, hh)) + %ee : {Bool.or(String.eq(vv, "true"), _) == True{} : Bool} + gv.or_t(String.eq(vv, "true")) + +# every binding's value is one +def gv.all(bs: List<&2, S.Bind>, +ws: List<&2, String>) -> Bool: + match bs: + case Nil{}: + True{} + case Con{hh, tt}: + S.Bind{_n, vv} = hh + Bool.and(gv.ok(ws, vv), gv.all(tt, ws)) + +# every level's bindings' values are +def gv.ups(lvs: List<&2, S.Level>, +ws: List<&2, String>) -> Bool: + match lvs: + case Nil{}: + True{} + case Con{hh, tt}: + S.Level{_a, bs} = hh + Bool.and(gv.all(bs, ws), gv.ups(tt, ws)) + +# every value a walker holds, at every level, is one +def gv.good(st: S.St, +ws: List<&2, String>) -> Bool: + S.St{_m, _a, up, _s, _p, binds, _o, _r, _n} = st + Bool.and(gv.all(binds, ws), gv.ups(up, ws)) + +# a walker whose values all are +def gv.G(st: S.St, +ws: List<&2, String>) -> Type: + {gv.good(st, ws) == True{} : Bool} + +# a value, when there is one, is +def gv.mok(mv: Maybe<&2, String>, +ws: List<&2, String>) -> Bool: + match mv: + case None{}: + True{} + case Some{vv}: + gv.ok(ws, vv) + +# the value of a name and value cut, when there is one, is +def gv.pok(nv: String & Maybe<&2, String>, +ws: List<&2, String>) -> Bool: + (_n, mv) = nv + gv.mok(mv, ws) + +# a walker that keeps its bindings and the levels above +def gv.keep( + +binds: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +ws: List<&2, String>, + +hb: {gv.all(binds, ws) == True{} : Bool}, + +hu: {gv.ups(up, ws) == True{} : Bool} +) -> {Bool.and(gv.all(binds, ws), gv.ups(up, ws)) == True{} : Bool}: + gv.and_tt(gv.all(binds, ws), gv.ups(up, ws), hb, hu) + +# the values a walker holds, as a type +def gv.B(+binds: List<&2, S.Bind>, +ws: List<&2, String>) -> Type: + {gv.all(binds, ws) == True{} : Bool} + +# the values of the levels above, as a type +def gv.U(+up: List<&2, S.Level>, +ws: List<&2, String>) -> Type: + {gv.ups(up, ws) == True{} : Bool} + +# a binding of a value that is one +def gv.put( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hv: {gv.ok(ws, val) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), ws): + gv.and_tt(Bool.and(gv.ok(ws, val), gv.all(binds, ws)), gv.ups(up, ws), + gv.and_tt(gv.ok(ws, val), gv.all(binds, ws), hv, hb), hu) + +# a value bound when allowed +def gv.choice( + ok: Bool, + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hv: {gv.ok(ws, val) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), ws): + match ok: + case True{}: + gv.put(name, val, args, up, subs, path, binds, pos, raw, seen, ws, hv, hb, hu) + case False{}: + gv.keep(binds, up, ws, hb, hu) + +# an option's value +def gv.opt_val( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hv: {gv.ok(ws, val) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), ws): + gv.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, + raw, seen, ws, hv, hb, hu) + +# a flag binds `true` +def gv.flag( + val: Maybe<&2, String>, + +name: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match val: + case None{}: + gv.put(name, "true", args, up, subs, path, binds, pos, raw, seen, ws, {==}, hb, hu) + case Some{_v}: + gv.keep(binds, up, ws, hb, hu) + +# an option: its value now, or it waits +def gv.opt( + val: Maybe<&2, String>, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hm: {gv.mok(val, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), ws): + match val: + case None{}: + gv.keep(binds, up, ws, hb, hu) + case Some{vv}: + gv.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) + +# the once-check keeps the walker's values +def gv.once( + again: Bool, + st: S.St, + +name: String, + +path: List<&2, String>, + +ws: List<&2, String>, + hg: gv.G(st, ws) +) -> gv.G(S.parse.once(again, st, name, path), ws): + match again: + case True{}: + match st: + case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: + hg + case False{}: + hg + +# a found argument, by its kind +def gv.akind( + kk: S.ArgKind, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hm: {gv.mok(val, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match kk: + case S.Flag{}: + gv.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), + name, path, ws, gv.flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen, ws, hb, hu)) + case S.Opt{}: + gv.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), + name, path, ws, gv.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu)) + case S.Many{}: + gv.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) + case S.Pos{}: + gv.keep(binds, up, ws, hb, hu) + case S.Rest{}: + gv.keep(binds, up, ws, hb, hu) + +# a found argument, or an unknown flag +def gv.arg( + found: Maybe<&2, S.Arg>, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hm: {gv.mok(val, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gv.keep(binds, up, ws, hb, hu) + case Some{aa}: + match aa: + case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: + gv.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) + +# an unknown long option: a request for help, or an unknown flag +def gv.lhelp( + hit: Bool, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match hit: + case True{}: + gv.keep(binds, up, ws, hb, hu) + case False{}: + gv.keep(binds, up, ws, hb, hu) + +# a long option the current command spells, or an unknown one +def gv.found( + found: Maybe<&2, S.Arg>, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hm: {gv.mok(val, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gv.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen, ws, hb, hu) + case Some{aa}: + gv.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) + +# a long option with a name +def gv.named( + empty: Bool, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hm: {gv.mok(val, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match empty: + case True{}: + gv.keep(binds, up, ws, hb, hu) + case False{}: + gv.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) + +# a long option after the `=` cut +def gv.cut( + nv: String & Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hp: {gv.pok(nv, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match nv: + case (+name, +val): + gv.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hp, hb, hu) + +# the `=` cut's value, when the `=` is here +def gv.cut_pick( + eq: Bool, + +acc: List<&2, Char>, + +tt: List<&2, Char>, + rest: String & Maybe<&2, String>, + +ws: List<&2, String>, + +hr: {gv.pok(rest, ws) == True{} : Bool}, + +ht: {gv.ok(ws, String.from_list(tt)) == True{} : Bool} +) -> {gv.pok(S.cut_eq.pick(eq, acc, tt, rest), ws) == True{} : Bool}: + match eq: + case True{}: + ht + case False{}: + hr + +# the `=` cut's value is within the words when the body is +def gv.cut_go( + cs: List<&2, Char>, + +acc: List<&2, Char>, + +ws: List<&2, String>, + hh: gv.In(String.from_list(cs), ws) +) -> {gv.pok(S.cut_eq.go(cs, acc), ws) == True{} : Bool}: + match cs: + case Nil{}: + {==} + case Con{+cc, +tt}: + +hi = {gv.in_tail(cc, String.from_list(tt), ws, hh) : {gv.sufs(String.from_list(tt), ws) == True{} : Bool}} + gv.cut_pick(Char.is_eq(cc, '='), acc, tt, S.cut_eq.go(tt, cc <> acc), ws, gv.cut_go(tt, cc <> acc, ws, hi), + gv.ok_in(ws, String.from_list(tt), hi)) + +# a string read as chars and back is itself +def gv.round(ss: String) -> {String.from_list(String.to_list(ss)) == ss : String}: + match ss: + case SNil{}: + {==} + case SCon{+hh, +tt}: + Equal.cong(String, String, xs => SCon{hh, xs}, String.from_list(String.to_list(tt)), tt, gv.round(tt)) + +# a long option body within the words +def gv.long( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + hh: gv.In(body, ws), + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): + ee = Equal.sym(String, String.from_list(String.to_list(body)), body, gv.round(body)) + hr = {%ee : gv.In(_, ws) + hh : gv.In(String.from_list(String.to_list(body)), ws)} + gv.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen, ws, + gv.cut_go(String.to_list(body), [], ws, hr), hb, hu) + +# a flag letter's value: the rest after `=` +def gv.cflag( + eq: Bool, + +rest: String, + +ws: List<&2, String>, + hh: gv.In(rest, ws) +) -> {gv.mok(S.cluster.val.flag(eq, rest), ws) == True{} : Bool}: + match eq: + case True{}: + gv.ok_in(ws, rest, hh) + case False{}: + {==} + +# the rest of the cluster, when there is any +def gv.crest( + rest: String, + +ws: List<&2, String>, + hh: gv.In(rest, ws) +) -> {gv.mok(Bool.pick(Maybe<&2, String>, String.is_empty(rest), None{}, Some{rest}), ws) == True{} : Bool}: + match rest: + case SNil{}: + {==} + case SCon{+cc, +tt}: + gv.ok_in(ws, SCon{cc, tt}, hh) + +# an option letter's value: after one `=`, or the rest +def gv.sval( + eq: Bool, + +rest: String, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool} +) -> {gv.mok(S.short_of.val(eq, rest), ws) == True{} : Bool}: + match eq: + case True{}: + gv.ok_in(ws, String.drop(rest, 1n), gv.in_drop(rest, 1n, ws, hh)) + case False{}: + gv.crest(rest, ws, hh) + +# a letter's value is within the words when the rest of the cluster is +def gv.cval( + found: Maybe<&2, S.Arg>, + +rest: String, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool} +) -> {gv.mok(S.cluster.val(found, rest), ws) == True{} : Bool}: + match found: + case None{}: + {==} + case Some{aa}: + match aa: + case S.Arg{_n, _s, _l, kind, _h, _r, _d, _c}: + match kind: + case S.Flag{}: + gv.cflag(String.starts_with(rest, "="), rest, ws, hh) + case S.Opt{}: + gv.sval(String.starts_with(rest, "="), rest, ws, hh) + case S.Many{}: + gv.sval(String.starts_with(rest, "="), rest, ws, hh) + case S.Pos{}: + {==} + case S.Rest{}: + {==} + +# one letter, by the walker's mode +def gv.capply_mode( + mode: S.Mode, + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match mode: + case S.Dead{_e}: + gv.keep(binds, up, ws, hb, hu) + case S.Free{}: + gv.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, ws, gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hu) + case S.Need{_n}: + gv.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, ws, gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hu) + case S.Help{}: + gv.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, ws, gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hu) + +# one letter against the walker +def gv.capply( + st: S.St, + +cc: Char, + +rest: String, + +orig: String, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool}, + +hg: {gv.good(st, ws) == True{} : Bool} +) -> gv.G(S.cluster.apply(st, cc, rest, orig), ws): + match st: + case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + gv.capply_mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, + gv.and_l(gv.all(binds, ws), gv.ups(up, ws), hg), gv.and_r(gv.all(binds, ws), gv.ups(up, ws), hg)) + +# one letter, unless the cluster has ended +def gv.letter( + done: Bool, + +cc: Char, + +rest: String, + +orig: String, + st: S.St, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool}, + +hg: {gv.good(st, ws) == True{} : Bool} +) -> gv.G(S.cluster.letter(done, cc, rest, orig, st), ws): + match done: + case True{}: + hg + case False{}: + gv.capply(st, cc, rest, orig, ws, hh, hg) + +# every letter of a cluster within the words +def gv.cluster( + body: String, + +orig: String, + +st: S.St, + +done: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + +hg: {gv.good(st, ws) == True{} : Bool} +) -> gv.G(S.cluster(body, orig, st, done), ws): + match body: + case SNil{}: + hg + case SCon{+cc, +rest}: + +hr = {gv.in_tail(cc, rest, ws, hh) : {gv.sufs(rest, ws) == True{} : Bool}} + gv.cluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), ws, hr, + gv.letter(done, cc, rest, orig, st, ws, hr, hg)) + +# a short option body within the words: a request for help, or a cluster +def gv.sgo( + hit: Bool, + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + +hb: {gv.all(binds, ws) == True{} : Bool}, + +hu: {gv.ups(up, ws) == True{} : Bool} +) -> gv.G(S.parse.take_short.go(hit, body, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match hit: + case True{}: + gv.keep(binds, up, ws, hb, hu) + case False{}: + gv.cluster(body, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, raw, seen}, False{}, ws, hh, + gv.keep(binds, up, ws, hb, hu)) + +# a short option body within the words +def gv.short( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): + gv.sgo(S.short_help(body, args, seen), body, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) + +# a positional binds the word +def gv.take_pos( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + pos: List<&2, S.Arg>, + +raw: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), ws): + match pos: + case Nil{}: + gv.keep(binds, up, ws, hb, hu) + case Con{hp, +tt}: + match hp: + case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: + gv.choice(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), + S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}, ws, gv.ok_in(ws, tok, hh), hb, hu) + +# entering a subcommand moves the bindings up a level +def gv.enter( + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +ws: List<&2, String>, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.enter(ss, args, up, path, binds), ws): + match ss: + case S.Sub{_n, _a, _g, _c}: + gv.keep(binds, up, ws, hb, hu) + +# a subcommand entered, or a missing positional +def gv.enter_or( + miss: Maybe<&2, String>, + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), ws): + match miss: + case None{}: + gv.enter(ss, args, up, path, binds, ws, hb, hu) + case Some{_n}: + gv.keep(binds, up, ws, hb, hu) + +# a word: a subcommand, or a positional +def gv.word_go( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gv.take_pos(tok, args, up, subs, path, binds, pos, raw, ws, hh, hb, hu) + case Some{ss}: + gv.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen, ws, hb, hu) + +# a word +def gv.word( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), ws): + gv.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) + +# `-` alone is a word; otherwise a cluster +def gv.short_go( + alone: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), ws): + match alone: + case True{}: + gv.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hu) + case False{}: + gv.short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, + gv.in_drop(tok, 1n, ws, hh), hb, hu) + +# a short option or a word +def gv.step_short( + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), ws): + match dash: + case True{}: + gv.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) + case False{}: + gv.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hu) + +# a long option, a short one, or a word +def gv.kind_go( + long: Bool, + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), ws): + match long: + case True{}: + gv.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, + gv.in_drop(tok, 2n, ws, hh), hb, hu) + case False{}: + gv.step_short(dash, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) + +# a word by its shape +def gv.skind( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), ws): + gv.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen, + ws, + hh, hb, hu) + +# `help` starts a help path, keeping the bindings +def gv.help( + hit: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), ws): + match hit: + case True{}: + gv.keep(binds, up, ws, hb, hu) + case False{}: + gv.skind(tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) + +# `--` switches to positionals, keeping the bindings +def gv.end( + end: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), ws): + match end: + case True{}: + gv.keep(binds, up, ws, hb, hu) + case False{}: + gv.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen, ws, hh, + hb, hu) + +# after `--` a word is a positional +def gv.raw( + raw: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), ws): + match raw: + case True{}: + gv.take_pos(tok, args, up, subs, path, binds, pos, True{}, ws, hh, hb, hu) + case False{}: + gv.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) + +# a pending option binds the word +def gv.need( + flag: Bool, + +name: String, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match flag: + case True{}: + gv.keep(binds, up, ws, hb, hu) + case False{}: + gv.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen, ws, gv.ok_in(ws, tok, hh), hb, hu) + +# a word of a help path keeps the bindings +def gv.help_path( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + _hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.help_path(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gv.keep(binds, up, ws, hb, hu) + case Some{ss}: + match ss: + case S.Sub{_n, _a, _g, _c}: + gv.keep(binds, up, ws, hb, hu) + +# one word, by the walker's mode +def gv.mode( + mode: S.Mode, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + hb: gv.B(binds, ws), + hu: gv.U(up, ws) +) -> gv.G(S.parse.step.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match mode: + case S.Dead{_e}: + gv.keep(binds, up, ws, hb, hu) + case S.Need{name}: + gv.need(S.looks_flag(tok), name, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) + case S.Help{}: + gv.help_path(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) + case S.Free{}: + gv.raw(raw, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) + +# one word within the words keeps every value a walker holds one +def gv.step( + +tok: String, + st: S.St, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hg: {gv.good(st, ws) == True{} : Bool} +) -> gv.G(S.parse.step(tok, st), ws): + match st: + case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + gv.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, + gv.and_l(gv.all(binds, ws), gv.ups(up, ws), hg), gv.and_r(gv.all(binds, ws), gv.ups(up, ws), hg)) + +# a walker's values, from one list of words to an equal one +def gv.tr( + st: S.St, + +aa: List<&2, String>, + +bb: List<&2, String>, + +ee: {aa == bb : List<&2, String>}, + +hg: {gv.good(st, bb) == True{} : Bool} +) -> {gv.good(st, aa) == True{} : Bool}: + e2 = Equal.sym(List<&2, String>, aa, bb, ee) + %e2 : {gv.good(st, _) == True{} : Bool} + hg + +# walking the words after `pre` keeps every value one of the words' pieces +def gv.walk( + tt: List<&2, String>, + +pre: List<&2, String>, + +st: S.St, + +hg: {gv.good(st, List.append(&2, String, pre, tt)) == True{} : Bool} +) -> {gv.good(S.parse.walk(tt, st), List.append(&2, String, pre, tt)) == True{} : Bool}: + match tt: + case Nil{}: + hg + case Con{+tok, +rest}: + +ll = List.append(&2, String, pre, tok <> rest) + +mm = List.append(&2, String, List.append(&2, String, pre, [tok]), rest) + +ee = {M2.sc.app(pre, [tok], rest) : {mm == ll : List<&2, String>}} + g1 = gv.step(tok, st, ll, gv.in_mid(pre, tok, rest), hg) + gw = gv.walk(rest, List.append(&2, String, pre, [tok]), S.parse.step(tok, st), + gv.tr(S.parse.step(tok, st), mm, ll, ee, g1)) + gv.tr(S.parse.walk(rest, S.parse.step(tok, st)), ll, mm, Equal.sym(List<&2, String>, mm, ll, ee), gw) + +# a fresh walker holds no values +def gv.start(spec: Shake.Cli, +ws: List<&2, String>) -> {gv.good(S.parse.start(spec), ws) == True{} : Bool}: + match spec: + case S.Cli{_n, _a, _v, _g, _s}: + {==} + +# the values of a frame's levels +def gv.fups(fr: Grow.Frame, +ws: List<&2, String>) -> Bool: + Grow.Frame{lvs, _p} = fr + gv.ups(lvs, ws) + +# a walker's frame holds its values +def gv.frame( + st: S.St, + +ws: List<&2, String>, + hg: {gv.good(st, ws) == True{} : Bool} +) -> {gv.fups(Grow.gw.frame(st), ws) == True{} : Bool}: + match st: + case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: + hg + +# a level in the middle of a list of levels whose values all are +def gv.ups_mid( + qs: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +ws: List<&2, String>, + +hh: {gv.ups(List.append(&2, S.Level, qs, S.Level{args, bs} <> up), ws) == True{} : Bool} +) -> {gv.all(bs, ws) == True{} : Bool}: + match qs: + case Nil{}: + gv.and_l(gv.all(bs, ws), gv.ups(up, ws), hh) + case Con{qq, +qt}: + match qq: + case S.Level{_a, +qb}: + gv.ups_mid(qt, args, bs, up, ws, + gv.and_r(gv.all(qb, ws), gv.ups(List.append(&2, S.Level, qt, S.Level{args, bs} <> up), ws), hh)) + +# `true` or a piece is given +def gv.or3( + ee: Bool, + pp: Bool, + +dd: Bool, + +hh: {Bool.or(ee, pp) == True{} : Bool} +) -> {Bool.or(ee, Bool.or(pp, dd)) == True{} : Bool}: + match ee: + case True{}: + {==} + case False{}: + match pp: + case True{}: + {==} + case False{}: + Empty.absurd({Bool.or(False{}, Bool.or(False{}, dd)) == True{} : Bool}, Walk.wk.false_true(hh)) + +# a list of values that all are one is given +def gv.all_given( + bs: List<&2, S.Bind>, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +hh: {gv.all(bs, ws) == True{} : Bool} +) -> {Laws.all_given(bs, ws, args) == True{} : Bool}: + match bs: + case Nil{}: + {==} + case Con{hd, +tt}: + match hd: + case S.Bind{+nn, +vv}: + gv.and_tt(D1.given(ws, args, S.Bind{nn, vv}), Laws.all_given(tt, ws, args), + gv.or3(String.eq(vv, "true"), D1.piece(ws, vv), D1.is_default(args, nn, vv), + gv.and_l(gv.ok(ws, vv), gv.all(tt, ws), hh)), + gv.all_given(tt, ws, args, gv.and_r(gv.ok(ws, vv), gv.all(tt, ws), hh))) + +# a default of more arguments is still one +def gv.dmono( + ee: Bool, + pp: Bool, + +xx: Bool, + dd: Bool, + +hh: {Bool.or(ee, Bool.or(pp, dd)) == True{} : Bool} +) -> {Bool.or(ee, Bool.or(pp, Bool.or(xx, dd))) == True{} : Bool}: + match ee: + case True{}: + {==} + case False{}: + match pp: + case True{}: + {==} + case False{}: + e2 = Equal.sym(Bool, dd, True{}, hh) + %e2 : {Bool.or(xx, _) == True{} : Bool} + gv.or_t(xx) + +# bindings given against some arguments are given against more +def gv.given_mono( + xs: List<&2, S.Bind>, + +ws: List<&2, String>, + +aa: S.Arg, + +tt: List<&2, S.Arg>, + +hh: {Laws.all_given(xs, ws, tt) == True{} : Bool} +) -> {Laws.all_given(xs, ws, aa <> tt) == True{} : Bool}: + match xs: + case Nil{}: + {==} + case Con{hd, +xt}: + match hd: + case S.Bind{+nn, +vv}: + match aa: + case S.Arg{+an, +sh, +lo, +kind, +hp, +req, S.Fallback{+ev, +ad}, +cs}: + gv.and_tt(D1.given(ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, ad}, cs} <> tt, S.Bind{nn, vv}), + Laws.all_given(xt, ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, ad}, cs} <> tt), + gv.dmono(String.eq(vv, "true"), D1.piece(ws, vv), + Bool.and(String.eq(an, nn), D1.default_is(ad, vv)), D1.is_default(tt, nn, vv), + gv.and_l(D1.given(ws, tt, S.Bind{nn, vv}), Laws.all_given(xt, ws, tt), hh)), + gv.given_mono(xt, ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, ad}, cs}, tt, + gv.and_r(D1.given(ws, tt, S.Bind{nn, vv}), Laws.all_given(xt, ws, tt), hh))) + +# an argument's own default is given +def gv.dself( + +ee: Bool, + +pp: Bool, + +nn: String, + +vv: String, + dd: Bool +) -> {Bool.or(ee, Bool.or(pp, Bool.or(Bool.and(String.eq(nn, nn), String.eq(vv, vv)), dd))) == True{} : Bool}: + e1 = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) + e2 = Equal.sym(Bool, String.eq(vv, vv), True{}, Eq.string_eq_self(vv)) + e3 = Equal.sym(Bool, Bool.or(pp, True{}), True{}, gv.or_t(pp)) + %e1 : {Bool.or(ee, Bool.or(pp, Bool.or(Bool.and(_, String.eq(vv, vv)), dd))) == True{} : Bool} + %e2 : {Bool.or(ee, Bool.or(pp, Bool.or(Bool.and(True{}, _), dd))) == True{} : Bool} + %e3 : {Bool.or(ee, _) == True{} : Bool} + gv.or_t(ee) + +# a default goes in front when its name is unbound +def gv.fone( + skip: Bool, + +nn: String, + +vv: String, + +rr: List<&2, S.Bind>, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +hr: {Laws.all_given(rr, ws, args) == True{} : Bool}, + +hd: {D1.given(ws, args, S.Bind{nn, vv}) == True{} : Bool} +) -> {Laws.all_given(S.fill.one(skip, nn, vv, rr), ws, args) == True{} : Bool}: + match skip: + case True{}: + hr + case False{}: + gv.and_tt(D1.given(ws, args, S.Bind{nn, vv}), Laws.all_given(rr, ws, args), hd, hr) + +# the defaults filled in are the arguments' own +def gv.fill( + args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +ws: List<&2, String>, + +hh: {gv.all(bs, ws) == True{} : Bool} +) -> {Laws.all_given(S.fill.args(args, bs), ws, args) == True{} : Bool}: + match args: + case Nil{}: + gv.all_given(bs, ws, [], hh) + case Con{aa, +tt}: + match aa: + case S.Arg{+an, +sh, +lo, +kind, +hp, +req, S.Fallback{+ev, ad}, +cs}: + match ad: + case None{}: + gv.given_mono(S.fill.args(tt, bs), ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs}, tt, + gv.fill(tt, bs, ws, hh)) + case Some{+dv}: + gv.fone(S.bound(S.fill.args(tt, bs), an), an, dv, S.fill.args(tt, bs), ws, + S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, Some{dv}}, cs} <> tt, + gv.given_mono(S.fill.args(tt, bs), ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, Some{dv}}, + cs}, tt, + gv.fill(tt, bs, ws, hh)), + gv.dself(String.eq(dv, "true"), D1.piece(ws, dv), an, dv, D1.is_default(tt, an, dv))) + +# reversed bindings are given when they are +def gv.rev_given( + xs: List<&2, S.Bind>, + +acc: List<&2, S.Bind>, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +hx: {Laws.all_given(xs, ws, args) == True{} : Bool}, + +ha: {Laws.all_given(acc, ws, args) == True{} : Bool} +) -> {Laws.all_given(List.reverse.go(&2, S.Bind, xs, acc), ws, args) == True{} : Bool}: + match xs: + case Nil{}: + ha + case Con{+hd, +tt}: + gv.rev_given(tt, hd <> acc, ws, args, + gv.and_r(D1.given(ws, args, hd), Laws.all_given(tt, ws, args), hx), + gv.and_tt(D1.given(ws, args, hd), Laws.all_given(acc, ws, args), + gv.and_l(D1.given(ws, args, hd), Laws.all_given(tt, ws, args), hx), ha)) + +# the bindings of a finished walker's level at `path` all are one +def gv.level( + +spec: Shake.Cli, + +ws: List<&2, String>, + +qs: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +ms: List<&2, String>, + h_fr: M1.fin.Framed(spec, ws, qs, args, bs, up, path, ms) +) -> {gv.all(bs, ws) == True{} : Bool}: + +fs = S.parse.walk(ws, S.parse.start(spec)) + +fr = {Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} + : Grow.Frame} + fg = gv.frame(fs, ws, gv.walk(ws, [], S.parse.start(spec), gv.start(spec, ws))) + gv.ups_mid(qs, args, bs, up, ws, + Equal.trans(Bool, gv.fups(fr, ws), gv.fups(Grow.gw.frame(fs), ws), True{}, + Equal.cong(Grow.Frame, Bool, ff => gv.fups(ff, ws), fr, Grow.gw.frame(fs), + Equal.sym(Grow.Frame, Grow.gw.frame(fs), fr, h_fr)), + fg)) + +def Laws.values_given(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len): + +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} + +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) + ea = Equal.sym(Maybe<&2, S.Matched>, S.at(found, path), Some{S.matched.build(f0 <> rq, ms)}, + M1.fin.at(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len)) + eo = Equal.sym(List<&2, S.Bind>, S.own(S.matched.build(f0 <> rq, ms)), + List.reverse(&2, S.Bind, S.fill.args(args, bs)), Grow.gw.own_build(f0, rq, ms)) + %ea : {Laws.given_at.go(_, ws, args) == Some{True{}} : Maybe<&2, Bool>} + %eo : {Some{Laws.all_given(_, ws, args)} == Some{True{}} : Maybe<&2, Bool>} + Equal.cong(Bool, Maybe<&2, Bool>, bb => Some{bb}, + Laws.all_given(List.reverse(&2, S.Bind, S.fill.args(args, bs)), ws, args), True{}, + gv.rev_given(S.fill.args(args, bs), [], ws, args, + gv.fill(args, bs, ws, gv.level(spec, ws, qs, args, bs, up, path, ms, h_fr)), {==})) + +# ---- laws/BindingsComeFromWords.bend: every value a command binds is named after one of its +# arguments and is a piece of the words read while it was current + +# an argument's name +def gn.name(aa: S.Arg) -> String: + S.Arg{nn, _s, _l, _k, _h, _r, _d, _c} = aa + nn + +# whether a binding is named after one of `args` and its value is `true` or a +# piece of one of the words +def gn.nok(+args: List<&2, S.Arg>, +ws: List<&2, String>, bb: S.Bind) -> Bool: + S.Bind{+nn, +vv} = bb + Bool.and(Laws.has_arg(args, nn), gv.ok(ws, vv)) + +# whether every binding is +def gn.all(bs: List<&2, S.Bind>, +args: List<&2, S.Arg>, +ws: List<&2, String>) -> Bool: + match bs: + case Nil{}: + True{} + case Con{hh, tt}: + Bool.and(gn.nok(args, ws, hh), gn.all(tt, args, ws)) + +# whether every pending positional is one of `args` +def gn.pin(pos: List<&2, S.Arg>, +args: List<&2, S.Arg>) -> Bool: + match pos: + case Nil{}: + True{} + case Con{hh, tt}: + S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c} = hh + Bool.and(Laws.has_arg(args, nn), gn.pin(tt, args)) + +# whether an option waiting for its value is one of `args` +def gn.min(mode: S.Mode, +args: List<&2, S.Arg>) -> Bool: + match mode: + case S.Need{nn}: + Laws.has_arg(args, nn) + case S.Free{}: + True{} + case S.Help{}: + True{} + case S.Dead{_e}: + True{} + +# a walker whose waiting option and pending positionals are its command's own +def gn.wf(st: S.St) -> Bool: + S.St{mode, +args, _g, _s, _p, _b, pos, _r, _n} = st + Bool.and(gn.min(mode, args), gn.pin(pos, args)) + +# that, and every binding of its command is named after one of its arguments +# and is `true` or a piece of the words +def gn.good(st: S.St, +ws: List<&2, String>) -> Bool: + S.St{mode, +args, _g, _s, _p, binds, pos, _r, _n} = st + Bool.and(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws)) + +# a walker that is good, as a type +def gn.G(st: S.St, +ws: List<&2, String>) -> Type: + {gn.good(st, ws) == True{} : Bool} + +# a walker that keeps its command, its bindings and its positionals, free +def gn.keep( + +args: List<&2, S.Arg>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +ws: List<&2, String>, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> {Bool.and(gn.pin(pos, args), gn.all(binds, args, ws)) == True{} : Bool}: + gv.and_tt(gn.pin(pos, args), gn.all(binds, args, ws), hp, hb) + +# `x` or something true is true +def gn.or_rt(+xx: Bool, yy: Bool, +hh: {yy == True{} : Bool}) -> {Bool.or(xx, yy) == True{} : Bool}: + ee = Equal.sym(Bool, yy, True{}, hh) + %ee : {Bool.or(xx, _) == True{} : Bool} + gv.or_t(xx) + +# an argument is one of a list it heads +def gn.has_self(aa: S.Arg, +tt: List<&2, S.Arg>) -> {Laws.has_arg(aa <> tt, gn.name(aa)) == True{} : Bool}: + match aa: + case S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c}: + ee = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) + %ee : {Bool.or(_, Laws.has_arg(tt, nn)) == True{} : Bool} + {==} + +# one of some arguments is one of more +def gn.has_mono( + aa: S.Arg, + +tt: List<&2, S.Arg>, + +nn: String, + +hh: {Laws.has_arg(tt, nn) == True{} : Bool} +) -> {Laws.has_arg(aa <> tt, nn) == True{} : Bool}: + match aa: + case S.Arg{+an, _s, _l, _k, _h, _r, _d, _c}: + gn.or_rt(String.eq(an, nn), Laws.has_arg(tt, nn), hh) + +# pending positionals of some arguments are pending of more +def gn.pin_mono( + xs: List<&2, S.Arg>, + +aa: S.Arg, + +tt: List<&2, S.Arg>, + +hh: {gn.pin(xs, tt) == True{} : Bool} +) -> {gn.pin(xs, aa <> tt) == True{} : Bool}: + match xs: + case Nil{}: + {==} + case Con{xx, +xt}: + match xx: + case S.Arg{+xn, _s, _l, _k, _h, _r, _d, _c}: + gv.and_tt(Laws.has_arg(aa <> tt, xn), gn.pin(xt, aa <> tt), + gn.has_mono(aa, tt, xn, gv.and_l(Laws.has_arg(tt, xn), gn.pin(xt, tt), hh)), + gn.pin_mono(xt, aa, tt, gv.and_r(Laws.has_arg(tt, xn), gn.pin(xt, tt), hh))) + +# a positional kept pending, or not +def gn.pin_put( + keep: Bool, + aa: S.Arg, + +rest: List<&2, S.Arg>, + +tt: List<&2, S.Arg>, + +hr: {gn.pin(rest, aa <> tt) == True{} : Bool} +) -> {gn.pin(S.pos_of.put(keep, aa, rest), aa <> tt) == True{} : Bool}: + match keep: + case True{}: + match aa: + case S.Arg{+nn, +sh, +lo, +kind, +hl, +req, +dd, +cs}: + gv.and_tt(Laws.has_arg(S.Arg{nn, sh, lo, kind, hl, req, dd, cs} <> tt, nn), + gn.pin(rest, S.Arg{nn, sh, lo, kind, hl, req, dd, cs} <> tt), + gn.has_self(S.Arg{nn, sh, lo, kind, hl, req, dd, cs}, tt), hr) + case False{}: + hr + +# the positionals of some arguments are among them +def gn.pin_of(args: List<&2, S.Arg>) -> {gn.pin(S.pos_of(args), args) == True{} : Bool}: + match args: + case Nil{}: + {==} + case Con{+aa, +tt}: + gn.pin_put(S.pos.is(aa), aa, S.pos_of(tt), tt, gn.pin_mono(S.pos_of(tt), aa, tt, gn.pin_of(tt))) + +# whether a found argument is one of `args` +def gn.fok(found: Maybe<&2, S.Arg>, +args: List<&2, S.Arg>) -> Bool: + match found: + case None{}: + True{} + case Some{aa}: + Laws.has_arg(args, gn.name(aa)) + +# a found argument among some is among more +def gn.fok_mono( + mm: Maybe<&2, S.Arg>, + +aa: S.Arg, + +tt: List<&2, S.Arg>, + +hh: {gn.fok(mm, tt) == True{} : Bool} +) -> {gn.fok(mm, aa <> tt) == True{} : Bool}: + match mm: + case None{}: + {==} + case Some{xx}: + gn.has_mono(aa, tt, gn.name(xx), hh) + +# the argument found by a long spelling, when the head matches or not +def gn.fok_long_at( + hit: Bool, + +hh: S.Arg, + +tt: List<&2, S.Arg>, + +rest: Maybe<&2, S.Arg>, + +hr: {gn.fok(rest, tt) == True{} : Bool} +) -> {gn.fok(S.by_long.at(hit, hh, rest), hh <> tt) == True{} : Bool}: + match hit: + case True{}: + gn.has_self(hh, tt) + case False{}: + gn.fok_mono(rest, hh, tt, hr) + +# the argument found by a long spelling is one of them +def gn.fok_long(args: List<&2, S.Arg>, +name: String) -> {gn.fok(S.by_long(args, name), args) == True{} : Bool}: + match args: + case Nil{}: + {==} + case Con{+hh, +tt}: + gn.fok_long_at(S.by_long.hit(hh, name), hh, tt, S.by_long(tt, name), gn.fok_long(tt, name)) + +# the argument found by a short spelling, when the head matches or not +def gn.fok_short_at( + hit: Bool, + +hh: S.Arg, + +tt: List<&2, S.Arg>, + +rest: Maybe<&2, S.Arg>, + +hr: {gn.fok(rest, tt) == True{} : Bool} +) -> {gn.fok(S.by_short.at(hit, hh, rest), hh <> tt) == True{} : Bool}: + match hit: + case True{}: + gn.has_self(hh, tt) + case False{}: + gn.fok_mono(rest, hh, tt, hr) + +# the argument found by a short spelling is one of them +def gn.fok_short(args: List<&2, S.Arg>, +name: String) -> {gn.fok(S.by_short(args, name), args) == True{} : Bool}: + match args: + case Nil{}: + {==} + case Con{+hh, +tt}: + gn.fok_short_at(S.by_short.hit(hh, name), hh, tt, S.by_short(tt, name), gn.fok_short(tt, name)) + +# a binding named after one of the arguments, of a value that is one +def gn.put( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hv: {gv.ok(ws, val) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), ws): + gv.and_tt(gn.pin(pos, args), Bool.and(gn.nok(args, ws, S.Bind{name, val}), gn.all(binds, args, ws)), hp, + gv.and_tt(gn.nok(args, ws, S.Bind{name, val}), gn.all(binds, args, ws), + gv.and_tt(Laws.has_arg(args, name), gv.ok(ws, val), hn, hv), hb)) + +# a value bound when allowed +def gn.choice( + ok: Bool, + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hv: {gv.ok(ws, val) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), ws): + match ok: + case True{}: + gn.put(name, val, args, up, subs, path, binds, pos, raw, seen, ws, hn, hv, hb, hp) + case False{}: + gn.keep(args, binds, pos, ws, hb, hp) + +# an option's value +def gn.opt_val( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hv: {gv.ok(ws, val) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), ws): + gn.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, + raw, seen, ws, hn, hv, hb, hp) + +# a flag binds `true` +def gn.flag( + val: Maybe<&2, String>, + +name: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match val: + case None{}: + gn.put(name, "true", args, up, subs, path, binds, pos, raw, seen, ws, hn, {==}, hb, hp) + case Some{_v}: + gn.keep(args, binds, pos, ws, hb, hp) + +# an option: its value now, or it waits, one of the arguments +def gn.opt( + val: Maybe<&2, String>, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hm: {gv.mok(val, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), ws): + match val: + case None{}: + gv.and_tt(Bool.and(Laws.has_arg(args, name), gn.pin(pos, args)), gn.all(binds, args, ws), + gv.and_tt(Laws.has_arg(args, name), gn.pin(pos, args), hn, hp), hb) + case Some{vv}: + gn.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, ws, hn, hm, hb, hp) + +# the once-check keeps a good walker good +def gn.once( + again: Bool, + st: S.St, + +name: String, + +path: List<&2, String>, + +ws: List<&2, String>, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> gn.G(S.parse.once(again, st, name, path), ws): + match again: + case True{}: + match st: + case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: + gv.and_tt(gn.pin(oo, aa), gn.all(bb, aa, ws), + gv.and_r(gn.min(mode, aa), gn.pin(oo, aa), + gv.and_l(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg)), + gv.and_r(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg)) + case False{}: + hg + +# a found argument, by its kind +def gn.akind( + kk: S.ArgKind, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hm: {gv.mok(val, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match kk: + case S.Flag{}: + gn.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), + name, path, ws, gn.flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen, ws, hn, hb, hp)) + case S.Opt{}: + gn.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), + name, path, ws, gn.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hn, hm, hb, hp)) + case S.Many{}: + gn.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hn, hm, hb, hp) + case S.Pos{}: + gn.keep(args, binds, pos, ws, hb, hp) + case S.Rest{}: + gn.keep(args, binds, pos, ws, hb, hp) + +# a found argument, or an unknown flag +def gn.arg( + found: Maybe<&2, S.Arg>, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hf: {gn.fok(found, args) == True{} : Bool}, + +hm: {gv.mok(val, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gn.keep(args, binds, pos, ws, hb, hp) + case Some{aa}: + match aa: + case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: + gn.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hf, hm, hb, hp) + +# an unknown long option: a request for help, or an unknown flag +def gn.lhelp( + hit: Bool, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match hit: + case True{}: + gn.keep(args, binds, pos, ws, hb, hp) + case False{}: + gn.keep(args, binds, pos, ws, hb, hp) + +# a long option the current command spells, or an unknown one +def gn.found( + found: Maybe<&2, S.Arg>, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hf: {gn.fok(found, args) == True{} : Bool}, + +hm: {gv.mok(val, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gn.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen, ws, hb, hp) + case Some{aa}: + gn.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hf, hm, hb, hp) + +# a long option with a name +def gn.named( + empty: Bool, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hm: {gv.mok(val, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match empty: + case True{}: + gn.keep(args, binds, pos, ws, hb, hp) + case False{}: + gn.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, + gn.fok_long(args, name), hm, hb, hp) + +# a long option after the `=` cut +def gn.cut( + nv: String & Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hq: {gv.pok(nv, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match nv: + case (+name, +val): + gn.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hq, hb, hp) + +# a long option body within the words +def gn.long( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): + ee = Equal.sym(String, String.from_list(String.to_list(body)), body, gv.round(body)) + hr = {%ee : gv.In(_, ws) + hh : gv.In(String.from_list(String.to_list(body)), ws)} + gn.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen, ws, + gv.cut_go(String.to_list(body), [], ws, hr), hb, hp) + +# one letter, by the walker's mode +def gn.capply_mode( + mode: S.Mode, + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match mode: + case S.Dead{_e}: + gn.keep(args, binds, pos, ws, hb, hp) + case S.Free{}: + gn.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, ws, gn.fok_short(args, SCon{cc, SNil{}}), + gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hp) + case S.Need{_n}: + gn.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, ws, gn.fok_short(args, SCon{cc, SNil{}}), + gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hp) + case S.Help{}: + gn.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, ws, gn.fok_short(args, SCon{cc, SNil{}}), + gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hp) + +# one letter against a good walker +def gn.capply( + st: S.St, + +cc: Char, + +rest: String, + +orig: String, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool}, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> gn.G(S.cluster.apply(st, cc, rest, orig), ws): + match st: + case S.St{+mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + gn.capply_mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, + gv.and_r(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg), + gv.and_r(gn.min(mode, args), gn.pin(pos, args), + gv.and_l(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg))) + +# one letter, unless the cluster has ended +def gn.letter( + done: Bool, + +cc: Char, + +rest: String, + +orig: String, + st: S.St, + +ws: List<&2, String>, + +hh: {gv.sufs(rest, ws) == True{} : Bool}, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> gn.G(S.cluster.letter(done, cc, rest, orig, st), ws): + match done: + case True{}: + hg + case False{}: + gn.capply(st, cc, rest, orig, ws, hh, hg) + +# every letter of a cluster within the words +def gn.cluster( + body: String, + +orig: String, + +st: S.St, + +done: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> gn.G(S.cluster(body, orig, st, done), ws): + match body: + case SNil{}: + hg + case SCon{+cc, +rest}: + +hr = {gv.in_tail(cc, rest, ws, hh) : {gv.sufs(rest, ws) == True{} : Bool}} + gn.cluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), ws, hr, + gn.letter(done, cc, rest, orig, st, ws, hr, hg)) + +# a short option body within the words: a request for help, or a cluster +def gn.sgo( + hit: Bool, + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_short.go(hit, body, orig, args, up, subs, path, binds, pos, raw, seen), ws): + match hit: + case True{}: + gn.keep(args, binds, pos, ws, hb, hp) + case False{}: + gn.cluster(body, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, raw, seen}, False{}, ws, hh, + gn.keep(args, binds, pos, ws, hb, hp)) + +# a short option body within the words +def gn.short( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(body, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): + gn.sgo(S.short_help(body, args, seen), body, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hp) + +# a positional consumed, or kept when it is a rest +def gn.kpos( + keep: Bool, + aa: S.Arg, + +tt: List<&2, S.Arg>, + +args: List<&2, S.Arg>, + +hh: {gn.pin(aa <> tt, args) == True{} : Bool} +) -> {gn.pin(S.parse.take_pos.keep(keep, aa, tt), args) == True{} : Bool}: + match keep: + case True{}: + hh + case False{}: + match aa: + case S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c}: + gv.and_r(Laws.has_arg(args, nn), gn.pin(tt, args), hh) + +# a positional binds the word, under its own name +def gn.take_pos( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + pos: List<&2, S.Arg>, + +raw: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), ws): + match pos: + case Nil{}: + gn.keep(args, binds, [], ws, hb, {==}) + case Con{ph, +tt}: + match ph: + case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: + gn.choice(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), + S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}, ws, + gv.and_l(Laws.has_arg(args, name), gn.pin(tt, args), hp), gv.ok_in(ws, tok, hh), hb, + gn.kpos(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), + S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt, args, hp)) + +# entering a subcommand: its positionals are its own, and it has no bindings +def gn.enter( + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +ws: List<&2, String> +) -> gn.G(S.parse.enter(ss, args, up, path, binds), ws): + match ss: + case S.Sub{_n, _a, +next, _c}: + gv.and_tt(gn.pin(S.pos_of(next), next), True{}, gn.pin_of(next), {==}) + +# a subcommand entered, or a missing positional +def gn.enter_or( + miss: Maybe<&2, String>, + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), ws): + match miss: + case None{}: + gn.enter(ss, args, up, path, binds, ws) + case Some{_n}: + gn.keep(args, binds, pos, ws, hb, hp) + +# a word: a subcommand, or a positional +def gn.word_go( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gn.take_pos(tok, args, up, subs, path, binds, pos, raw, ws, hh, hb, hp) + case Some{ss}: + gn.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen, ws, hb, hp) + +# a word +def gn.word( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), ws): + gn.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hp) + +# `-` alone is a word; otherwise a cluster +def gn.short_go( + alone: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), ws): + match alone: + case True{}: + gn.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hp) + case False{}: + gn.short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, + gv.in_drop(tok, 1n, ws, hh), hb, hp) + +# a short option or a word +def gn.step_short( + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), ws): + match dash: + case True{}: + gn.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) + case False{}: + gn.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hp) + +# a long option, a short one, or a word +def gn.kind_go( + long: Bool, + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), ws): + match long: + case True{}: + gn.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, + gv.in_drop(tok, 2n, ws, hh), hb, hp) + case False{}: + gn.step_short(dash, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) + +# a word by its shape +def gn.skind( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), ws): + gn.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen, + ws, + hh, hb, hp) + +# `help` starts a help path +def gn.help( + hit: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), ws): + match hit: + case True{}: + gn.keep(args, binds, pos, ws, hb, hp) + case False{}: + gn.skind(tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) + +# `--` switches to positionals +def gn.end( + end: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), ws): + match end: + case True{}: + gn.keep(args, binds, pos, ws, hb, hp) + case False{}: + gn.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen, ws, hh, + hb, hp) + +# after `--` a word is a positional +def gn.raw( + raw: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), ws): + match raw: + case True{}: + gn.take_pos(tok, args, up, subs, path, binds, pos, True{}, ws, hh, hb, hp) + case False{}: + gn.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) + +# a waiting option binds the word under its own name +def gn.need( + flag: Bool, + +name: String, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hn: {Laws.has_arg(args, name) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match flag: + case True{}: + gn.keep(args, binds, pos, ws, hb, hp) + case False{}: + gn.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen, ws, hn, gv.ok_in(ws, tok, hh), hb, hp) + +# a word of a help path +def gn.help_path( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.help_path(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match found: + case None{}: + gn.keep(args, binds, pos, ws, hb, hp) + case Some{ss}: + match ss: + case S.Sub{_n, _a, _g, _c}: + gn.keep(args, binds, pos, ws, hb, hp) + +# one word, by the walker's mode +def gn.mode( + mode: S.Mode, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hm: {gn.min(mode, args) == True{} : Bool}, + +hb: {gn.all(binds, args, ws) == True{} : Bool}, + +hp: {gn.pin(pos, args) == True{} : Bool} +) -> gn.G(S.parse.step.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen), ws): + match mode: + case S.Dead{_e}: + gn.keep(args, binds, pos, ws, hb, hp) + case S.Need{name}: + gn.need(S.looks_flag(tok), name, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hm, hb, hp) + case S.Help{}: + gn.help_path(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hb, hp) + case S.Free{}: + gn.raw(raw, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) + +# one word within the words keeps a walker good +def gn.step( + +tok: String, + st: S.St, + +ws: List<&2, String>, + +hh: {gv.sufs(tok, ws) == True{} : Bool}, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> gn.G(S.parse.step(tok, st), ws): + match st: + case S.St{+mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + +hw = {gv.and_l(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg) + : {Bool.and(gn.min(mode, args), gn.pin(pos, args)) == True{} : Bool}} + gn.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, + gv.and_l(gn.min(mode, args), gn.pin(pos, args), hw), + gv.and_r(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg), + gv.and_r(gn.min(mode, args), gn.pin(pos, args), hw)) + +# a good walker, from one list of words to an equal one +def gn.tr( + st: S.St, + +aa: List<&2, String>, + +bb: List<&2, String>, + ee: {aa == bb : List<&2, String>}, + hg: {gn.good(st, bb) == True{} : Bool} +) -> {gn.good(st, aa) == True{} : Bool}: + e2 = Equal.sym(List<&2, String>, aa, bb, ee) + %e2 : {gn.good(st, _) == True{} : Bool} + hg + +# walking the words after `pre` keeps a walker good +def gn.walk( + tt: List<&2, String>, + +pre: List<&2, String>, + +st: S.St, + +hg: {gn.good(st, List.append(&2, String, pre, tt)) == True{} : Bool} +) -> {gn.good(S.parse.walk(tt, st), List.append(&2, String, pre, tt)) == True{} : Bool}: + match tt: + case Nil{}: + hg + case Con{+tok, +rest}: + +ll = List.append(&2, String, pre, tok <> rest) + +mm = List.append(&2, String, List.append(&2, String, pre, [tok]), rest) + +ee = {M2.sc.app(pre, [tok], rest) : {mm == ll : List<&2, String>}} + g1 = gn.step(tok, st, ll, gv.in_mid(pre, tok, rest), hg) + gw = gn.walk(rest, List.append(&2, String, pre, [tok]), S.parse.step(tok, st), + gn.tr(S.parse.step(tok, st), mm, ll, ee, g1)) + gn.tr(S.parse.walk(rest, S.parse.step(tok, st)), ll, mm, Equal.sym(List<&2, String>, mm, ll, ee), gw) + +# a fresh walker is good +def gn.start(spec: Shake.Cli, +ws: List<&2, String>) -> {gn.good(S.parse.start(spec), ws) == True{} : Bool}: + match spec: + case S.Cli{_n, _a, _v, +args, _s}: + gv.and_tt(gn.pin(S.pos_of(args), args), True{}, gn.pin_of(args), {==}) + +# the bindings of a view's command, named and within the words +def gn.vall(vv: Grow.View, +ws: List<&2, String>) -> Bool: + Grow.View{aa, _u, _p, bb} = vv + gn.all(bb, aa, ws) + +# a good walker's view holds its bindings +def gn.vall_of( + st: S.St, + +ws: List<&2, String>, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> {gn.vall(Grow.view(st), ws) == True{} : Bool}: + match st: + case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: + gv.and_r(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg) + +# a good walker's waiting option and positionals are its command's own, +# whatever the words +def gn.wf_of( + st: S.St, + +ws: List<&2, String>, + +hg: {gn.good(st, ws) == True{} : Bool} +) -> {gn.wf(st) == True{} : Bool}: + match st: + case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: + gv.and_l(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg) + +# the two halves make a good walker +def gn.join( + st: S.St, + +ws: List<&2, String>, + +hw: {gn.wf(st) == True{} : Bool}, + +ha: {gn.vall(Grow.view(st), ws) == True{} : Bool} +) -> {gn.good(st, ws) == True{} : Bool}: + match st: + case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: + gv.and_tt(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hw, ha) + +# bindings named after the arguments and within the words are named and given +def gn.all_named( + bs: List<&2, S.Bind>, + +args: List<&2, S.Arg>, + +ws: List<&2, String>, + +hh: {gn.all(bs, args, ws) == True{} : Bool} +) -> {Laws.all_named(bs, ws, args) == True{} : Bool}: + match bs: + case Nil{}: + {==} + case Con{hd, +tt}: + match hd: + case S.Bind{+nn, +vv}: + +hn = {gv.and_l(gn.nok(args, ws, S.Bind{nn, vv}), gn.all(tt, args, ws), hh) + : {Bool.and(Laws.has_arg(args, nn), gv.ok(ws, vv)) == True{} : Bool}} + gv.and_tt(Laws.named_given(ws, args, S.Bind{nn, vv}), Laws.all_named(tt, ws, args), + gv.and_tt(Laws.has_arg(args, nn), D1.given(ws, args, S.Bind{nn, vv}), + gv.and_l(Laws.has_arg(args, nn), gv.ok(ws, vv), hn), + gv.or3(String.eq(vv, "true"), D1.piece(ws, vv), D1.is_default(args, nn, vv), + gv.and_r(Laws.has_arg(args, nn), gv.ok(ws, vv), hn))), + gn.all_named(tt, args, ws, gv.and_r(gn.nok(args, ws, S.Bind{nn, vv}), gn.all(tt, args, ws), hh))) + +# whether an argument's default, when it has one, is a default of `args` +# under a name one of them has +def gn.dcase(dd: Maybe<&2, String>, +an: String, +args: List<&2, S.Arg>) -> Bool: + match dd: + case None{}: + True{} + case Some{dv}: + Bool.and(Laws.has_arg(args, an), D1.is_default(args, an, dv)) + +# whether every default of some arguments is a default of `args` +def gn.dsub(tt: List<&2, S.Arg>, +args: List<&2, S.Arg>) -> Bool: + match tt: + case Nil{}: + True{} + case Con{aa, rest}: + S.Arg{+an, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c} = aa + Bool.and(gn.dcase(dd, an, args), gn.dsub(rest, args)) + +# a default of some arguments is a default of more +def gn.dcase_mono( + dd: Maybe<&2, String>, + +an: String, + aa: S.Arg, + +tt: List<&2, S.Arg>, + +hh: {gn.dcase(dd, an, tt) == True{} : Bool} +) -> {gn.dcase(dd, an, aa <> tt) == True{} : Bool}: + match dd: + case None{}: + {==} + case Some{+dv}: + match aa: + case S.Arg{+bn, +bs, +bl, +bk, +bh, +br, S.Fallback{+ev, +bd}, +bc}: + gv.and_tt(Laws.has_arg(S.Arg{bn, bs, bl, bk, bh, br, S.Fallback{ev, bd}, bc} <> tt, an), + D1.is_default(S.Arg{bn, bs, bl, bk, bh, br, S.Fallback{ev, bd}, bc} <> tt, an, dv), + gn.or_rt(String.eq(bn, an), Laws.has_arg(tt, an), + gv.and_l(Laws.has_arg(tt, an), D1.is_default(tt, an, dv), hh)), + gn.or_rt(Bool.and(String.eq(bn, an), D1.default_is(bd, dv)), D1.is_default(tt, an, dv), + gv.and_r(Laws.has_arg(tt, an), D1.is_default(tt, an, dv), hh))) + +# the defaults of some arguments are defaults of more +def gn.dsub_mono( + xs: List<&2, S.Arg>, + +aa: S.Arg, + +tt: List<&2, S.Arg>, + +hh: {gn.dsub(xs, tt) == True{} : Bool} +) -> {gn.dsub(xs, aa <> tt) == True{} : Bool}: + match xs: + case Nil{}: + {==} + case Con{xx, +xt}: + match xx: + case S.Arg{+xn, _s, _l, _k, _h, _r, S.Fallback{_e, +xd}, _c}: + gv.and_tt(gn.dcase(xd, xn, aa <> tt), gn.dsub(xt, aa <> tt), + gn.dcase_mono(xd, xn, aa, tt, gv.and_l(gn.dcase(xd, xn, tt), gn.dsub(xt, tt), hh)), + gn.dsub_mono(xt, aa, tt, gv.and_r(gn.dcase(xd, xn, tt), gn.dsub(xt, tt), hh))) + +# an argument's own default is a default of a list it heads +def gn.dcase_self( + +ev: Maybe<&2, String>, + dd: Maybe<&2, String>, + +an: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hl: String, + +req: Bool, + +cs: List<&2, String>, + +tt: List<&2, S.Arg> +) -> {gn.dcase(dd, an, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs} <> tt) == True{} : Bool}: + match dd: + case None{}: + {==} + case Some{+dv}: + e1 = Equal.sym(Bool, String.eq(an, an), True{}, Eq.string_eq_self(an)) + e2 = Equal.sym(Bool, String.eq(dv, dv), True{}, Eq.string_eq_self(dv)) + %e1 : {Bool.and(Bool.or(_, Laws.has_arg(tt, an)), + Bool.or(Bool.and(_, String.eq(dv, dv)), D1.is_default(tt, an, dv))) + == True{} : Bool} + %e2 : {Bool.and(Bool.or(True{}, Laws.has_arg(tt, an)), Bool.or(Bool.and(True{}, _), D1.is_default(tt, an, dv))) + == True{} : Bool} + {==} + +# every argument's default is a default of the list +def gn.dsub_self(args: List<&2, S.Arg>) -> {gn.dsub(args, args) == True{} : Bool}: + match args: + case Nil{}: + {==} + case Con{aa, +tt}: + match aa: + case S.Arg{+an, +sh, +lo, +kind, +hl, +req, S.Fallback{+ev, +dd}, +cs}: + gv.and_tt(gn.dcase(dd, an, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs} <> tt), + gn.dsub(tt, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs} <> tt), + gn.dcase_self(ev, dd, an, sh, lo, kind, hl, req, cs, tt), + gn.dsub_mono(tt, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs}, tt, gn.dsub_self(tt))) + +# a default goes in front when its name is unbound, named and given +def gn.fone( + skip: Bool, + +nn: String, + +vv: String, + +rr: List<&2, S.Bind>, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +hr: {Laws.all_named(rr, ws, args) == True{} : Bool}, + +hd: {Laws.named_given(ws, args, S.Bind{nn, vv}) == True{} : Bool} +) -> {Laws.all_named(S.fill.one(skip, nn, vv, rr), ws, args) == True{} : Bool}: + match skip: + case True{}: + hr + case False{}: + gv.and_tt(Laws.named_given(ws, args, S.Bind{nn, vv}), Laws.all_named(rr, ws, args), hd, hr) + +# `x`, or `y` or something true, is true +def gn.or_rr( + +xx: Bool, + +yy: Bool, + zz: Bool, + +hh: {zz == True{} : Bool} +) -> {Bool.or(xx, Bool.or(yy, zz)) == True{} : Bool}: + ee = Equal.sym(Bool, zz, True{}, hh) + e2 = Equal.sym(Bool, Bool.or(yy, True{}), True{}, gv.or_t(yy)) + %ee : {Bool.or(xx, Bool.or(yy, _)) == True{} : Bool} + %e2 : {Bool.or(xx, _) == True{} : Bool} + gv.or_t(xx) + +# one argument's default, filled when unbound +def gn.fill_arg( + dd: Maybe<&2, String>, + +an: String, + +rr: List<&2, S.Bind>, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +hr: {Laws.all_named(rr, ws, args) == True{} : Bool}, + +hd: {gn.dcase(dd, an, args) == True{} : Bool} +) -> {Laws.all_named(S.fill.arg.go(dd, an, rr), ws, args) == True{} : Bool}: + match dd: + case None{}: + hr + case Some{+dv}: + gn.fone(S.bound(rr, an), an, dv, rr, ws, args, hr, + gv.and_tt(Laws.has_arg(args, an), D1.given(ws, args, S.Bind{an, dv}), + gv.and_l(Laws.has_arg(args, an), D1.is_default(args, an, dv), hd), + gn.or_rr(String.eq(dv, "true"), D1.piece(ws, dv), D1.is_default(args, an, dv), + gv.and_r(Laws.has_arg(args, an), D1.is_default(args, an, dv), hd)))) + +# the defaults filled in are named and given +def gn.fill( + tt: List<&2, S.Arg>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +ws: List<&2, String>, + +hb: {Laws.all_named(bs, ws, args) == True{} : Bool}, + +hs: {gn.dsub(tt, args) == True{} : Bool} +) -> {Laws.all_named(S.fill.args(tt, bs), ws, args) == True{} : Bool}: + match tt: + case Nil{}: + hb + case Con{aa, +rest}: + match aa: + case S.Arg{+an, _s, _l, _k, _h, _r, S.Fallback{_e, +dd}, _c}: + gn.fill_arg(dd, an, S.fill.args(rest, bs), ws, args, + gn.fill(rest, args, bs, ws, hb, gv.and_r(gn.dcase(dd, an, args), gn.dsub(rest, args), hs)), + gv.and_l(gn.dcase(dd, an, args), gn.dsub(rest, args), hs)) + +# reversed bindings are named and given when they are +def gn.rev_named( + xs: List<&2, S.Bind>, + +acc: List<&2, S.Bind>, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +hx: {Laws.all_named(xs, ws, args) == True{} : Bool}, + +ha: {Laws.all_named(acc, ws, args) == True{} : Bool} +) -> {Laws.all_named(List.reverse.go(&2, S.Bind, xs, acc), ws, args) == True{} : Bool}: + match xs: + case Nil{}: + ha + case Con{+hd, +tt}: + gn.rev_named(tt, hd <> acc, ws, args, + gv.and_r(Laws.named_given(ws, args, hd), Laws.all_named(tt, ws, args), hx), + gv.and_tt(Laws.named_given(ws, args, hd), Laws.all_named(acc, ws, args), + gv.and_l(Laws.named_given(ws, args, hd), Laws.all_named(tt, ws, args), hx), ha)) + +# the words `ws` leave the walker at the command with these arguments, above +# these levels, at this path, with these bindings +def gn.At( + +spec: Shake.Cli, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +bs: List<&2, S.Bind> +) -> Type: + {Grow.view(S.parse.walk(ws, S.parse.start(spec))) == Grow.View{args, up, path, bs} : Grow.View} + +# the bindings a command made while it was current are named and within its +# words +def gn.here( + +spec: Shake.Cli, + +pre: List<&2, String>, + +mid: List<&2, String>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + h_pre: gn.At(spec, pre, args, up, path, []), + h_mid: gn.At(spec, List.append(&2, String, pre, mid), args, up, path, bs) +) -> {gn.all(bs, args, mid) == True{} : Bool}: + +s1 = S.parse.walk(pre, S.parse.start(spec)) + +s2 = S.parse.walk(mid, s1) + +v0 = {Grow.View{args, up, path, []} : Grow.View} + +v2 = {Grow.View{args, up, path, bs} : Grow.View} + hw = gn.wf_of(s1, pre, gn.walk(pre, [], S.parse.start(spec), gn.start(spec, pre))) + ha = Equal.trans(Bool, gn.vall(Grow.view(s1), mid), gn.vall(v0, mid), True{}, + Equal.cong(Grow.View, Bool, vv => gn.vall(vv, mid), Grow.view(s1), v0, h_pre), {==}) + +wm = S.parse.walk(List.append(&2, String, pre, mid), S.parse.start(spec)) + e2 = Equal.trans(Grow.View, Grow.view(s2), Grow.view(wm), v2, + Equal.cong(S.St, Grow.View, st => Grow.view(st), s2, wm, + Equal.sym(S.St, wm, s2, L1.walk_append(pre, mid, S.parse.start(spec)))), + h_mid) + Equal.trans(Bool, gn.vall(v2, mid), gn.vall(Grow.view(s2), mid), True{}, + Equal.cong(Grow.View, Bool, vv => gn.vall(vv, mid), v2, Grow.view(s2), Equal.sym(Grow.View, Grow.view(s2), v2, e2)), + gn.vall_of(s2, mid, gn.walk(mid, [], s1, gn.join(s1, mid, hw, ha)))) + +def Laws.values_here(spec, pre, mid, post, found, qs, args, bs, up, path, ms, h_done, h_pre, h_mid, h_fr, h_len): + +ws = List.append(&2, String, pre, List.append(&2, String, mid, post)) + +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} + +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) + ea = Equal.sym(Maybe<&2, S.Matched>, S.at(found, path), Some{S.matched.build(f0 <> rq, ms)}, + M1.fin.at(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len)) + eo = Equal.sym(List<&2, S.Bind>, S.own(S.matched.build(f0 <> rq, ms)), + List.reverse(&2, S.Bind, S.fill.args(args, bs)), Grow.gw.own_build(f0, rq, ms)) + %ea : {Laws.named_at.go(_, mid, args) == Some{True{}} : Maybe<&2, Bool>} + %eo : {Some{Laws.all_named(_, mid, args)} == Some{True{}} : Maybe<&2, Bool>} + Equal.cong(Bool, Maybe<&2, Bool>, bb => Some{bb}, + Laws.all_named(List.reverse(&2, S.Bind, S.fill.args(args, bs)), mid, args), True{}, + gn.rev_named(S.fill.args(args, bs), [], mid, args, + gn.fill(args, args, bs, mid, + gn.all_named(bs, args, mid, gn.here(spec, pre, mid, args, bs, up, path, h_pre, h_mid)), + gn.dsub_self(args)), + {==})) + +# ---- laws/BindingsComeFromWords.bend: a word binds at most one value + +# a walker's bindings count at most `kk` values +def nd.T(st: S.St, +kk: Nat) -> Type: + {Laws.nat_le(Laws.valued(Laws.binds_of(st)), kk) == True{} : Bool} + +# a count is at most itself +def nd.le_refl(nn: Nat) -> {Laws.nat_le(nn, nn) == True{} : Bool}: + match nn: + case 0n: + {==} + case 1n+pp: + nd.le_refl(pp) + +# a count is at most one more +def nd.le_s(nn: Nat) -> {Laws.nat_le(nn, 1n+nn) == True{} : Bool}: + match nn: + case 0n: + {==} + case 1n+pp: + nd.le_s(pp) + +# at most is transitive +def nd.le_trans( + aa: Nat, + bb: Nat, + cc: Nat, + h1: {Laws.nat_le(aa, bb) == True{} : Bool}, + h2: {Laws.nat_le(bb, cc) == True{} : Bool} +) -> {Laws.nat_le(aa, cc) == True{} : Bool}: + match aa: + case 0n: + {==} + case 1n+pp: + match bb: + case 0n: + Empty.absurd({Laws.nat_le(1n+pp, cc) == True{} : Bool}, Walk.wk.false_true(h1)) + case 1n+qq: + match cc: + case 0n: + Empty.absurd({False{} == True{} : Bool}, Walk.wk.false_true(h2)) + case 1n+rr: + nd.le_trans(pp, qq, rr, h1, h2) + +# a binding in front counts at most one more +def nd.put_le(flag: Bool, +nn: Nat) -> {Laws.nat_le(Laws.valued.at(flag, nn), 1n+nn) == True{} : Bool}: + match flag: + case True{}: + nd.le_s(nn) + case False{}: + nd.le_refl(1n+nn) + +# the once-check keeps the walker's bindings +def nd.once_keep( + again: Bool, + st: S.St, + +name: String, + +path: List<&2, String> +) -> {Laws.binds_of(S.parse.once(again, st, name, path)) == Laws.binds_of(st) : List<&2, S.Bind>}: + match again: + case True{}: + match st: + case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: + {==} + case False{}: + {==} + +# the once-check keeps a count +def nd.once( + again: Bool, + +st: S.St, + +name: String, + +path: List<&2, String>, + +kk: Nat, + hh: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), kk) == True{} : Bool} +) -> nd.T(S.parse.once(again, st, name, path), kk): + ee = Equal.sym(List<&2, S.Bind>, Laws.binds_of(S.parse.once(again, st, name, path)), Laws.binds_of(st), + nd.once_keep(again, st, name, path)) + %ee : {Laws.nat_le(Laws.valued(_), kk) == True{} : Bool} + hh + +# a binding +def nd.put( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + nd.put_le(String.eq(val, "true"), Laws.valued(binds)) + +# a value bound when allowed +def nd.choice( + ok: Bool, + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match ok: + case True{}: + nd.put(name, val, args, up, subs, path, binds, pos, raw, seen) + case False{}: + nd.le_s(Laws.valued(binds)) + +# an option's value +def nd.opt_val( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + nd.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, raw, + seen) + +# a flag +def nd.flag( + val: Maybe<&2, String>, + +name: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match val: + case None{}: + nd.put(name, "true", args, up, subs, path, binds, pos, raw, seen) + case Some{_v}: + nd.le_s(Laws.valued(binds)) + +# an option +def nd.opt( + val: Maybe<&2, String>, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match val: + case None{}: + nd.le_s(Laws.valued(binds)) + case Some{vv}: + nd.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen) + +# a found argument, by its kind +def nd.akind( + kk: S.ArgKind, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, + seen), 1n+Laws.valued(binds)): + match kk: + case S.Flag{}: + nd.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), + name, path, 1n+Laws.valued(binds), nd.flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen)) + case S.Opt{}: + nd.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), + name, path, 1n+Laws.valued(binds), nd.opt(val, name, args, up, subs, path, binds, pos, raw, seen)) + case S.Many{}: + nd.opt(val, name, args, up, subs, path, binds, pos, raw, seen) + case S.Pos{}: + nd.le_s(Laws.valued(binds)) + case S.Rest{}: + nd.le_s(Laws.valued(binds)) + +# a found argument, or an unknown flag +def nd.arg( + found: Maybe<&2, S.Arg>, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match found: + case None{}: + nd.le_s(Laws.valued(binds)) + case Some{aa}: + match aa: + case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: + nd.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen) + +# an unknown long option: a request for help, or an unknown flag +def nd.lhelp( + hit: Bool, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match hit: + case True{}: + nd.le_s(Laws.valued(binds)) + case False{}: + nd.le_s(Laws.valued(binds)) + +# a long option the current command spells, or an unknown one +def nd.found( + found: Maybe<&2, S.Arg>, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), + 1n+Laws.valued(binds)): + match found: + case None{}: + nd.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen) + case Some{aa}: + nd.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen) + +# a long option with a name +def nd.named( + empty: Bool, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, + seen), 1n+Laws.valued(binds)): + match empty: + case True{}: + nd.le_s(Laws.valued(binds)) + case False{}: + nd.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen) + +# a long option after the `=` cut +def nd.cut( + nv: String & Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match nv: + case (+name, +val): + nd.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen) + +# a long option +def nd.long( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + nd.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen) + +# the bound on a cluster so far: the count it started with while no letter +# has ended it, one more once one has +def nd.inv(st: S.St, +n0: Nat, done: Bool) -> Bool: + match done: + case True{}: + Laws.nat_le(Laws.valued(Laws.binds_of(st)), 1n+n0) + case False{}: + Laws.nat_le(Laws.valued(Laws.binds_of(st)), n0) + +# a walker within the count it started with is within the bound, ended or not +def nd.inv_any( + done: Bool, + +st: S.St, + +n0: Nat, + +hh: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), n0) == True{} : Bool} +) -> {nd.inv(st, n0, done) == True{} : Bool}: + match done: + case True{}: + nd.le_trans(Laws.valued(Laws.binds_of(st)), n0, 1n+n0, hh, nd.le_s(n0)) + case False{}: + hh + +# one more than a count within the start, lifted +def nd.lift( + +st: S.St, + +bs: List<&2, S.Bind>, + +n0: Nat, + ht: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), 1n+Laws.valued(bs)) == True{} : Bool}, + hh: {Laws.nat_le(Laws.valued(bs), n0) == True{} : Bool} +) -> {nd.inv(st, n0, True{}) == True{} : Bool}: + nd.le_trans(Laws.valued(Laws.binds_of(st)), 1n+Laws.valued(bs), 1n+n0, ht, hh) + +# a flag letter: `true` goes on, or `=` ends the cluster refused +def nd.lflag( + eq: Bool, + +name: String, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +n0: Nat, + +hh: {Laws.nat_le(Laws.valued(binds), n0) == True{} : Bool} +) -> {nd.inv(S.parse.once(S.bound(binds, name), S.parse.take_flag(S.cluster.val.flag(eq, rest), name, orig, args, up, + subs, path, binds, pos, raw, seen), name, path), n0, eq) == True{} : Bool}: + match eq: + case True{}: + +st = S.parse.take_flag(Some{rest}, name, orig, args, up, subs, path, binds, pos, raw, seen) + ee = Equal.sym(List<&2, S.Bind>, Laws.binds_of(S.parse.once(S.bound(binds, name), st, name, path)), + Laws.binds_of(st), nd.once_keep(S.bound(binds, name), st, name, path)) + %ee : {Laws.nat_le(Laws.valued(_), 1n+n0) == True{} : Bool} + nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) + case False{}: + +st = S.parse.take_flag(None{}, name, orig, args, up, subs, path, binds, pos, raw, seen) + ee = Equal.sym(List<&2, S.Bind>, Laws.binds_of(S.parse.once(S.bound(binds, name), st, name, path)), + Laws.binds_of(st), nd.once_keep(S.bound(binds, name), st, name, path)) + %ee : {Laws.nat_le(Laws.valued(_), n0) == True{} : Bool} + hh + +# a letter's effect, and whether it ends the cluster +def nd.lpair( + found: Maybe<&2, S.Arg>, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +n0: Nat, + +hh: {Laws.nat_le(Laws.valued(binds), n0) == True{} : Bool} +) -> {nd.inv(S.parse.take_arg(found, S.cluster.val(found, rest), orig, args, up, subs, path, binds, pos, raw, seen), n0, + S.cluster.ends(found, rest)) == True{} : Bool}: + match found: + case None{}: + nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) + case Some{aa}: + match aa: + case S.Arg{+name, +sh, +lo, kind, +hl, +req, +dd, +cs}: + match kind: + case S.Flag{}: + nd.lflag(String.starts_with(rest, "="), name, rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, + hh) + case S.Opt{}: + +vv = S.short_of.val(String.starts_with(rest, "="), rest) + nd.lift(S.parse.once(S.bound(binds, name), S.parse.take_opt(vv, name, args, up, subs, path, binds, pos, + raw, + seen), name, path), binds, n0, + nd.once(S.bound(binds, name), S.parse.take_opt(vv, name, args, up, subs, path, binds, pos, raw, seen), + name, path, 1n+Laws.valued(binds), nd.opt(vv, name, args, up, subs, path, binds, pos, raw, seen)), hh) + case S.Many{}: + +vv = S.short_of.val(String.starts_with(rest, "="), rest) + nd.lift(S.parse.take_opt(vv, name, args, up, subs, path, binds, pos, raw, seen), binds, n0, + nd.opt(vv, name, args, up, subs, path, binds, pos, raw, seen), hh) + case S.Pos{}: + nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) + case S.Rest{}: + nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) + +# a walker within the bound is within one more than the start +def nd.inv_out( + done: Bool, + +st: S.St, + +n0: Nat, + +hh: {nd.inv(st, n0, done) == True{} : Bool} +) -> nd.T(st, 1n+n0): + match done: + case True{}: + hh + case False{}: + nd.le_trans(Laws.valued(Laws.binds_of(st)), n0, 1n+n0, hh, nd.le_s(n0)) + +# one letter, by the walker's mode +def nd.cmode( + mode: S.Mode, + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +n0: Nat, + +hh: {Laws.nat_le(Laws.valued(binds), n0) == True{} : Bool} +) -> {nd.inv(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), n0, + S.cluster.ends(S.by_short(args, SCon{cc, SNil{}}), rest)) == True{} : Bool}: + match mode: + case S.Dead{+ee}: + nd.inv_any(S.cluster.ends(S.by_short(args, SCon{cc, SNil{}}), rest), + S.St{S.Dead{ee}, args, up, subs, path, binds, pos, raw, seen}, n0, hh) + case S.Free{}: + nd.lpair(S.by_short(args, SCon{cc, SNil{}}), rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) + case S.Need{_n}: + nd.lpair(S.by_short(args, SCon{cc, SNil{}}), rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) + case S.Help{}: + nd.lpair(S.by_short(args, SCon{cc, SNil{}}), rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) + +# one letter against a walker within the start +def nd.capply( + st: S.St, + +cc: Char, + +rest: String, + +orig: String, + +n0: Nat, + +hh: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), n0) == True{} : Bool} +) -> {nd.inv(S.cluster.apply(st, cc, rest, orig), n0, S.cluster.done(False{}, cc, rest, st)) == True{} : Bool}: + match st: + case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + nd.cmode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) + +# a letter, and whether the cluster goes on +def nd.letter( + done: Bool, + +cc: Char, + +rest: String, + +orig: String, + +st: S.St, + +n0: Nat, + +hh: {nd.inv(st, n0, done) == True{} : Bool} +) -> {nd.inv(S.cluster.letter(done, cc, rest, orig, st), n0, S.cluster.done(done, cc, rest, st)) == True{} : Bool}: + match done: + case True{}: + hh + case False{}: + nd.capply(st, cc, rest, orig, n0, hh) + +# a whole cluster adds at most one value to what it started with +def nd.cluster( + body: String, + +orig: String, + +st: S.St, + +done: Bool, + +n0: Nat, + +hh: {nd.inv(st, n0, done) == True{} : Bool} +) -> nd.T(S.cluster(body, orig, st, done), 1n+n0): + match body: + case SNil{}: + nd.inv_out(done, st, n0, hh) + case SCon{+cc, +rest}: + nd.cluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), n0, + nd.letter(done, cc, rest, orig, st, n0, hh)) + +# a short option: a request for help, or a cluster +def nd.sgo( + hit: Bool, + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_short.go(hit, body, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match hit: + case True{}: + nd.le_s(Laws.valued(binds)) + case False{}: + nd.cluster(body, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, raw, seen}, False{}, Laws.valued(binds), + nd.le_refl(Laws.valued(binds))) + +# a short option +def nd.short( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + nd.sgo(S.short_help(body, args, seen), body, orig, args, up, subs, path, binds, pos, raw, seen) + +# a positional +def nd.take_pos( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + pos: List<&2, S.Arg>, + +raw: Bool +) -> nd.T(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), 1n+Laws.valued(binds)): + match pos: + case Nil{}: + nd.le_s(Laws.valued(binds)) + case Con{ph, +tt}: + match ph: + case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: + nd.choice(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), + S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}) + +# entering a subcommand starts with no bindings +def nd.enter( + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +binds: List<&2, S.Bind> +) -> nd.T(S.parse.enter(ss, args, up, path, binds), 1n+Laws.valued(binds)): + match ss: + case S.Sub{_n, _a, _g, _c}: + {==} + +# a subcommand entered, or a missing positional +def nd.enter_or( + miss: Maybe<&2, String>, + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match miss: + case None{}: + nd.enter(ss, args, up, path, binds) + case Some{_n}: + nd.le_s(Laws.valued(binds)) + +# a word +def nd.word_go( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match found: + case None{}: + nd.take_pos(tok, args, up, subs, path, binds, pos, raw) + case Some{ss}: + nd.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen) + +# a word +def nd.word( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + nd.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen) + +# `-` alone, or a cluster +def nd.short_go( + alone: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + match alone: + case True{}: + nd.word(tok, args, up, subs, path, binds, pos, False{}, seen) + case False{}: + nd.short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, False{}, seen) + +# a short option or a word +def nd.step_short( + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + match dash: + case True{}: + nd.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen) + case False{}: + nd.word(tok, args, up, subs, path, binds, pos, False{}, seen) + +# a long option, a short one, or a word +def nd.kind_go( + long: Bool, + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + match long: + case True{}: + nd.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen) + case False{}: + nd.step_short(dash, tok, args, up, subs, path, binds, pos, seen) + +# a word by its shape +def nd.skind( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + nd.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen) + +# `help` +def nd.help( + hit: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + match hit: + case True{}: + nd.le_s(Laws.valued(binds)) + case False{}: + nd.skind(tok, args, up, subs, path, binds, pos, seen) + +# `--` +def nd.end( + end: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + match end: + case True{}: + nd.le_s(Laws.valued(binds)) + case False{}: + nd.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen) + +# after `--` +def nd.raw( + raw: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> nd.T(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): + match raw: + case True{}: + nd.take_pos(tok, args, up, subs, path, binds, pos, True{}) + case False{}: + nd.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen) + +# a waiting option +def nd.need( + flag: Bool, + +name: String, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match flag: + case True{}: + nd.le_s(Laws.valued(binds)) + case False{}: + nd.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen) + +# a word of a help path +def nd.help_path( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.step.help_path(found, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match found: + case None{}: + nd.le_s(Laws.valued(binds)) + case Some{ss}: + match ss: + case S.Sub{_n, _a, _g, _c}: + nd.le_s(Laws.valued(binds)) + +# one word, by the walker's mode +def nd.mode( + mode: S.Mode, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> nd.T(S.parse.step.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): + match mode: + case S.Dead{_e}: + nd.le_s(Laws.valued(binds)) + case S.Need{name}: + nd.need(S.looks_flag(tok), name, tok, args, up, subs, path, binds, pos, raw, seen) + case S.Help{}: + nd.help_path(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen) + case S.Free{}: + nd.raw(raw, tok, args, up, subs, path, binds, pos, seen) + +def Laws.one_value_per_word(tok, st): + match st: + case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + nd.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen) + +# ---- laws/BindingsComeFromWords.bend: no word is ignored + +# whether a mode has failed, waits for a value, reads a help path +def nu.dead_m(mode: S.Mode) -> Bool: + match mode: + case S.Dead{_e}: + True{} + case S.Free{}: + False{} + case S.Need{_n}: + False{} + case S.Help{}: + False{} + +def nu.need_m(mode: S.Mode) -> Bool: + match mode: + case S.Need{_n}: + True{} + case S.Free{}: + False{} + case S.Dead{_e}: + False{} + case S.Help{}: + False{} + +def nu.help_m(mode: S.Mode) -> Bool: + match mode: + case S.Help{}: + True{} + case S.Free{}: + False{} + case S.Need{_n}: + False{} + case S.Dead{_e}: + False{} + +# a walker's mode facts, its raw switch, its binding and level counts +def nu.dead(st: S.St) -> Bool: + S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st + nu.dead_m(mode) + +def nu.need(st: S.St) -> Bool: + S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st + nu.need_m(mode) + +def nu.help(st: S.St) -> Bool: + S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st + nu.help_m(mode) + +def nu.raw(st: S.St) -> Bool: + S.St{_m, _a, _g, _s, _p, _b, _o, raw, _n} = st + raw + +def nu.blen(st: S.St) -> Nat: + S.St{_m, _a, _g, _s, _p, binds, _o, _r, _n} = st + List.length(&2, S.Bind, binds) + +def nu.ulen(st: S.St) -> Nat: + S.St{_m, _a, up, _s, _p, _b, _o, _r, _n} = st + List.length(&2, S.Level, up) + +# a walker that is not the other one +def nu.N(res: S.St, orig: S.St) -> Type: + {res == orig : S.St} -> Empty + +# one fact that tells two walkers apart +def nu.apart( + +res: S.St, + +orig: S.St, + ff: S.St -> Bool, + hr: {ff(res) == True{} : Bool}, + ho: {ff(orig) == False{} : Bool} +) -> nu.N(res, orig): + ee => Walk.wk.true_false(Equal.trans(Bool, True{}, ff(res), False{}, Equal.sym(Bool, ff(res), True{}, hr), + Equal.trans(Bool, ff(res), ff(orig), False{}, Equal.cong(S.St, Bool, ff, res, orig, ee), ho))) + +# zero is not one more than a count +def nu.z_ty(nn: Nat) -> Type: + match nn: + case 0n: + Unit + case 1n+_p: + Empty + +# the count before this one +def nu.pred(nn: Nat) -> Nat: + match nn: + case 0n: + 0n + case 1n+pp: + pp + +# no count is one more than itself +def nu.succ_ne(nn: Nat, ee: {1n+nn == nn : Nat}) -> Empty: + match nn: + case 0n: + e2 = Equal.sym(Nat, 1n, 0n, ee) + %e2 : nu.z_ty(_) + Unit{} + case 1n+pp: + nu.succ_ne(pp, Equal.cong(Nat, Nat, xx => nu.pred(xx), 1n+(1n+pp), 1n+pp, ee)) + +# more bindings tell two walkers apart +def nu.more( + +res: S.St, + +orig: S.St, + +nn: Nat, + hr: {nu.blen(res) == 1n+nn : Nat}, + ho: {nu.blen(orig) == nn : Nat} +) -> nu.N(res, orig): + ee => nu.succ_ne(nn, Equal.trans(Nat, 1n+nn, nu.blen(res), nn, Equal.sym(Nat, nu.blen(res), 1n+nn, hr), + Equal.trans(Nat, nu.blen(res), nu.blen(orig), nn, Equal.cong(S.St, Nat, ss => nu.blen(ss), res, orig, ee), ho))) + +# more levels tell two walkers apart +def nu.deeper( + +res: S.St, + +orig: S.St, + +nn: Nat, + hr: {nu.ulen(res) == 1n+nn : Nat}, + ho: {nu.ulen(orig) == nn : Nat} +) -> nu.N(res, orig): + ee => nu.succ_ne(nn, Equal.trans(Nat, 1n+nn, nu.ulen(res), nn, Equal.sym(Nat, nu.ulen(res), 1n+nn, hr), + Equal.trans(Nat, nu.ulen(res), nu.ulen(orig), nn, Equal.cong(S.St, Nat, ss => nu.ulen(ss), res, orig, ee), ho))) + +# a binding tells the walker apart +def nu.put( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode +) -> nu.N(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, binds, + pos, raw, seen}): + nu.more(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, binds, + pos, raw, seen}, + List.length(&2, S.Bind, binds), {==}, {==}) + +# a value bound or refused +def nu.choice( + ok: Bool, + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}): + match ok: + case True{}: + nu.put(name, val, args, up, subs, path, binds, pos, raw, seen, m0) + case False{}: + nu.apart(S.parse.take_choice(False{}, name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, + up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + +# an option's value +def nu.opt_val( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, + binds, pos, raw, seen}): + nu.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, raw, + seen, + m0, hd) + +# a flag +def nu.flag( + val: Maybe<&2, String>, + +name: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}): + match val: + case None{}: + nu.put(name, "true", args, up, subs, path, binds, pos, raw, seen, m0) + case Some{+vv}: + nu.apart(S.parse.take_flag(Some{vv}, name, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, + up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + +# an option +def nu.opt( + val: Maybe<&2, String>, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, + binds, pos, raw, seen}): + match val: + case None{}: + nu.apart(S.parse.take_opt(None{}, name, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}, + ss => nu.need(ss), {==}, hn) + case Some{vv}: + nu.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, m0, hd) + +# a repeat fails +def nu.once_dead( + st: S.St, + +name: String, + +path0: List<&2, String> +) -> {nu.dead(S.parse.once(True{}, st, name, path0)) == True{} : Bool}: + match st: + case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: + {==} + +# the once-check: a repeat fails, else the walker it was given +def nu.once( + again: Bool, + +st: S.St, + +name: String, + +path0: List<&2, String>, + +orig: S.St, + +hdo: {nu.dead(orig) == False{} : Bool}, + hh: nu.N(st, orig) +) -> nu.N(S.parse.once(again, st, name, path0), orig): + match again: + case True{}: + nu.apart(S.parse.once(True{}, st, name, path0), orig, ss => nu.dead(ss), nu.once_dead(st, name, path0), hdo) + case False{}: + hh + +# a found argument, by its kind +def nu.akind( + kk: S.ArgKind, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, + subs, path, binds, pos, raw, seen}): + match kk: + case S.Flag{}: + nu.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), + name, path, S.St{m0, args, up, subs, path, binds, pos, raw, seen}, hd, nu.flag(val, name, orig, args, up, + subs, path, binds, pos, raw, seen, m0, hd)) + case S.Opt{}: + nu.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), + name, path, S.St{m0, args, up, subs, path, binds, pos, raw, seen}, hd, nu.opt(val, name, args, up, subs, path, + binds, pos, raw, seen, m0, hd, hn)) + case S.Many{}: + nu.opt(val, name, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn) + case S.Pos{}: + nu.apart(S.parse.take_arg.kind(S.Pos{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, + args, up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + case S.Rest{}: + nu.apart(S.parse.take_arg.kind(S.Rest{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, + args, up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + +# a found argument, or an unknown flag +def nu.arg( + found: Maybe<&2, S.Arg>, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}): + match found: + case None{}: + nu.apart(S.parse.take_arg(None{}, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, + subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + case Some{aa}: + match aa: + case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: + nu.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn) + +# an unknown long option: a request for help, or an unknown flag +def nu.lhelp( + hit: Bool, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}): + match hit: + case True{}: + nu.apart(S.parse.take_long.help(True{}, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, + subs, path, binds, pos, raw, seen}, ss => nu.help(ss), {==}, hl) + case False{}: + nu.apart(S.parse.take_long.help(False{}, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, + subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + +# a long option the current command spells, or an unknown one +def nu.found( + found: Maybe<&2, S.Arg>, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, + args, up, subs, path, binds, pos, raw, seen}): + match found: + case None{}: + nu.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hl) + case Some{aa}: + nu.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn) + +# a long option with a name +def nu.named( + empty: Bool, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, + up, subs, path, binds, pos, raw, seen}): + match empty: + case True{}: + nu.apart(S.parse.take_long.named(True{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, + args, up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + case False{}: + nu.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn, hl) + +# a long option after the `=` cut +def nu.cut( + nv: String & Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, + binds, pos, raw, seen}): + match nv: + case (+name, +val): + nu.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn, + hl) + +# a long option +def nu.long( + +body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, + binds, pos, raw, seen}): + nu.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn, hl) + +# a cluster walker changed from one with `l0` bindings, free: no fewer +# bindings, and failed, waiting, reading a help path, or more bindings +def nu.J(+st: S.St, +l0: Nat) -> Bool: + Bool.and(Laws.nat_le(l0, nu.blen(st)), + Bool.or(Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))), Laws.nat_le(1n+l0, nu.blen(st)))) + +# a walker that is not free and has no fewer bindings has changed +def nu.j_stop( + +st: S.St, + +l0: Nat, + hb: {Laws.nat_le(l0, nu.blen(st)) == True{} : Bool}, + hs: {Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))) == True{} : Bool} +) -> {nu.J(st, l0) == True{} : Bool}: + ee = Equal.sym(Bool, Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))), True{}, hs) + %ee : {Bool.and(Laws.nat_le(l0, nu.blen(st)), Bool.or(_, Laws.nat_le(1n+l0, nu.blen(st)))) == True{} : Bool} + gv.and_tt(Laws.nat_le(l0, nu.blen(st)), True{}, hb, {==}) + +# a binding changes a cluster walker +def nu.jput( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: + gv.and_tt(Laws.nat_le(l0, 1n+List.length(&2, S.Bind, binds)), Laws.nat_le(l0, List.length(&2, S.Bind, binds)), + nd.le_trans(l0, List.length(&2, S.Bind, binds), 1n+List.length(&2, S.Bind, binds), hl0, + nd.le_s(List.length(&2, S.Bind, binds))), hl0) + +# bound or refused +def nu.jchoice( + ok: Bool, + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: + match ok: + case True{}: + nu.jput(name, val, args, up, subs, path, binds, pos, raw, seen, l0, hl0) + case False{}: + nu.j_stop(S.parse.take_choice(False{}, name, val, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) + +# an option's value +def nu.jopt_val( + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: + nu.jchoice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, raw, + seen, l0, hl0) + +# a flag +def nu.jflag( + val: Maybe<&2, String>, + +name: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: + match val: + case None{}: + nu.jput(name, "true", args, up, subs, path, binds, pos, raw, seen, l0, hl0) + case Some{+vv}: + nu.j_stop(S.parse.take_flag(Some{vv}, name, orig, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) + +# an option +def nu.jopt( + val: Maybe<&2, String>, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: + match val: + case None{}: + nu.j_stop(S.parse.take_opt(None{}, name, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) + case Some{vv}: + nu.jopt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, l0, hl0) + +# the once-check keeps a changed cluster walker changed +def nu.jonce( + again: Bool, + +st: S.St, + +name: String, + +path0: List<&2, String>, + +l0: Nat, + +hh: {nu.J(st, l0) == True{} : Bool} +) -> {nu.J(S.parse.once(again, st, name, path0), l0) == True{} : Bool}: + match again: + case True{}: + match st: + case S.St{+mm, +aa, +gg, +ss, +pp, +bb, +oo, +rr, +nn}: + nu.j_stop(S.parse.once(True{}, S.St{mm, aa, gg, ss, pp, bb, oo, rr, nn}, name, path0), l0, + gv.and_l(Laws.nat_le(l0, List.length(&2, S.Bind, bb)), + Bool.or(Bool.or(nu.dead_m(mm), Bool.or(nu.need_m(mm), nu.help_m(mm))), + Laws.nat_le(1n+l0, List.length(&2, S.Bind, bb))), hh), {==}) + case False{}: + hh + +# a found argument, by its kind +def nu.jakind( + kk: S.ArgKind, + +name: String, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), + l0) == True{} : Bool}: + match kk: + case S.Flag{}: + nu.jonce(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), + name, path, l0, nu.jflag(val, name, orig, args, up, subs, path, binds, pos, raw, seen, l0, hl0)) + case S.Opt{}: + nu.jonce(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), + name, path, l0, nu.jopt(val, name, args, up, subs, path, binds, pos, raw, seen, l0, hl0)) + case S.Many{}: + nu.jopt(val, name, args, up, subs, path, binds, pos, raw, seen, l0, hl0) + case S.Pos{}: + nu.j_stop(S.parse.take_arg.kind(S.Pos{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), l0, hl0, + {==}) + case S.Rest{}: + nu.j_stop(S.parse.take_arg.kind(S.Rest{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), l0, + hl0, {==}) + +# a found argument, or an unknown flag +def nu.jarg( + found: Maybe<&2, S.Arg>, + +val: Maybe<&2, String>, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: + match found: + case None{}: + nu.j_stop(S.parse.take_arg(None{}, val, orig, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) + case Some{aa}: + match aa: + case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: + nu.jakind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, l0, hl0) + +# one letter, by the walker's mode +def nu.jmode( + mode: S.Mode, + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +l0: Nat, + +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} +) -> {nu.J(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), + l0) == True{} : Bool}: + match mode: + case S.Dead{+ee}: + nu.j_stop(S.St{S.Dead{ee}, args, up, subs, path, binds, pos, raw, seen}, l0, hl0, {==}) + case S.Free{}: + nu.jarg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, l0, hl0) + case S.Need{_n}: + nu.jarg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, l0, hl0) + case S.Help{}: + nu.jarg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, + up, subs, path, binds, pos, raw, seen, l0, hl0) + +# one letter against a walker with no fewer bindings than the start +def nu.japply( + st: S.St, + +cc: Char, + +rest: String, + +orig: String, + +l0: Nat, + +hb: {Laws.nat_le(l0, nu.blen(st)) == True{} : Bool} +) -> {nu.J(S.cluster.apply(st, cc, rest, orig), l0) == True{} : Bool}: + match st: + case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + nu.jmode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, l0, hb) + +# a changed walker has no fewer bindings +def nu.j_le( + +st: S.St, + +l0: Nat, + +hh: {nu.J(st, l0) == True{} : Bool} +) -> {Laws.nat_le(l0, nu.blen(st)) == True{} : Bool}: + gv.and_l(Laws.nat_le(l0, nu.blen(st)), + Bool.or(Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))), Laws.nat_le(1n+l0, nu.blen(st))), hh) + +# a letter keeps a changed walker changed +def nu.jletter( + done: Bool, + +cc: Char, + +rest: String, + +orig: String, + +st: S.St, + +l0: Nat, + +hh: {nu.J(st, l0) == True{} : Bool} +) -> {nu.J(S.cluster.letter(done, cc, rest, orig, st), l0) == True{} : Bool}: + match done: + case True{}: + hh + case False{}: + nu.japply(st, cc, rest, orig, l0, nu.j_le(st, l0, hh)) + +# the rest of a cluster keeps a changed walker changed +def nu.jcluster( + body: String, + +orig: String, + +st: S.St, + +done: Bool, + +l0: Nat, + +hh: {nu.J(st, l0) == True{} : Bool} +) -> {nu.J(S.cluster(body, orig, st, done), l0) == True{} : Bool}: + match body: + case SNil{}: + hh + case SCon{+cc, +rest}: + nu.jcluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), l0, + nu.jletter(done, cc, rest, orig, st, l0, hh)) + +# a count is never above itself by one +def nu.le_sn(nn: Nat) -> {Laws.nat_le(1n+nn, nn) == False{} : Bool}: + match nn: + case 0n: + {==} + case 1n+pp: + nu.le_sn(pp) + +# a cluster of at least one letter changes the walker +def nu.cluster1( + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, False{}, seen}, + False{}), S.St{m0, args, up, subs, path, binds, pos, False{}, seen}): + +l0 = List.length(&2, S.Bind, binds) + +s0 = {S.St{S.Free{}, args, up, subs, path, binds, pos, False{}, seen} : S.St} + +fin = S.cluster(SCon{cc, rest}, orig, s0, False{}) + hj = nu.jcluster(rest, orig, S.cluster.letter(False{}, cc, rest, orig, s0), S.cluster.done(False{}, cc, rest, s0), l0, + nu.japply(s0, cc, rest, orig, l0, nd.le_refl(l0))) + e1 = Equal.sym(Bool, nu.dead_m(m0), False{}, hd) + e2 = Equal.sym(Bool, nu.need_m(m0), False{}, hn) + e3 = Equal.sym(Bool, nu.help_m(m0), False{}, hl) + e4 = Equal.sym(Bool, Laws.nat_le(l0, l0), True{}, nd.le_refl(l0)) + e5 = Equal.sym(Bool, Laws.nat_le(1n+l0, l0), False{}, nu.le_sn(l0)) + jo = {%e1 : {Bool.and(Laws.nat_le(l0, l0), Bool.or(Bool.or(_, Bool.or(nu.need_m(m0), nu.help_m(m0))), + Laws.nat_le(1n+l0, l0))) == False{} : Bool} + %e2 : {Bool.and(Laws.nat_le(l0, l0), Bool.or(Bool.or(False{}, Bool.or(_, nu.help_m(m0))), + Laws.nat_le(1n+l0, l0))) == False{} : Bool} + %e3 : {Bool.and(Laws.nat_le(l0, l0), Bool.or(Bool.or(False{}, Bool.or(False{}, _)), + Laws.nat_le(1n+l0, l0))) == False{} : Bool} + %e4 : {Bool.and(_, Laws.nat_le(1n+l0, l0)) == False{} : Bool} + %e5 : {_ == False{} : Bool} + {==} : {nu.J(S.St{m0, args, up, subs, path, binds, pos, False{}, seen}, l0) == False{} : Bool}} + nu.apart(fin, S.St{m0, args, up, subs, path, binds, pos, False{}, seen}, ss => nu.J(ss, l0), hj, jo) + +# a nonempty short option body: a request for help, or a cluster +def nu.sgo( + hit: Bool, + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_short.go(hit, SCon{cc, rest}, orig, args, up, subs, path, binds, pos, False{}, seen), + S.St{m0, args, up, subs, path, binds, pos, False{}, seen}): + match hit: + case True{}: + nu.apart(S.parse.take_short.go(True{}, SCon{cc, rest}, orig, args, up, subs, path, binds, pos, False{}, seen), + S.St{m0, args, up, subs, path, binds, pos, False{}, seen}, ss => nu.help(ss), {==}, hl) + case False{}: + nu.cluster1(cc, rest, orig, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) + +# a nonempty cluster body is a char in front of the rest +def nu.cl0( + body: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool}, + +he: {String.is_empty(body) == False{} : Bool} +) -> nu.N(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, False{}, seen), + S.St{m0, args, up, subs, path, binds, pos, False{}, seen}): + match body: + case SNil{}: + Empty.absurd(nu.N(S.parse.take_short(SNil{}, orig, args, up, subs, path, binds, pos, False{}, seen), + S.St{m0, args, up, subs, path, binds, pos, False{}, seen}), Walk.wk.true_false(he)) + case SCon{+cc, +rest}: + nu.sgo(S.short_help(SCon{cc, rest}, args, seen), cc, rest, orig, args, up, subs, path, binds, pos, seen, m0, hd, + hn, hl) + +# a value bound or refused, against any walker with as many bindings that has +# not failed +def nu.choice_any( + ok: Bool, + +name: String, + +val: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +orig: S.St, + +hb: {nu.blen(orig) == List.length(&2, S.Bind, binds) : Nat}, + +hd: {nu.dead(orig) == False{} : Bool} +) -> nu.N(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), orig): + match ok: + case True{}: + nu.more(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), orig, List.length(&2, S.Bind, binds), + {==}, hb) + case False{}: + nu.apart(S.parse.take_choice(False{}, name, val, args, up, subs, path, binds, pos, raw, seen), orig, + ss => nu.dead(ss), {==}, hd) + +# a positional binds the word, or none is left +def nu.take_pos( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), S.St{m0, args, up, subs, path, binds, pos, + raw, seen}): + match pos: + case Nil{}: + nu.apart(S.parse.take_pos(tok, args, up, subs, path, binds, [], raw), S.St{m0, args, up, subs, path, binds, [], + raw, seen}, + ss => nu.dead(ss), {==}, hd) + case Con{ph, +tt}: + match ph: + case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: + nu.choice_any(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), + S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}, + S.St{m0, args, up, subs, path, binds, S.Arg{name, sh, lo, kind, hl, req, dd, cs} <> tt, raw, + seen}, {==}, hd) + +# entering a subcommand adds a level +def nu.enter( + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode +) -> nu.N(S.parse.enter(ss, args, up, path, binds), S.St{m0, args, up, subs, path, binds, pos, raw, seen}): + match ss: + case S.Sub{+nn, +na, +nx, +nc}: + nu.deeper(S.parse.enter(S.Sub{nn, na, nx, nc}, args, up, path, binds), S.St{m0, args, up, subs, path, binds, + pos, raw, seen}, + List.length(&2, S.Level, up), {==}, {==}) + +# a subcommand entered, or a missing positional +def nu.enter_or( + miss: Maybe<&2, String>, + ss: S.Sub, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, + binds, pos, raw, seen}): + match miss: + case None{}: + nu.enter(ss, args, up, subs, path, binds, pos, raw, seen, m0) + case Some{+nn}: + nu.apart(S.parse.enter_or(Some{nn}, ss, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + +# a word +def nu.word_go( + found: Maybe<&2, S.Sub>, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, + binds, pos, raw, seen}): + match found: + case None{}: + nu.take_pos(tok, args, up, subs, path, binds, pos, raw, seen, m0, hd) + case Some{ss}: + nu.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen, m0, hd) + +# a word +def nu.word( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, binds, pos, + raw, seen}): + nu.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, m0, hd) + +# `-` alone, or a cluster +def nu.short_go( + alone: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool}, + +ha: {String.is_empty(String.drop(tok, 1n)) == alone : Bool} +) -> nu.N(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, + binds, pos, False{}, seen}): + match alone: + case True{}: + nu.word(tok, args, up, subs, path, binds, pos, False{}, seen, m0, hd) + case False{}: + nu.cl0(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl, ha) + +# a short option or a word +def nu.step_short( + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, + pos, False{}, seen}): + match dash: + case True{}: + nu.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl, + {==}) + case False{}: + nu.word(tok, args, up, subs, path, binds, pos, False{}, seen, m0, hd) + +# a long option, a short one, or a word +def nu.kind_go( + long: Bool, + dash: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, + path, binds, pos, False{}, seen}): + match long: + case True{}: + nu.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen, m0, hd, hn, hl) + case False{}: + nu.step_short(dash, tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) + +# a word by its shape +def nu.skind( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, pos, + False{}, seen}): + nu.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen, + m0, + hd, hn, hl) + +# `help` starts a help path +def nu.help_w( + hit: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, + pos, False{}, seen}): + match hit: + case True{}: + nu.apart(S.parse.step.help(True{}, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, + binds, pos, False{}, seen}, ss => nu.help(ss), {==}, hl) + case False{}: + nu.skind(tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) + +# `--` switches to positionals +def nu.end( + end: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, + pos, False{}, seen}): + match end: + case True{}: + nu.apart(S.parse.step.end(True{}, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, + binds, pos, False{}, seen}, ss => nu.raw(ss), {==}, {==}) + case False{}: + nu.help_w(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen, m0, hd, + hn, + hl) + +# after `--` a word is a positional +def nu.rawf( + raw: Bool, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool}, + +hn: {nu.need_m(m0) == False{} : Bool}, + +hl: {nu.help_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, + pos, raw, seen}): + match raw: + case True{}: + nu.take_pos(tok, args, up, subs, path, binds, pos, True{}, seen, m0, hd) + case False{}: + nu.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) + +# a waiting option binds the word, or refuses it +def nu.need_w( + flag: Bool, + +name: String, + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +binds: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +m0: S.Mode, + +hd: {nu.dead_m(m0) == False{} : Bool} +) -> nu.N(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, + path, binds, pos, raw, seen}): + match flag: + case True{}: + nu.apart(S.parse.step.need(True{}, name, tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, + subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) + case False{}: + nu.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen, m0, hd) + +def Laws.word_used(tok, st, h_live, h_same): + match st: + case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: + match mode: + case S.Dead{+ee}: + Walk.wk.false_true(h_live) + case S.Help{}: + Walk.wk.false_true(h_live) + case S.Need{+nn}: + nu.need_w(S.looks_flag(tok), nn, tok, args, up, subs, path, binds, pos, raw, seen, S.Need{nn}, {==}, h_same) + case S.Free{}: + nu.rawf(raw, tok, args, up, subs, path, binds, pos, seen, S.Free{}, {==}, {==}, {==}, h_same) diff --git a/proofs/BuiltinsListed.bend b/proofs/BuiltinsListed.bend new file mode 100644 index 0000000..b552435 --- /dev/null +++ b/proofs/BuiltinsListed.bend @@ -0,0 +1,57 @@ +# proofs of laws/BuiltinsListed.bend +import Base +import ../src/cli.bend as S +import ../laws/BuiltinsListed.bend as Laws + +# the help row is the law's +def bi.help( + long: Maybe<&2, S.Arg>, + short: Maybe<&2, S.Arg> +) -> {S.help.help_row(long, short) == Laws.help_rows.of(long, short) : List<&2, S.Arg>}: + match long: + case None{}: + match short: + case None{}: + {==} + case Some{_a}: + {==} + case Some{_a}: + {==} + +# the version row of a page with a version is the law's +def bi.vrow( + +vv: String, + long: Maybe<&2, S.Arg>, + short: Maybe<&2, S.Arg> +) -> {S.help.version_row(Some{vv}, long, short) == Laws.version_rows.of(long, short) : List<&2, S.Arg>}: + match long: + case None{}: + match short: + case None{}: + {==} + case Some{_a}: + {==} + case Some{_a}: + {==} + +# the version row is the law's +def bi.version( + +args: List<&2, S.Arg>, + version: Maybe<&2, String> +) -> {S.help.version_row(version, S.by_long(args, "version"), S.by_short(args, "V")) == Laws.version_rows(args, version) + : List<&2, S.Arg>}: + match version: + case None{}: + {==} + case Some{+vv}: + bi.vrow(vv, S.by_long(args, "version"), S.by_short(args, "V")) + +def Laws.builtins_listed(args, version): + +hr = S.help.help_row(S.by_long(args, "help"), S.by_short(args, "h")) + +vr = S.help.version_row(version, S.by_long(args, "version"), S.by_short(args, "V")) + Equal.trans(List<&2, S.Arg>, List.append(&2, S.Arg, hr, vr), List.append(&2, S.Arg, Laws.help_rows(args), vr), + List.append(&2, S.Arg, Laws.help_rows(args), Laws.version_rows(args, version)), + Equal.cong(List<&2, S.Arg>, List<&2, S.Arg>, xs => List.append(&2, S.Arg, xs, vr), hr, Laws.help_rows(args), + bi.help(S.by_long(args, "help"), S.by_short(args, "h"))), + Equal.cong(List<&2, S.Arg>, List<&2, S.Arg>, xs => List.append(&2, S.Arg, Laws.help_rows(args), xs), vr, + Laws.version_rows(args, version), bi.version(args, version))) diff --git a/proofs/CheckReadsReportsBadReads.bend b/proofs/CheckReadsReportsBadReads.bend new file mode 100644 index 0000000..c0ef9a4 --- /dev/null +++ b/proofs/CheckReadsReportsBadReads.bend @@ -0,0 +1,118 @@ +# proofs of laws/CheckReadsReportsBadReads.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/check.bend as K +import ../laws/CheckReadsReportsBadReads.bend as Laws + +def Laws.reads_nil(_spec): + {==} + +def Laws.reads_con(_spec, _rr, _rs): + {==} + +# the code's walk down a path is the law's +def rd.down( + here: Maybe<&2, S.Sub>, + +name: String +) -> {K.reads.down(here, name) == Laws.sub_named(here, name) : Maybe<&2, S.Sub>}: + match here: + case None{}: + {==} + case Some{ss}: + match ss: + case S.Sub{_n, _a, _g, _s}: + {==} + +# the command a path names, as the code finds its arguments +def rd.cmd( + path: List<&2, String>, + +here: Maybe<&2, S.Sub> +) -> {K.reads.cmd(path, here) == K.reads.args(Laws.named_cmd(path, here)) : Maybe<&2, List<&2, S.Arg>>}: + match path: + case Nil{}: + {==} + case Con{+hh, +tt}: + Equal.trans(Maybe<&2, List<&2, S.Arg>>, K.reads.cmd(tt, K.reads.down(here, hh)), + K.reads.cmd(tt, Laws.sub_named(here, hh)), K.reads.args(Laws.named_cmd(tt, Laws.sub_named(here, hh))), + Equal.cong(Maybe<&2, S.Sub>, Maybe<&2, List<&2, S.Arg>>, xx => K.reads.cmd(tt, xx), K.reads.down(here, hh), + Laws.sub_named(here, hh), rd.down(here, hh)), + rd.cmd(tt, Laws.sub_named(here, hh))) + +# a read, its command found as the law finds it +def rd.base( + spec: Shake.Cli, + +path: List<&2, String>, + +name: String, + +want: K.Want, + +found: Maybe<&2, S.Sub>, + h_cmd: {Laws.named_cmd(path, Some{Laws.root_sub(spec)}) == found : Maybe<&2, S.Sub>} +) -> {K.read(spec, K.Read{path, name, want}) == K.reads.at(K.reads.args(found), want, path, name) + : List<&2, K.ReadErr>}: + match spec: + case S.Cli{+bin, +about, _v, +args, +subs}: + +here = {Some{S.Sub{bin, about, args, subs}} : Maybe<&2, S.Sub>} + Equal.cong(Maybe<&2, List<&2, S.Arg>>, List<&2, K.ReadErr>, xx => K.reads.at(xx, want, path, name), + K.reads.cmd(path, here), K.reads.args(found), + Equal.trans(Maybe<&2, List<&2, S.Arg>>, K.reads.cmd(path, here), K.reads.args(Laws.named_cmd(path, here)), + K.reads.args(found), rd.cmd(path, here), + Equal.cong(Maybe<&2, S.Sub>, Maybe<&2, List<&2, S.Arg>>, xx => K.reads.args(xx), + Laws.named_cmd(path, here), found, h_cmd))) + +def Laws.read_no_command(spec, path, name, want, h_cmd): + rd.base(spec, path, name, want, None{}, h_cmd) + +# a read of the argument a command's arguments find under its name +def rd.arg( + +spec: Shake.Cli, + +path: List<&2, String>, + +name: String, + +want: K.Want, + +sn: String, + +sa: String, + +sargs: List<&2, S.Arg>, + +ssubs: List<&2, S.Sub>, + +fa: Maybe<&2, S.Arg>, + h_cmd: {Laws.named_cmd(path, Some{Laws.root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>}, + h_arg: {S.by_name(sargs, name) == fa : Maybe<&2, S.Arg>} +) -> {K.read(spec, K.Read{path, name, want}) == K.reads.arg(fa, want, path, name) : List<&2, K.ReadErr>}: + Equal.trans(List<&2, K.ReadErr>, K.read(spec, K.Read{path, name, want}), + K.reads.arg(S.by_name(sargs, name), want, path, name), K.reads.arg(fa, want, path, name), + rd.base(spec, path, name, want, Some{S.Sub{sn, sa, sargs, ssubs}}, h_cmd), + Equal.cong(Maybe<&2, S.Arg>, List<&2, K.ReadErr>, xx => K.reads.arg(xx, want, path, name), S.by_name(sargs, name), + fa, h_arg)) + +def Laws.read_no_arg(spec, path, name, want, sn, sa, sargs, ssubs, h_cmd, h_arg): + rd.arg(spec, path, name, want, sn, sa, sargs, ssubs, None{}, h_cmd, h_arg) + +# the code's flag test is the law's +def rd.flag(kind: S.ArgKind) -> {K.reads.flag(kind) == Laws.is_flag(kind) : Bool}: + match kind: + case S.Flag{}: + {==} + case S.Opt{}: + {==} + case S.Pos{}: + {==} + case S.Rest{}: + {==} + case S.Many{}: + {==} + +def Laws.read_flag(spec, path, name, sn, sa, sargs, ssubs, nn, sh, lo, kind, hp, rq, fb, cs, h_cmd, h_arg): + Equal.trans(List<&2, K.ReadErr>, K.read(spec, K.Read{path, name, K.WantFlag{}}), + K.reads.one(Bool.not(K.reads.flag(kind)), K.NotFlag{path, name}), + K.reads.one(Bool.not(Laws.is_flag(kind)), K.NotFlag{path, name}), + rd.arg(spec, path, name, K.WantFlag{}, sn, sa, sargs, ssubs, Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}}, h_cmd, + h_arg), + Equal.cong(Bool, List<&2, K.ReadErr>, xx => K.reads.one(Bool.not(xx), K.NotFlag{path, name}), K.reads.flag(kind), + Laws.is_flag(kind), rd.flag(kind))) + +def Laws.read_value(spec, path, name, sn, sa, sargs, ssubs, nn, sh, lo, kind, hp, rq, fb, cs, h_cmd, h_arg): + Equal.trans(List<&2, K.ReadErr>, K.read(spec, K.Read{path, name, K.WantValue{}}), + K.reads.one(K.reads.flag(kind), K.IsFlag{path, name}), + K.reads.one(Laws.is_flag(kind), K.IsFlag{path, name}), + rd.arg(spec, path, name, K.WantValue{}, sn, sa, sargs, ssubs, Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}}, h_cmd, + h_arg), + Equal.cong(Bool, List<&2, K.ReadErr>, xx => K.reads.one(xx, K.IsFlag{path, name}), K.reads.flag(kind), + Laws.is_flag(kind), rd.flag(kind))) diff --git a/proofs/CheckReportsSpecErrors.bend b/proofs/CheckReportsSpecErrors.bend new file mode 100644 index 0000000..1744dfa --- /dev/null +++ b/proofs/CheckReportsSpecErrors.bend @@ -0,0 +1,212 @@ +# proofs of laws/CheckReportsSpecErrors.bend +import Base +import ../src/cli.bend as S +import ../src/check.bend as K +import ../laws/CheckReportsSpecErrors.bend as Laws +import ../laws/defs/ArgumentLists.bend as D1 +import ./lemmas/NameAppend.bend as M1 + +# two report lists, each equal to another, one after the other +def sc.both( + +x1: List<&2, K.SpecErr>, + +y1: List<&2, K.SpecErr>, + +x2: List<&2, K.SpecErr>, + +y2: List<&2, K.SpecErr>, + e1: {x1 == y1 : List<&2, K.SpecErr>}, + e2: {x2 == y2 : List<&2, K.SpecErr>} +) -> {K.both(x1, x2) == K.both(y1, y2) : List<&2, K.SpecErr>}: + Equal.trans(List<&2, K.SpecErr>, K.both(x1, x2), K.both(y1, x2), K.both(y1, y2), + Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, zz => K.both(zz, x2), x1, y1, e1), + Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, zz => K.both(y1, zz), x2, y2, e2)) + +# a listed-or-not answer, then the rest's +def sc.ok.at( + bb: Bool, + +r1: Bool, + +r2: Bool, + ee: {r1 == r2 : Bool} +) -> {S.allowed.ok.at(bb, r1) == List.any.if(_u => r2, bb) : Bool}: + match bb: + case True{}: + {==} + case False{}: + ee + +# a value is listed among choices when the choices contain it +def sc.ok( + cs: List<&2, String>, + +vv: String +) -> {S.allowed.ok(cs, vv) == List.contains(~String, ~String.eq, cs, vv) : Bool}: + match cs: + case Nil{}: + {==} + case Con{+hh, +tt}: + sc.ok.at(String.eq(hh, vv), S.allowed.ok(tt, vv), List.contains(~String, ~String.eq, tt, vv), sc.ok(tt, vv)) + +# the default's report is the one the law names +def sc.dflt( + +path: List<&2, String>, + +name: String, + +cs: List<&2, String>, + dflt: Maybe<&2, String> +) -> {K.args.default(dflt, cs, path, name) == D1.off_choice(path, name, cs, dflt) : List<&2, K.SpecErr>}: + match dflt: + case None{}: + {==} + case Some{+vv}: + Equal.cong(Bool, List<&2, K.SpecErr>, + bb => K.one(Bool.and(Bool.not(List.is_empty(&2, String, cs)), Bool.not(bb)), + K.DefaultNotChoice{path, name, vv}), + S.allowed.ok(cs, vv), List.contains(~String, ~String.eq, cs, vv), sc.ok(cs, vv)) + +# the argument reports, from the arguments before +def sc.args( + args: List<&2, S.Arg>, + +path: List<&2, String>, + +before: List<&2, S.Arg> +) -> {K.args.dups(args, path, D1.arg_names(before), D1.arg_shorts(before), D1.arg_longs(before)) + == D1.args_reports(args, path, before) : List<&2, K.SpecErr>}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+name, +short, +long, +kind, +help, +req, S.Fallback{+ev, +dflt}, +cs}: + Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, + zz => K.both( + K.one(List.contains(~String, ~String.eq, D1.arg_names(before), name), K.SameName{path, name}), + K.both(K.one(K.spelled.seen(short, D1.arg_shorts(before)), K.SameShort{path, S.text.of(short, "")}), + K.both(K.one(K.spelled.seen(long, D1.arg_longs(before)), K.SameLong{path, S.text.of(long, "")}), + zz))), + K.both(K.args.default(dflt, cs, path, name), + K.args.dups(tt, path, name <> D1.arg_names(before), K.spelled.add(short, D1.arg_shorts(before)), + K.spelled.add(long, D1.arg_longs(before)))), + K.both(D1.off_choice(path, name, cs, dflt), + D1.args_reports(tt, path, S.Arg{name, short, long, kind, help, req, S.Fallback{ev, dflt}, + cs} <> before)), + sc.both(K.args.default(dflt, cs, path, name), D1.off_choice(path, name, cs, dflt), + K.args.dups(tt, path, name <> D1.arg_names(before), K.spelled.add(short, D1.arg_shorts(before)), + K.spelled.add(long, D1.arg_longs(before))), + D1.args_reports(tt, path, S.Arg{name, short, long, kind, help, req, S.Fallback{ev, dflt}, + cs} <> before), + sc.dflt(path, name, cs, dflt), + sc.args(tt, path, S.Arg{name, short, long, kind, help, req, S.Fallback{ev, dflt}, cs} <> before))) + +# the positional reports, from the positionals before +def sc.pos( + pos: List<&2, S.Arg>, + +path: List<&2, String>, + +before: List<&2, S.Arg> +) -> {K.pos.order(pos, path, D1.any_optional(before)) == D1.pos_reports(pos, path, before) + : List<&2, K.SpecErr>}: + match pos: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+name, +short, +long, +kind, +help, +req, +dflt, +cs}: + Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, + zz => K.both( + K.one(Bool.and(S.rest.is.kind(kind), Bool.not(List.is_empty(&2, S.Arg, tt))), K.RestNotLast{path, name}), + K.both(K.one(Bool.and(req, D1.any_optional(before)), K.RequiredAfterOptional{path, name}), zz)), + K.pos.order(tt, path, Bool.or(D1.any_optional(before), Bool.not(req))), + D1.pos_reports(tt, path, S.Arg{name, short, long, kind, help, req, dflt, cs} <> before), + sc.pos(tt, path, S.Arg{name, short, long, kind, help, req, dflt, cs} <> before)) + +# the subcommand reports, from the subcommands before +def sc.subs( + subs: List<&2, S.Sub>, + +path: List<&2, String>, + +before: List<&2, S.Sub> +) -> {K.subs.dups(subs, path, D1.sub_names(before)) == D1.sub_reports(subs, path, before) : List<&2, K.SpecErr>}: + match subs: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Sub{+name, +about, +args, +kids}: + Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, + zz => K.both(K.one(List.contains(~String, ~String.eq, D1.sub_names(before), name), K.SameSub{path, name}), + K.both(K.one(String.eq(name, "help"), K.HelpSub{path}), zz)), + K.subs.dups(tt, path, name <> D1.sub_names(before)), + D1.sub_reports(tt, path, S.Sub{name, about, args, kids} <> before), + sc.subs(tt, path, S.Sub{name, about, args, kids} <> before)) + +# one command's reports +def sc.cmd( + +path: List<&2, String>, + +args: List<&2, S.Arg>, + +subs: List<&2, S.Sub> +) -> {K.cmd(path, args, subs) == D1.cmd_reports(path, args, subs) : List<&2, K.SpecErr>}: + sc.both(K.args.dups(args, path, [], [], []), D1.args_reports(args, path, []), + K.both(K.pos.order(S.pos_of(args), path, False{}), K.subs.dups(subs, path, [])), + K.both(D1.pos_reports(S.pos_of(args), path, []), D1.sub_reports(subs, path, [])), + sc.args(args, path, []), + sc.both(K.pos.order(S.pos_of(args), path, False{}), D1.pos_reports(S.pos_of(args), path, []), + K.subs.dups(subs, path, []), D1.sub_reports(subs, path, []), + sc.pos(S.pos_of(args), path, []), sc.subs(subs, path, []))) + +# reversing names onto a list is reversing them, then appending it +def sc.rev_go( + xs: List<&2, String>, + +acc: List<&2, String> +) -> {List.reverse.go(&2, String, xs, acc) == List.append(&2, String, List.reverse.go(&2, String, xs, []), acc) + : List<&2, String>}: + match xs: + case Nil{}: + {==} + case Con{+hh, +tt}: + Equal.trans(List<&2, String>, List.reverse.go(&2, String, tt, hh <> acc), + List.append(&2, String, List.reverse.go(&2, String, tt, []), hh <> acc), + List.append(&2, String, List.reverse.go(&2, String, tt, [hh]), acc), + sc.rev_go(tt, hh <> acc), + Equal.sym(List<&2, String>, List.append(&2, String, List.reverse.go(&2, String, tt, [hh]), acc), + List.append(&2, String, List.reverse.go(&2, String, tt, []), hh <> acc), + Equal.trans(List<&2, String>, + List.append(&2, String, List.reverse.go(&2, String, tt, [hh]), acc), + List.append(&2, String, List.append(&2, String, List.reverse.go(&2, String, tt, []), [hh]), acc), + List.append(&2, String, List.reverse.go(&2, String, tt, []), hh <> acc), + Equal.cong(List<&2, String>, List<&2, String>, ys => List.append(&2, String, ys, acc), + List.reverse.go(&2, String, tt, [hh]), + List.append(&2, String, List.reverse.go(&2, String, tt, []), [hh]), sc.rev_go(tt, [hh])), + M1.sc.app(List.reverse.go(&2, String, tt, []), [hh], acc)))) + +# a reversed path with a name in front is the path, then the name +def sc.rev( + +name: String, + back: List<&2, String> +) -> {List.reverse(&2, String, name <> back) == List.append(&2, String, List.reverse(&2, String, back), [name]) + : List<&2, String>}: + sc.rev_go(back, [name]) + +# every subcommand's reports, under a reversed path +def sc.forest( + subs: List<&2, S.Sub>, + +back: List<&2, String> +) -> {K.forest(subs, back) == D1.below(subs, List.reverse(&2, String, back)) : List<&2, K.SpecErr>}: + match subs: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Sub{+name, _a, +args, +kids}: + +rn = List.reverse(&2, String, name <> back) + +rp = List.append(&2, String, List.reverse(&2, String, back), [name]) + sc.both(K.cmd(rn, args, kids), D1.cmd_reports(rp, args, kids), + K.both(K.forest(kids, name <> back), K.forest(tt, back)), + K.both(D1.below(kids, rp), D1.below(tt, List.reverse(&2, String, back))), + Equal.trans(List<&2, K.SpecErr>, K.cmd(rn, args, kids), D1.cmd_reports(rn, args, kids), + D1.cmd_reports(rp, args, kids), sc.cmd(rn, args, kids), + Equal.cong(List<&2, String>, List<&2, K.SpecErr>, pp => D1.cmd_reports(pp, args, kids), rn, rp, + sc.rev(name, back))), + sc.both(K.forest(kids, name <> back), D1.below(kids, rp), K.forest(tt, back), + D1.below(tt, List.reverse(&2, String, back)), + Equal.trans(List<&2, K.SpecErr>, K.forest(kids, name <> back), D1.below(kids, rn), + D1.below(kids, rp), sc.forest(kids, name <> back), + Equal.cong(List<&2, String>, List<&2, K.SpecErr>, pp => D1.below(kids, pp), rn, rp, + sc.rev(name, back))), + sc.forest(tt, back))) + +def Laws.check_listed(_name, _about, _version, args, subs): + sc.both(K.cmd([], args, subs), D1.cmd_reports([], args, subs), K.forest(subs, []), D1.below(subs, []), + sc.cmd([], args, subs), sc.forest(subs, [])) diff --git a/proofs/ChoiceRefused.bend b/proofs/ChoiceRefused.bend new file mode 100644 index 0000000..c845861 --- /dev/null +++ b/proofs/ChoiceRefused.bend @@ -0,0 +1,81 @@ +# proofs of laws/ChoiceRefused.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/ChoiceRefused.bend as Laws +import ../laws/Refused.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 + +# a plain word outside the next positional's choices fails the step with +# BadValue +def choice_refused.step( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + +name: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +rest: List<&2, S.Arg>, + h_dd: {String.eq(tok, "--") == False{} : Bool}, + h_help: {String.eq(tok, "help") == False{} : Bool}, + +h_dash: {String.starts_with(tok, "-") == False{} : Bool}, + h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>}, + h_bad: {S.allowed(cs, tok) == False{} : Bool} +) -> {D1.dead_of(S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, + req, dflt, cs} <> rest, False{}, seen})) == Some{S.BadValue{path, name, tok}} + : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(S.St, S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, + hp, req, dflt, cs} <> rest, False{}, seen}), + S.parse.take_pos(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}), + Walk.wk.word_step(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + seen, h_dd, h_help, h_dash, + h_sub)) + %e1 : {D1.dead_of(_) == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Bool, S.allowed(cs, tok), False{}, h_bad) + %e2 : {D1.dead_of(S.parse.take_choice(_, name, tok, args, up, subs, path, bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{})) + == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.choice_refused( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + seen, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + h_at, + h_dd, + h_help, + h_dash, + h_sub, + h_bad +): + L1.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, + req, dflt, cs} <> rest, False{}, seen}, S.BadValue{path, name, tok}, h_at, + choice_refused.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, + rest, h_dd, h_help, h_dash, h_sub, h_bad)) diff --git a/proofs/ClusterDead.bend b/proofs/ClusterDead.bend new file mode 100644 index 0000000..1da797f --- /dev/null +++ b/proofs/ClusterDead.bend @@ -0,0 +1,6 @@ +# proofs of laws/ClusterDead.bend +import Base +import ../laws/ClusterDead.bend as Laws +import ./lemmas/FailedWalkThrough.bend as M1 + + diff --git a/proofs/ClusterDone.bend b/proofs/ClusterDone.bend new file mode 100644 index 0000000..7ebf396 --- /dev/null +++ b/proofs/ClusterDone.bend @@ -0,0 +1,6 @@ +# proofs of laws/ClusterDone.bend +import Base +import ../laws/ClusterDone.bend as Laws +import ./lemmas/ClusterEnd.bend as M1 + + diff --git a/proofs/DeadWalk.bend b/proofs/DeadWalk.bend new file mode 100644 index 0000000..7bde4cd --- /dev/null +++ b/proofs/DeadWalk.bend @@ -0,0 +1,6 @@ +# proofs of laws/DeadWalk.bend +import Base +import ../laws/DeadWalk.bend as Laws +import ./lemmas/FailedWalkThrough.bend as M1 + + diff --git a/proofs/DefaultsFillUnbound.bend b/proofs/DefaultsFillUnbound.bend new file mode 100644 index 0000000..9dcacb8 --- /dev/null +++ b/proofs/DefaultsFillUnbound.bend @@ -0,0 +1,462 @@ +# proofs of laws/DefaultsFillUnbound.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../src/walk.bend as Walk +import ../src/grow.bend as Grow +import ../laws/DefaultsFillUnbound.bend as Laws +import ../laws/defs/ValuesAtPath.bend as D1 +import ./lemmas/FinishedWalker.bend as M1 + +# a list that is not nonempty is empty +def fin.empty_of( + xs: List<&2, String>, + hh: {Bool.not(List.is_empty(&2, String, xs)) == False{} : Bool} +) -> {xs == [] : List<&2, String>}: + match xs: + case Nil{}: + {==} + case Con{+x, +t}: + Empty.absurd({x <> t == [] : List<&2, String>}, Walk.wk.true_false(hh)) + +# an unbound name has no values +def fin.g_unbound( + +xs: List<&2, S.Bind>, + +nn: String, + hh: {S.bound(xs, nn) == False{} : Bool} +) -> {S.get_all.bind(xs, nn) == [] : List<&2, String>}: + fin.empty_of(S.get_all.bind(xs, nn), + Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))), S.bound(xs, nn), False{}, + Equal.sym(Bool, S.bound(xs, nn), Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))), + M1.fin.bound_g(xs, nn)), hh)) + +# a default of another name adds nothing to `nn`'s values, nor binds it +def fin.one_other( + skip: Bool, + +mm: String, + +vv: String, + +ff: List<&2, S.Bind>, + +nn: String, + h_ne: {String.eq(mm, nn) == False{} : Bool} +) -> {S.get_all.bind(S.fill.one(skip, mm, vv, ff), nn) == S.get_all.bind(ff, nn) : List<&2, String>}: + match skip: + case True{}: + {==} + case False{}: + ee = Equal.sym(Bool, String.eq(mm, nn), False{}, h_ne) + %ee : {S.get_all.bind.hit(_, vv, S.get_all.bind(ff, nn)) == S.get_all.bind(ff, nn) : List<&2, String>} + {==} + +# the same, for whether `nn` is bound +def fin.one_other_b( + skip: Bool, + +mm: String, + +vv: String, + +ff: List<&2, S.Bind>, + +nn: String, + h_ne: {String.eq(mm, nn) == False{} : Bool} +) -> {S.bound(S.fill.one(skip, mm, vv, ff), nn) == S.bound(ff, nn) : Bool}: + match skip: + case True{}: + {==} + case False{}: + ee = Equal.sym(Bool, String.eq(mm, nn), False{}, h_ne) + %ee : {S.bound.at(_, S.bound(ff, nn)) == S.bound(ff, nn) : Bool} + {==} + +# an argument of another name, with or without a default +def fin.go_other( + dd: Maybe<&2, String>, + +mm: String, + +ff: List<&2, S.Bind>, + +nn: String, + +h_ne: {String.eq(mm, nn) == False{} : Bool} +) -> {S.get_all.bind(S.fill.arg.go(dd, mm, ff), nn) == S.get_all.bind(ff, nn) : List<&2, String>}: + match dd: + case None{}: + {==} + case Some{+vv}: + fin.one_other(S.bound(ff, mm), mm, vv, ff, nn, h_ne) + +# the same, for whether `nn` is bound +def fin.go_other_b( + dd: Maybe<&2, String>, + +mm: String, + +ff: List<&2, S.Bind>, + +nn: String, + +h_ne: {String.eq(mm, nn) == False{} : Bool} +) -> {S.bound(S.fill.arg.go(dd, mm, ff), nn) == S.bound(ff, nn) : Bool}: + match dd: + case None{}: + {==} + case Some{+vv}: + fin.one_other_b(S.bound(ff, mm), mm, vv, ff, nn, h_ne) + +# the head of a list with no argument named `nn` is not named `nn` +def fin.named_head( + +mm: String, + +tt: List<&2, S.Arg>, + +nn: String, + +h_none: {Bool.and(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn)) == True{} : Bool} +) -> {String.eq(mm, nn) == False{} : Bool}: + Walk.wk.not_true(String.eq(mm, nn), Walk.wk.and_l(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn), h_none)) + +# the rest of a list with no argument named `nn` has none +def fin.named_tail( + +mm: String, + +tt: List<&2, S.Arg>, + +nn: String, + +h_none: {Bool.and(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn)) == True{} : Bool} +) -> {Laws.none_named(tt, nn) == True{} : Bool}: + Walk.wk.and_r(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn), h_none) + +# filling the defaults of arguments none named `nn` changes none of its values +def fin.fill_other( + args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +nn: String, + +h_none: {Laws.none_named(args, nn) == True{} : Bool} +) -> {S.get_all.bind(S.fill.args(args, bs), nn) == S.get_all.bind(bs, nn) : List<&2, String>}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+mm, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c}: + Equal.trans(List<&2, String>, S.get_all.bind(S.fill.arg.go(dd, mm, S.fill.args(tt, bs)), nn), + S.get_all.bind(S.fill.args(tt, bs), nn), S.get_all.bind(bs, nn), + fin.go_other(dd, mm, S.fill.args(tt, bs), nn, fin.named_head(mm, tt, nn, h_none)), + fin.fill_other(tt, bs, nn, fin.named_tail(mm, tt, nn, h_none))) + +# the same, for whether `nn` is bound +def fin.fill_other_b( + args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +nn: String, + +h_none: {Laws.none_named(args, nn) == True{} : Bool} +) -> {S.bound(S.fill.args(args, bs), nn) == S.bound(bs, nn) : Bool}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+mm, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c}: + Equal.trans(Bool, S.bound(S.fill.arg.go(dd, mm, S.fill.args(tt, bs)), nn), S.bound(S.fill.args(tt, bs), nn), + S.bound(bs, nn), + fin.go_other_b(dd, mm, S.fill.args(tt, bs), nn, fin.named_head(mm, tt, nn, h_none)), + fin.fill_other_b(tt, bs, nn, fin.named_tail(mm, tt, nn, h_none))) + +# arguments before the one named `nn`, none named `nn`, change none of its +# values +def fin.fill_pre( + pa: List<&2, S.Arg>, + +rest: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +nn: String, + +h_none: {Laws.none_named(pa, nn) == True{} : Bool} +) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, rest), bs), nn) + == S.get_all.bind(S.fill.args(rest, bs), nn) : List<&2, String>}: + match pa: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+mm, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c}: + +ff = S.fill.args(List.append(&2, S.Arg, tt, rest), bs) + Equal.trans(List<&2, String>, S.get_all.bind(S.fill.arg.go(dd, mm, ff), nn), S.get_all.bind(ff, nn), + S.get_all.bind(S.fill.args(rest, bs), nn), + fin.go_other(dd, mm, ff, nn, fin.named_head(mm, tt, nn, h_none)), + fin.fill_pre(tt, rest, bs, nn, fin.named_tail(mm, tt, nn, h_none))) + +# the default goes in front, once, of values there were none of +def fin.fill_default.go( + +nn: String, + +dd: String, + +ff: List<&2, S.Bind>, + e1: {False{} == S.bound(ff, nn) : Bool}, + e2: {True{} == String.eq(nn, nn) : Bool}, + e3: {[] == S.get_all.bind(ff, nn) : List<&2, String>} +) -> {S.get_all.bind(S.fill.one(S.bound(ff, nn), nn, dd, ff), nn) == [dd] : List<&2, String>}: + %e1 : {S.get_all.bind(S.fill.one(_, nn, dd, ff), nn) == [dd] : List<&2, String>} + %e2 : {S.get_all.bind.hit(_, dd, S.get_all.bind(ff, nn)) == [dd] : List<&2, String>} + %e3 : {dd <> _ == [dd] : List<&2, String>} + {==} + +# the default of the argument named `nn`, when its command left it unbound +def fin.fill_default( + +pa: List<&2, S.Arg>, + +qa: List<&2, S.Arg>, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +ev: Maybe<&2, String>, + +dd: String, + +cs: List<&2, String>, + +bs: List<&2, S.Bind>, + h_pa: {Laws.none_named(pa, nn) == True{} : Bool}, + +h_qa: {Laws.none_named(qa, nn) == True{} : Bool}, + +h_free: {S.bound(bs, nn) == False{} : Bool} +) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, + Some{dd}}, cs} <> qa), bs), + nn) == [dd] : List<&2, String>}: + +ff = S.fill.args(qa, bs) + e1 = Equal.sym(Bool, S.bound(ff, nn), False{}, + Equal.trans(Bool, S.bound(ff, nn), S.bound(bs, nn), False{}, fin.fill_other_b(qa, bs, nn, h_qa), h_free)) + e2 = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) + e3 = Equal.sym(List<&2, String>, S.get_all.bind(ff, nn), [], + Equal.trans(List<&2, String>, S.get_all.bind(ff, nn), S.get_all.bind(bs, nn), [], fin.fill_other(qa, bs, nn, h_qa), + fin.g_unbound(bs, nn, h_free))) + Equal.trans(List<&2, String>, + S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, + cs} <> qa), + bs), nn), + S.get_all.bind(S.fill.one(S.bound(ff, nn), nn, dd, ff), nn), [dd], + fin.fill_pre(pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, cs} <> qa, bs, nn, h_pa), + fin.fill_default.go(nn, dd, ff, e1, e2, e3)) + +# a bound argument's values, with or without a default +def fin.kept_go( + dflt: Maybe<&2, String>, + +nn: String, + +ff: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + eg: {S.get_all.bind(ff, nn) == S.get_all.bind(bs, nn) : List<&2, String>}, + eb: {S.bound(ff, nn) == True{} : Bool} +) -> {S.get_all.bind(S.fill.arg.go(dflt, nn, ff), nn) == S.get_all.bind(bs, nn) : List<&2, String>}: + match dflt: + case None{}: + eg + case Some{+vv}: + ee = Equal.sym(Bool, S.bound(ff, nn), True{}, eb) + %ee : {S.get_all.bind(S.fill.one(_, nn, vv, ff), nn) == S.get_all.bind(bs, nn) : List<&2, String>} + eg + +# whatever the default, a bound argument keeps exactly its values +def fin.fill_kept( + +pa: List<&2, S.Arg>, + +qa: List<&2, S.Arg>, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +bs: List<&2, S.Bind>, + h_pa: {Laws.none_named(pa, nn) == True{} : Bool}, + +h_qa: {Laws.none_named(qa, nn) == True{} : Bool}, + h_bound: {S.bound(bs, nn) == True{} : Bool} +) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} <> qa), bs), + nn) == S.get_all.bind(bs, nn) : List<&2, String>}: + match dflt: + case S.Fallback{+ev, +dm}: + +ff = S.fill.args(qa, bs) + Equal.trans(List<&2, String>, + S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dm}, + cs} <> qa), + bs), nn), + S.get_all.bind(S.fill.arg.go(dm, nn, ff), nn), S.get_all.bind(bs, nn), + fin.fill_pre(pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dm}, cs} <> qa, bs, nn, h_pa), + fin.kept_go(dm, nn, ff, bs, fin.fill_other(qa, bs, nn, h_qa), + Equal.trans(Bool, S.bound(ff, nn), S.bound(bs, nn), True{}, fin.fill_other_b(qa, bs, nn, h_qa), h_bound))) + +# an argument with no default keeps exactly its values +def fin.fill_nodef( + +pa: List<&2, S.Arg>, + +qa: List<&2, S.Arg>, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +ev: Maybe<&2, String>, + +cs: List<&2, String>, + +bs: List<&2, S.Bind>, + h_pa: {Laws.none_named(pa, nn) == True{} : Bool}, + h_qa: {Laws.none_named(qa, nn) == True{} : Bool} +) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, + cs} <> qa), bs), + nn) == S.get_all.bind(bs, nn) : List<&2, String>}: + Equal.trans(List<&2, String>, + S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, + cs} <> qa), bs), nn), + S.get_all.bind(S.fill.args(qa, bs), nn), S.get_all.bind(bs, nn), + fin.fill_pre(pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs} <> qa, bs, nn, h_pa), + fin.fill_other(qa, bs, + nn, h_qa)) + +# `get_all` in the Matched of the command at `path`: its bindings, defaults +# filled, read back +def fin.all( + +spec: Shake.Cli, + +ws: List<&2, String>, + +found: Shake.Matched, + +qs: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +ms: List<&2, String>, + +nn: String, + h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}, + h_fr: M1.fin.Framed(spec, ws, qs, args, bs, up, path, ms), + h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} +) -> {D1.all_at(found, path, nn) + == Some{List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn))} : Maybe<&2, List<&2, String>>}: + +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} + +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) + Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, nn), + Some{S.get_all.bind(S.own(S.matched.build(f0 <> rq, ms)), nn)}, + Some{List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn))}, + Equal.cong(Maybe<&2, S.Matched>, Maybe<&2, List<&2, String>>, mm => D1.all_at.go(mm, nn), S.at(found, path), + Some{S.matched.build(f0 <> rq, ms)}, M1.fin.at(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len)), + Equal.trans(Maybe<&2, List<&2, String>>, Some{S.get_all.bind(S.own(S.matched.build(f0 <> rq, ms)), nn)}, + Some{S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, bs)), nn)}, + Some{List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn))}, + Equal.cong(List<&2, S.Bind>, Maybe<&2, List<&2, String>>, xs => Some{S.get_all.bind(xs, nn)}, + S.own(S.matched.build(f0 <> rq, ms)), List.reverse(&2, S.Bind, S.fill.args(args, bs)), + Grow.gw.own_build(f0, rq, ms)), + Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, xs => Some{xs}, + S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, bs)), nn), + List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn)), M1.fin.g_rev(S.fill.args(args, bs), nn)))) + +# `get_all` of the command at `path` reads `xs` +def fin.All(+found: Shake.Matched, +path: List<&2, String>, +nn: String, +xs: List<&2, String>) -> Type: + {D1.all_at(found, path, nn) == Some{xs} : Maybe<&2, List<&2, String>>} + +# a level's values read back, from what filling it gives the name +def fin.back( + +found: Shake.Matched, + +path: List<&2, String>, + +nn: String, + +xs: List<&2, String>, + +ys: List<&2, String>, + ea: {D1.all_at(found, path, nn) == Some{List.reverse(&2, String, xs)} : Maybe<&2, List<&2, String>>}, + ex: {xs == ys : List<&2, String>} +) -> {D1.all_at(found, path, nn) == Some{List.reverse(&2, String, ys)} : Maybe<&2, List<&2, String>>}: + Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, nn), Some{List.reverse(&2, String, xs)}, + Some{List.reverse(&2, String, ys)}, ea, + Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, zs => Some{List.reverse(&2, String, zs)}, xs, ys, ex)) + +# the values the words bound, read back in binding order +def fin.back_bs( + +found: Shake.Matched, + +path: List<&2, String>, + +nn: String, + +bs: List<&2, S.Bind>, + ea: fin.All(found, path, nn, List.reverse(&2, String, S.get_all.bind(bs, nn))) +) -> {D1.all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} + : Maybe<&2, List<&2, String>>}: + Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, nn), + Some{List.reverse(&2, String, S.get_all.bind(bs, nn))}, Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)}, ea, + Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, zs => Some{zs}, + List.reverse(&2, String, S.get_all.bind(bs, nn)), S.get_all.bind(List.reverse(&2, S.Bind, bs), nn), + Equal.sym(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, bs), nn), + List.reverse(&2, String, S.get_all.bind(bs, nn)), M1.fin.g_rev(bs, nn)))) + +def Laws.default_filled( + spec, + ws, + found, + qs, + pa, + qa, + nn, + sh, + lo, + kind, + hp, + req, + ev, + dd, + cs, + bs, + up, + path, + ms, + h_done, + h_fr, + h_len, + h_pa, + h_qa, + h_free +): + +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, cs} <> qa) + fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), [dd], + fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), + fin.fill_default(pa, qa, nn, sh, lo, kind, hp, req, ev, dd, cs, bs, h_pa, h_qa, h_free)) + +def Laws.bound_kept( + spec, + ws, + found, + qs, + pa, + qa, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + bs, + up, + path, + ms, + h_done, + h_fr, + h_len, + h_pa, + h_qa, + h_bound +): + +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} <> qa) + fin.back_bs(found, path, nn, bs, + fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), S.get_all.bind(bs, nn), + fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), + fin.fill_kept(pa, qa, nn, sh, lo, kind, hp, req, dflt, cs, bs, h_pa, h_qa, h_bound))) + +def Laws.no_default( + spec, + ws, + found, + qs, + pa, + qa, + nn, + sh, + lo, + kind, + hp, + req, + ev, + cs, + bs, + up, + path, + ms, + h_done, + h_fr, + h_len, + h_pa, + h_qa +): + +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs} <> qa) + fin.back_bs(found, path, nn, bs, + fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), S.get_all.bind(bs, nn), + fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), + fin.fill_nodef(pa, qa, nn, sh, lo, kind, hp, req, ev, cs, bs, h_pa, h_qa))) + +def Laws.no_argument(spec, ws, found, qs, args, nn, bs, up, path, ms, h_done, h_fr, h_len, h_none): + fin.back_bs(found, path, nn, bs, + fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), S.get_all.bind(bs, nn), + fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), + fin.fill_other(args, bs, nn, h_none))) diff --git a/proofs/DoubleDashEndsOptions.bend b/proofs/DoubleDashEndsOptions.bend new file mode 100644 index 0000000..8c6bfe1 --- /dev/null +++ b/proofs/DoubleDashEndsOptions.bend @@ -0,0 +1,8 @@ +# proofs of laws/DoubleDashEndsOptions.bend +import Base +import ../src/cli.bend as S +import ../laws/DoubleDashEndsOptions.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + +def Laws.dd_step(spec, pre, args, up, subs, path, bs, pos, seen, h_at): + M1.kept.stepped(spec, pre, "--", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, h_at) diff --git a/proofs/DropZero.bend b/proofs/DropZero.bend new file mode 100644 index 0000000..bf1809d --- /dev/null +++ b/proofs/DropZero.bend @@ -0,0 +1,6 @@ +# proofs of laws/DropZero.bend +import Base +import ../laws/DropZero.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + + diff --git a/proofs/EnterMissing.bend b/proofs/EnterMissing.bend new file mode 100644 index 0000000..7245f81 --- /dev/null +++ b/proofs/EnterMissing.bend @@ -0,0 +1,72 @@ +# proofs of laws/EnterMissing.bend +import Base +import ../src/cli.bend as S +import ../laws/EnterMissing.bend as Laws +import ../laws/Refused.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +# a blocking positional: the word fails the step with Missing of it +def enter.miss( + +pos: List<&2, S.Arg>, + +sub: S.Sub, + +nm: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + h_req: {S.parse.first_req(pos) == Some{nm} : Maybe<&2, String>} +) -> {D1.dead_of(S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.Missing{path, nm}} : Maybe<&2, S.ParseErr>}: + er = Equal.sym(Maybe<&2, String>, S.parse.first_req(pos), Some{nm}, h_req) + %er : {D1.dead_of(S.parse.enter_or(_, sub, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.Missing{path, nm}} : Maybe<&2, S.ParseErr>} + {==} + +# a step equal to a failed walker fails the same way +def enter.dead( + +tok: String, + +st: S.St, + +st2: S.St, + +err: S.ParseErr, + ee: {st2 == S.parse.step(tok, st) : S.St}, + hd: {D1.dead_of(st2) == Some{err} : Maybe<&2, S.ParseErr>} +) -> {D1.dead_of(S.parse.step(tok, st)) == Some{err} : Maybe<&2, S.ParseErr>}: + %ee : {D1.dead_of(_) == Some{err} : Maybe<&2, S.ParseErr>} + hd + +def Laws.enter_missing( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + pos, + seen, + sub, + nm, + h_at, + h_dd, + h_help, + h_dash, + h_sub, + h_req +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e1 = Equal.sym(S.St, S.parse.step(tok, st), + S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), + Equal.trans(S.St, S.parse.step(tok, st), + S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen), + S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), + M2.enter.word(tok, args, up, subs, path, bs, pos, seen, h_dd, h_help, h_dash), + M2.enter.go(tok, args, up, subs, path, bs, pos, seen, sub, h_sub))) + L1.refused(spec, pre, more, tok, st, S.Missing{path, nm}, h_at, + enter.dead(tok, st, S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), + S.Missing{path, nm}, e1, enter.miss(pos, sub, nm, args, up, subs, path, bs, seen, h_req))) diff --git a/proofs/EnterStep.bend b/proofs/EnterStep.bend new file mode 100644 index 0000000..e82139c --- /dev/null +++ b/proofs/EnterStep.bend @@ -0,0 +1,65 @@ +# proofs of laws/EnterStep.bend +import Base +import ../src/cli.bend as S +import ../laws/EnterStep.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + +# nothing blocking: the subcommand is entered +def enter.none( + +pos: List<&2, S.Arg>, + +name: String, + +about: String, + +next: List<&2, S.Arg>, + +kids: List<&2, S.Sub>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + h_req: {S.parse.first_req(pos) == None{} : Maybe<&2, String>} +) -> {S.parse.enter_or(S.parse.first_req(pos), S.Sub{name, about, next, kids}, args, up, subs, path, bs, pos, False{}, + seen) + == S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), + False{}, False{}} : S.St}: + er = Equal.sym(Maybe<&2, String>, S.parse.first_req(pos), None{}, h_req) + %er : {S.parse.enter_or(_, S.Sub{name, about, next, kids}, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), + False{}, False{}} : S.St} + {==} + +def Laws.enter_step( + spec, + pre, + tok, + args, + up, + subs, + path, + bs, + pos, + seen, + name, + about, + next, + kids, + h_at, + h_dd, + h_help, + h_dash, + h_sub, + h_req +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + +sub = {S.Sub{name, about, next, kids} : S.Sub} + +rhs = {S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), + False{}, False{}} : S.St} + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), S.parse.step(tok, st), rhs, + M1.kept.stepped(spec, pre, tok, st, h_at), + Equal.trans(S.St, S.parse.step(tok, st), + S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen), rhs, + M1.enter.word(tok, args, up, subs, path, bs, pos, seen, h_dd, h_help, h_dash), + Equal.trans(S.St, S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen), + S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), rhs, + M1.enter.go(tok, args, up, subs, path, bs, pos, seen, sub, h_sub), + enter.none(pos, name, about, next, kids, args, up, subs, path, bs, seen, h_req)))) diff --git a/proofs/EnvFallsBetweenWordsAndDefault.bend b/proofs/EnvFallsBetweenWordsAndDefault.bend new file mode 100644 index 0000000..266257d --- /dev/null +++ b/proofs/EnvFallsBetweenWordsAndDefault.bend @@ -0,0 +1,190 @@ +# proofs of laws/EnvFallsBetweenWordsAndDefault.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/EnvFallsBetweenWordsAndDefault.bend as Laws +import ../laws/EnvValueOutsideChoicesFails.bend as L1 +import ./lemmas/EnvChecksNoArgs.bend as M1 + +def Laws.env_sets(_nn, _sh, _lo, _kind, _hp, _req, _ev, _dd, _cs, _var): + {==} + +def Laws.env_of_none(_vars): + {==} + +def Laws.env_of_nil(_var): + {==} + +def Laws.env_of_hit(var, nn, vv, rest, h_eq, h_full): + e1 = Equal.sym(Bool, String.eq(nn, var), True{}, h_eq) + %e1 : {S.env.full(S.env.get.at(_, vv, S.env.get(rest, var))) == Some{vv} : Maybe<&2, String>} + e2 = Equal.sym(Bool, String.is_empty(vv), False{}, h_full) + %e2 : {Bool.pick(Maybe<&2, String>, _, None{}, Some{vv}) == Some{vv} : Maybe<&2, String>} + {==} + +def Laws.env_of_empty(var, nn, vv, rest, h_eq, h_empty): + e1 = Equal.sym(Bool, String.eq(nn, var), True{}, h_eq) + %e1 : {S.env.full(S.env.get.at(_, vv, S.env.get(rest, var))) == None{} : Maybe<&2, String>} + e2 = Equal.sym(Bool, String.is_empty(vv), True{}, h_empty) + %e2 : {Bool.pick(Maybe<&2, String>, _, None{}, Some{vv}) == None{} : Maybe<&2, String>} + {==} + +def Laws.env_of_skip(var, nn, vv, rest, h_eq): + e1 = Equal.sym(Bool, String.eq(nn, var), False{}, h_eq) + %e1 : {S.env.full(S.env.get.at(_, vv, S.env.get(rest, var))) == S.env.of(Some{var}, rest) + : Maybe<&2, String>} + {==} + +# that a kind is not a flag +def fb.not_flag(kk: S.ArgKind) -> Type: + {S.env.flag.is(kk) == False{} : Bool} + +# what an env value gives an argument other than a flag is that value +def fb.kind( + kk: S.ArgKind, + +vv: String, + hh: fb.not_flag(kk) +) -> {S.env.kind(kk, vv) == Some{vv} : Maybe<&2, String>}: + match kk: + case S.Flag{}: + Empty.absurd({S.env.kind(S.Flag{}, vv) == Some{vv} : Maybe<&2, String>}, Walk.wk.true_false(hh)) + case S.Opt{}: + {==} + case S.Many{}: + {==} + case S.Pos{}: + {==} + case S.Rest{}: + {==} + +def Laws.fallback_env(nn, sh, lo, kind, hp, req, ev, dd, cs, vars, vv, h_kind, h_env): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) + %e1 : {S.Arg{nn, sh, lo, kind, hp, req, S.resolve.with(_, kind, ev, dd), cs} + == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{vv}}, cs} : S.Arg} + e2 = Equal.sym(Maybe<&2, String>, S.env.kind(kind, vv), Some{vv}, fb.kind(kind, vv, h_kind)) + %e2 : {S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, _}, cs} + == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{vv}}, cs} : S.Arg} + {==} + +def Laws.fallback_flag_on(nn, sh, lo, hp, req, ev, dd, cs, vars, vv, h_env, h_on): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) + %e1 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.resolve.with(_, S.Flag{}, ev, dd), cs} + == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Some{"true"}}, cs} : S.Arg} + e2 = Equal.sym(Bool, S.allowed.ok(Laws.off_words(), String.to_lower(vv)), False{}, h_on) + %e2 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Bool.pick(Maybe<&2, String>, _, None{}, Some{"true"})}, cs} + == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Some{"true"}}, cs} : S.Arg} + {==} + +def Laws.fallback_flag_off(nn, sh, lo, hp, req, ev, dd, cs, vars, vv, h_env, h_off): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) + %e1 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.resolve.with(_, S.Flag{}, ev, dd), cs} + == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, None{}}, cs} : S.Arg} + e2 = Equal.sym(Bool, S.allowed.ok(Laws.off_words(), String.to_lower(vv)), True{}, h_off) + %e2 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Bool.pick(Maybe<&2, String>, _, None{}, Some{"true"})}, cs} + == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, None{}}, cs} : S.Arg} + {==} + +def Laws.fallback_default(nn, sh, lo, kind, hp, req, ev, dd, cs, vars, h_env): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), None{}, h_env) + %e1 : {S.Arg{nn, sh, lo, kind, hp, req, S.resolve.with(_, kind, ev, dd), cs} + == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs} : S.Arg} + {==} + +def Laws.resolve_spec(_nn, _ab, _ver, _args, _subs, _vars): + {==} + +def Laws.resolve_args_con(_aa, _tt, _vars): + {==} + +def Laws.resolve_subs_con(_nn, _ab, _args, _kids, _tt, _vars): + {==} + +def Laws.env_parse(_spec, _ws, _vars): + {==} + +# an argument resolved under no pairs is unchanged +def rs.arg_nil(aa: S.Arg) -> {S.resolve.arg(aa, []) == aa : S.Arg}: + match aa: + case S.Arg{+n, +s, +l, +kk, +h, +r, fb, +cs}: + match fb: + case S.Fallback{ev, +dd}: + match ev: + case None{}: + {==} + case Some{+var}: + {==} + +# arguments resolved under no pairs are unchanged +def rs.args_nil(args: List<&2, S.Arg>) -> {S.resolve.args(args, []) == args : List<&2, S.Arg>}: + match args: + case Nil{}: + {==} + case Con{+hh, +tt}: + e1 = Equal.sym(S.Arg, S.resolve.arg(hh, []), hh, rs.arg_nil(hh)) + %e1 : {_ <> S.resolve.args(tt, []) == hh <> tt : List<&2, S.Arg>} + e2 = Equal.sym(List<&2, S.Arg>, S.resolve.args(tt, []), tt, rs.args_nil(tt)) + %e2 : {hh <> _ == hh <> tt : List<&2, S.Arg>} + {==} + +# subcommands resolved under no pairs are unchanged +def rs.subs_nil(subs: List<&2, S.Sub>) -> {S.resolve.subs(subs, []) == subs : List<&2, S.Sub>}: + match subs: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Sub{+n, +a, +args, +kids}: + e1 = Equal.sym(List<&2, S.Arg>, S.resolve.args(args, []), args, rs.args_nil(args)) + %e1 : {S.Sub{n, a, _, S.resolve.subs(kids, [])} <> S.resolve.subs(tt, []) + == S.Sub{n, a, args, kids} <> tt : List<&2, S.Sub>} + e2 = Equal.sym(List<&2, S.Sub>, S.resolve.subs(kids, []), kids, rs.subs_nil(kids)) + %e2 : {S.Sub{n, a, args, _} <> S.resolve.subs(tt, []) == S.Sub{n, a, args, kids} <> tt : List<&2, S.Sub>} + e3 = Equal.sym(List<&2, S.Sub>, S.resolve.subs(tt, []), tt, rs.subs_nil(tt)) + %e3 : {S.Sub{n, a, args, kids} <> _ == S.Sub{n, a, args, kids} <> tt : List<&2, S.Sub>} + {==} + +# a spec resolved under no pairs is unchanged +def rs.spec_nil(spec: S.Cli) -> {S.resolve(spec, []) == spec : S.Cli}: + match spec: + case S.Cli{+n, +a, +v, +args, +subs}: + e1 = Equal.sym(List<&2, S.Arg>, S.resolve.args(args, []), args, rs.args_nil(args)) + %e1 : {S.Cli{n, a, v, _, S.resolve.subs(subs, [])} == S.Cli{n, a, v, args, subs} : S.Cli} + e2 = Equal.sym(List<&2, S.Sub>, S.resolve.subs(subs, []), subs, rs.subs_nil(subs)) + %e2 : {S.Cli{n, a, v, args, _} == S.Cli{n, a, v, args, subs} : S.Cli} + {==} + +# no command on the path has a bad argument under no pairs +def ck.go_nil( + mm: S.Matched, + +args: List<&2, S.Arg>, + +subs: List<&2, S.Sub> +) -> {S.env.bad.go(mm, args, subs, []) == None{} : Maybe<&2, S.Var>}: + match mm: + case S.Leaf{+bs}: + L1.bad_args_nil(args, bs) + case S.Node{+bs, +name, next}: + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.args(args, [], bs), None{}, L1.bad_args_nil(args, bs)) + %e1 : {S.env.first(_, + S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), [])) + == None{} : Maybe<&2, S.Var>} + ck.go_nil(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name))) + +# the check under no pairs leaves every result as it is +def ck.nil( + spec: S.Cli, + got: Result<&2, &2, S.ParseErr, S.Matched> +) -> {S.env.check(spec, [], got) == got : Result<&2, &2, S.ParseErr, S.Matched>}: + match spec: + case S.Cli{_n, _a, _v, +args, +subs}: + match got: + case Fail{_e}: + {==} + case Done{+mm}: + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.go(mm, args, subs, []), None{}, ck.go_nil(mm, args, subs)) + %e1 : {S.env.check.done(_, mm) == Done{mm} : Result<&2, &2, S.ParseErr, S.Matched>} + {==} + +def Laws.env_none(spec, ws): + e1 = Equal.sym(S.Cli, S.resolve(spec, []), spec, rs.spec_nil(spec)) + %e1 : {S.env.check(spec, [], S.parse(_, ws)) == S.parse(spec, ws) : Result<&2, &2, S.ParseErr, S.Matched>} + ck.nil(spec, S.parse(spec, ws)) diff --git a/proofs/EnvValueOutsideChoicesFails.bend b/proofs/EnvValueOutsideChoicesFails.bend new file mode 100644 index 0000000..2d9f6fe --- /dev/null +++ b/proofs/EnvValueOutsideChoicesFails.bend @@ -0,0 +1,122 @@ +# proofs of laws/EnvValueOutsideChoicesFails.bend +import Base +import ../src/cli.bend as S +import ../laws/EnvValueOutsideChoicesFails.bend as Laws +import ./lemmas/EnvChecksNoArgs.bend as M1 + +def Laws.env_check_fail(_spec, _vars, _ee): + {==} + +def Laws.env_check_good(_nn, _ab, _ver, args, subs, vars, mm, h_none): + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.go(mm, args, subs, vars), None{}, h_none) + %e1 : {S.env.check.done(_, mm) == Done{mm} : Result<&2, &2, S.ParseErr, S.Matched>} + {==} + +def Laws.env_check_bad(_nn, _ab, _ver, args, subs, vars, mm, name, vv, h_bad): + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.go(mm, args, subs, vars), Some{(name, vv)}, h_bad) + %e1 : {S.env.check.done(_, mm) == Fail{S.BadValue{S.path_of(mm), name, vv}} + : Result<&2, &2, S.ParseErr, S.Matched>} + {==} + +def Laws.bad_go_leaf(_bs, _args, _subs, _vars): + {==} + +def Laws.bad_go_root(bs, name, next, args, subs, vars, pp, h_here): + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.args(args, vars, bs), Some{pp}, h_here) + %e1 : {S.env.first(_, + S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars)) + == Some{pp} : Maybe<&2, S.Var>} + {==} + +def Laws.bad_go_down(bs, name, next, args, subs, vars, h_none): + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.args(args, vars, bs), None{}, h_none) + %e1 : {S.env.first(_, + S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars)) + == S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars) + : Maybe<&2, S.Var>} + {==} + +def Laws.bad_args_here(aa, tt, vars, bs, pp, h_here): + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.arg(aa, vars, bs), Some{pp}, h_here) + %e1 : {S.env.first(_, S.env.bad.args(tt, vars, bs)) == Some{pp} : Maybe<&2, S.Var>} + {==} + +def Laws.bad_args_next(aa, tt, vars, bs, h_none): + e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.arg(aa, vars, bs), None{}, h_none) + %e1 : {S.env.first(_, S.env.bad.args(tt, vars, bs)) == S.env.bad.args(tt, vars, bs) : Maybe<&2, S.Var>} + {==} + +# an `and` with a false right side is false +def ba.and_false(bb: Bool) -> {Bool.and(bb, False{}) == False{} : Bool}: + match bb: + case True{}: + {==} + case False{}: + {==} + +# a flag is never reported, whatever its env value +def ba.flag( + found: Maybe<&2, String>, + +nn: String, + +cs: List<&2, String>, + +bs: List<&2, S.Bind> +) -> {S.env.bad.val(found, S.Flag{}, nn, cs, bs) == None{} : Maybe<&2, S.Var>}: + match found: + case None{}: + {==} + case Some{_vv}: + {==} + +def Laws.bad_arg_some(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, vv, h_env, h_kind, h_out, h_bound): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) + %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == Some{(nn, vv)} : Maybe<&2, S.Var>} + e2 = Equal.sym(Bool, S.env.flag.is(kind), False{}, h_kind) + %e2 : {Bool.pick(Maybe<&2, S.Var>, + Bool.and(Bool.not(_), Bool.and(Bool.not(S.allowed(cs, vv)), + List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv))), + Some{(nn, vv)}, None{}) == Some{(nn, vv)} : Maybe<&2, S.Var>} + e3 = Equal.sym(Bool, S.allowed(cs, vv), False{}, h_out) + %e3 : {Bool.pick(Maybe<&2, S.Var>, + Bool.and(Bool.not(_), List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv)), + Some{(nn, vv)}, None{}) == Some{(nn, vv)} : Maybe<&2, S.Var>} + e4 = Equal.sym(Bool, List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv), True{}, h_bound) + %e4 : {Bool.pick(Maybe<&2, S.Var>, _, Some{(nn, vv)}, None{}) == Some{(nn, vv)} : Maybe<&2, S.Var>} + {==} + +def Laws.bad_arg_unset(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, h_env): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), None{}, h_env) + %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == None{} : Maybe<&2, S.Var>} + {==} + +def Laws.bad_arg_flag(nn, _sh, _lo, _hp, _req, ev, _dd, cs, vars, bs): + ba.flag(S.env.of(ev, vars), nn, cs, bs) + +def Laws.bad_arg_allowed(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, vv, h_env, h_in): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) + %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == None{} : Maybe<&2, S.Var>} + e2 = Equal.sym(Bool, S.allowed(cs, vv), True{}, h_in) + %e2 : {Bool.pick(Maybe<&2, S.Var>, + Bool.and(Bool.not(S.env.flag.is(kind)), Bool.and(Bool.not(_), + List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv))), + Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} + e3 = Equal.sym(Bool, Bool.and(Bool.not(S.env.flag.is(kind)), False{}), False{}, + ba.and_false(Bool.not(S.env.flag.is(kind)))) + %e3 : {Bool.pick(Maybe<&2, S.Var>, _, Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} + {==} + +def Laws.bad_arg_unbound(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, vv, h_env, h_free): + e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) + %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == None{} : Maybe<&2, S.Var>} + e2 = Equal.sym(Bool, List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv), False{}, h_free) + %e2 : {Bool.pick(Maybe<&2, S.Var>, + Bool.and(Bool.not(S.env.flag.is(kind)), Bool.and(Bool.not(S.allowed(cs, vv)), _)), + Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} + e3 = Equal.sym(Bool, Bool.and(Bool.not(S.allowed(cs, vv)), False{}), False{}, + ba.and_false(Bool.not(S.allowed(cs, vv)))) + %e3 : {Bool.pick(Maybe<&2, S.Var>, + Bool.and(Bool.not(S.env.flag.is(kind)), _), + Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} + e4 = Equal.sym(Bool, Bool.and(Bool.not(S.env.flag.is(kind)), False{}), False{}, + ba.and_false(Bool.not(S.env.flag.is(kind)))) + %e4 : {Bool.pick(Maybe<&2, S.Var>, _, Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} + {==} diff --git a/proofs/EnvVarsAskedInOrder.bend b/proofs/EnvVarsAskedInOrder.bend new file mode 100644 index 0000000..d7af71e --- /dev/null +++ b/proofs/EnvVarsAskedInOrder.bend @@ -0,0 +1,15 @@ +# proofs of laws/EnvVarsAskedInOrder.bend +import Base +import ../laws/EnvVarsAskedInOrder.bend as Laws + +def Laws.env_names_root(_nn, _ab, _ver, _args, _subs): + {==} + +def Laws.env_names_forest(_nn, _ab, _args, _kids, _tt): + {==} + +def Laws.env_names_some(_nn, _sh, _lo, _kind, _hp, _req, _var, _dd, _cs, _tt): + {==} + +def Laws.env_names_none(_nn, _sh, _lo, _kind, _hp, _req, _dd, _cs, _tt): + {==} diff --git a/proofs/ErrTextEmptyForRequests.bend b/proofs/ErrTextEmptyForRequests.bend new file mode 100644 index 0000000..dbe5a15 --- /dev/null +++ b/proofs/ErrTextEmptyForRequests.bend @@ -0,0 +1,29 @@ +# proofs of laws/ErrTextEmptyForRequests.bend +import Base +import ../src/cli.bend as S +import ../laws/ErrTextEmptyForRequests.bend as Laws + +def Laws.err_text_help(_spec, _path): + {==} + +def Laws.err_text_version(_spec): + {==} + +def Laws.err_text_iff(spec, err): + match spec err: + case S.Cli{_n, _a, _v, _g, _s} S.UnknownFlag{_at, _flag}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.Missing{_at, _name}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.NoValue{_at, _name}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.BadValue{_at, _name, _value}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.NeedHelp{_path}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.Unexpected{_at, _arg}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.Repeated{_at, _name}: + {==} + case S.Cli{_n, _a, _v, _g, _s} S.NeedVersion{}: + {==} diff --git a/proofs/ErrTextNamesArgumentForm.bend b/proofs/ErrTextNamesArgumentForm.bend new file mode 100644 index 0000000..129ffd2 --- /dev/null +++ b/proofs/ErrTextNamesArgumentForm.bend @@ -0,0 +1,177 @@ +# proofs of laws/ErrTextNamesArgumentForm.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../laws/ErrTextNamesArgumentForm.bend as Laws +import ../laws/defs/ArgumentLists.bend as D1 +import ../laws/defs/HelpPages.bend as D2 +import ./lemmas/StringAppendEmpty.bend as M1 + +# the find kept here is the later of the one above and this one +def ef.here( + found: Maybe<&2, S.Arg>, + +best: Maybe<&2, S.Arg> +) -> {S.err.find.here(found, best) == Laws.keep_later(best, found) : Maybe<&2, S.Arg>}: + match found: + case None{}: + {==} + case Some{_a}: + {==} + +# keeping the later find is associative +def ef.assoc( + +found: Maybe<&2, S.Arg>, + +best: Maybe<&2, S.Arg>, + later: Maybe<&2, S.Arg> +) -> {Laws.keep_later(S.err.find.here(found, best), later) + == Laws.keep_later(best, Laws.keep_later(found, later)) : Maybe<&2, S.Arg>}: + match later: + case None{}: + ef.here(found, best) + case Some{_a}: + {==} + +# the search down a path keeps the deepest page's find, else `best` +def ef.go( + path: List<&2, String>, + how: S.How, + +name: String, + +best: Maybe<&2, S.Arg> +) -> {S.err.find.go(path, how, name, best) == Laws.keep_later(best, Laws.deepest(Laws.pages(path, how), name)) + : Maybe<&2, S.Arg>}: + match path: + case Nil{}: + match how: + case S.How{_b, _a, _v, +args, _s, _u}: + ef.here(S.by_name(args, name), best) + case Con{+hh, +tt}: + match how: + case S.How{+bin, +about, +version, +args, +subs, +usage}: + +hw = {S.How{bin, about, version, args, subs, usage} : S.How} + +here = S.err.find.here(S.by_name(args, name), best) + +deeper = Laws.deepest(Laws.pages(tt, S.help.step(hw, hh)), name) + Equal.trans(Maybe<&2, S.Arg>, S.err.find.go(tt, S.help.step(hw, hh), name, here), + Laws.keep_later(here, deeper), Laws.keep_later(best, Laws.keep_later(S.by_name(args, name), deeper)), + ef.go(tt, S.help.step(hw, hh), name, here), + ef.assoc(S.by_name(args, name), best, deeper)) + +# keeping the later of nothing and a find is the find +def ef.none(found: Maybe<&2, S.Arg>) -> {Laws.keep_later(None{}, found) == found : Maybe<&2, S.Arg>}: + match found: + case None{}: + {==} + case Some{_a}: + {==} + +def Laws.err_find(spec, at, name): + match spec: + case S.Cli{+bin, +about, +version, +args, +subs}: + +hw = {S.How{bin, about, version, args, subs, bin} : S.How} + Equal.trans(Maybe<&2, S.Arg>, S.err.find.go(at, hw, name, None{}), + Laws.keep_later(None{}, Laws.deepest(Laws.pages(at, hw), name)), Laws.deepest(Laws.pages(at, hw), name), + ef.go(at, hw, name, None{}), ef.none(Laws.deepest(Laws.pages(at, hw), name))) + +# a string with nothing after it, under a char +def ef.nil_under(+cc: Char, xs: String) -> {SCon{cc, xs ++ ""} == SCon{cc, xs} : String}: + Equal.cong(String, String, ys => SCon{cc, ys}, xs ++ "", xs, M1.hs.nil(xs)) + +def Laws.err_form(nn, _sh, _lo, kind, _hp, req, _fb, _cs): + match kind: + case S.Flag{}: + {==} + case S.Opt{}: + {==} + case S.Many{}: + {==} + case S.Pos{}: + match req: + case True{}: + ef.nil_under('<', String.to_upper(nn) ++ ">") + case False{}: + ef.nil_under('[', String.to_upper(nn) ++ "]") + case S.Rest{}: + match req: + case True{}: + {==} + case False{}: + {==} + +# a found argument is named as the law names it +def en.named(found: Maybe<&2, S.Arg>, +name: String) -> {S.err.named(found, name) == D1.named(found, name) : String}: + match found: + case None{}: + {==} + case Some{aa}: + match aa: + case S.Arg{+nn, +sh, +lo, +kind, +hp, +req, +fb, +cs}: + Laws.err_form(nn, sh, lo, kind, hp, req, fb, cs) + +# an error's argument, named as the law names it +def en.name( + +spec: Shake.Cli, + +at: List<&2, String>, + +name: String +) -> {S.err.name(spec, at, name) == D1.named(Laws.deepest(Laws.pages(at, D2.root(spec)), name), name) : String}: + +dd = Laws.deepest(Laws.pages(at, D2.root(spec)), name) + Equal.trans(String, S.err.named(S.err.find(spec, at, name), name), S.err.named(dd, name), D1.named(dd, name), + Equal.cong(Maybe<&2, S.Arg>, String, ff => S.err.named(ff, name), S.err.find(spec, at, name), dd, + Laws.err_find(spec, at, name)), + en.named(dd, name)) + +# a found argument's choices line is the law's +def en.values(found: Maybe<&2, S.Arg>) -> {S.err.values(found) == Laws.values_line(found) : String}: + match found: + case None{}: + {==} + case Some{aa}: + match aa: + case S.Arg{_n, _s, _l, _k, _h, _r, _d, cs}: + match cs: + case Nil{}: + {==} + case Con{_c, _t}: + {==} + +def Laws.err_missing_named(spec, at, name): + Equal.cong(String, String, xx => S.err.wrap(spec, at, + "error: the following required argument was not provided:\n " ++ xx), + S.err.name(spec, at, name), D1.named(Laws.deepest(Laws.pages(at, D2.root(spec)), name), name), + en.name(spec, at, name)) + +def Laws.err_novalue_named(spec, at, name): + Equal.cong(String, String, xx => S.err.wrap(spec, at, + "error: a value is required for '" ++ xx ++ "' but none was supplied"), + S.err.name(spec, at, name), D1.named(Laws.deepest(Laws.pages(at, D2.root(spec)), name), name), + en.name(spec, at, name)) + +def Laws.err_repeated_named(spec, at, name): + Equal.cong(String, String, xx => S.err.wrap(spec, at, + "error: the argument '" ++ xx ++ "' cannot be used multiple times"), + S.err.name(spec, at, name), D1.named(Laws.deepest(Laws.pages(at, D2.root(spec)), name), name), + en.name(spec, at, name)) + +def Laws.err_bad_named(spec, at, name, value): + +dd = Laws.deepest(Laws.pages(at, D2.root(spec)), name) + +ff = S.err.find(spec, at, name) + Equal.trans(String, + S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(ff, + name) ++ "'" ++ S.err.values(ff)), + S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(dd, + name) ++ "'" ++ S.err.values(dd)), + S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ D1.named(dd, + name) ++ "'" ++ Laws.values_line(dd)), + Equal.cong(Maybe<&2, S.Arg>, String, + gg => S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(gg, name) ++ "'" ++ + S.err.values(gg)), ff, dd, Laws.err_find(spec, at, name)), + Equal.trans(String, + S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(dd, + name) ++ "'" ++ S.err.values(dd)), + S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ D1.named(dd, + name) ++ "'" ++ S.err.values(dd)), + S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ D1.named(dd, + name) ++ "'" ++ Laws.values_line(dd)), + Equal.cong(String, String, xx => S.err.wrap(spec, at, + "error: invalid value '" ++ value ++ "' for '" ++ xx ++ "'" ++ + S.err.values(dd)), S.err.named(dd, name), D1.named(dd, name), en.named(dd, name)), + Equal.cong(String, String, xx => S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ + D1.named(dd, name) ++ "'" ++ xx), S.err.values(dd), Laws.values_line(dd), en.values(dd)))) diff --git a/proofs/ErrTextShowsSuggestion.bend b/proofs/ErrTextShowsSuggestion.bend new file mode 100644 index 0000000..6232a1f --- /dev/null +++ b/proofs/ErrTextShowsSuggestion.bend @@ -0,0 +1,144 @@ +# proofs of laws/ErrTextShowsSuggestion.bend +import Base +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../laws/ErrTextShowsSuggestion.bend as Laws +import ../laws/PrefixEmpty.bend as L1 +import ./lemmas/WalkSteps.bend as M1 + +# that `qq` contains `ss` +def ct.has(qq: String, ss: String) -> Type: + {String.contains(qq, ss) == True{} : Bool} + +# appending is associative +def ct.assoc(aa: String, +bb: String, +cc: String) -> {(aa ++ bb) ++ cc == aa ++ (bb ++ cc) : String}: + match aa: + case SNil{}: + {==} + case SCon{+hh, tt}: + Equal.cong(String, String, ys => SCon{hh, ys}, (tt ++ bb) ++ cc, tt ++ (bb ++ cc), ct.assoc(tt, bb, cc)) + +# a string starts with itself, whatever follows +def ct.starts(ss: String, +post: String) -> {String.starts_with(ss ++ post, ss) == True{} : Bool}: + match ss: + case SNil{}: + L1.prefix_empty(post) + case SCon{+hh, tt}: + e1 = Equal.sym(Bool, Char.is_eq(hh, hh), True{}, Eq.char_eq_self(hh)) + %e1 : {String.starts_with.go.if(tt, tt ++ post, _) == True{} : Bool} + ct.starts(tt, post) + +# every string contains the empty one +def ct.nil(post: String) -> ct.has(post, ""): + match post: + case SNil{}: + {==} + case SCon{_h, _t}: + {==} + +# a string contains itself, whatever follows +def ct.front(ss: String, +post: String) -> ct.has(ss ++ post, ss): + match ss: + case SNil{}: + ct.nil(post) + case SCon{+hh, +tt}: + e1 = Equal.sym(Bool, String.starts_with(SCon{hh, tt ++ post}, SCon{hh, tt}), True{}, + ct.starts(SCon{hh, tt}, post)) + %e1 : {String.contains.if(tt ++ post, SCon{hh, tt}, _) == True{} : Bool} + {==} + +# a contained string stays contained, here or further on +def ct.if( + tt: String, + +ss: String, + here: Bool, + hh: ct.has(tt, ss) +) -> {String.contains.if(tt, ss, here) == True{} : Bool}: + match here: + case False{}: + hh + case True{}: + {==} + +# what a string contains, it contains after anything +def ct.after(pre: String, +qq: String, +ss: String, hh: ct.has(qq, ss)) -> ct.has(pre ++ qq, ss): + match pre: + case SNil{}: + hh + case SCon{+ch, +tt}: + ct.if(tt ++ qq, ss, String.starts_with(SCon{ch, tt ++ qq}, ss), ct.after(tt, qq, ss, hh)) + +# a string contained in the part after `pp` is contained in the whole +def ct.step( + +pp: String, + +qq: String, + +rr: String, + +ss: String, + hh: ct.has(qq ++ rr, ss) +) -> ct.has((pp ++ qq) ++ rr, ss): + e1 = Equal.sym(String, (pp ++ qq) ++ rr, pp ++ (qq ++ rr), ct.assoc(pp, qq, rr)) + %e1 : {String.contains(_, ss) == True{} : Bool} + ct.after(pp, qq ++ rr, ss, hh) + +# a string is contained where it leads +def ct.lead(+ss: String, +qq: String, +rr: String) -> ct.has((ss ++ qq) ++ rr, ss): + e1 = Equal.sym(String, (ss ++ qq) ++ rr, ss ++ (qq ++ rr), ct.assoc(ss, qq, rr)) + %e1 : {String.contains(_, ss) == True{} : Bool} + ct.front(ss, qq ++ rr) + +# the type that is empty exactly where a Maybe is Some +def mb.emp(mm: Maybe<&2, String>) -> Type: + match mm: + case None{}: + Unit + case Some{_s}: + Empty + +# that None is Some +def mb.none_some(ss: String) -> Type: + {None{} == Some{ss} : Maybe<&2, String>} + +# None is not Some +def mb.clash(+ss: String, eq: mb.none_some(ss)) -> Empty: + %eq : mb.emp(_) + Unit{} + +# an error with no suggestion proves anything about its text +def tp.none( + +spec: S.Cli, + +err: S.ParseErr, + +ss: String, + eq: mb.none_some(ss) +) -> ct.has(S.err_text(spec, err), ss): + Empty.absurd(ct.has(S.err_text(spec, err), ss), mb.clash(ss, eq)) + +def Laws.err_text_tip(spec, err, ss, h_sug): + match err: + case S.UnknownFlag{+at, +word}: + e1 = Equal.sym(Maybe<&2, String>, S.sug.word(spec, at, word), Some{ss}, h_sug) + %e1 : {String.contains(S.err.wrap(spec, at, "error: unexpected argument '" ++ word ++ "' found" ++ S.sug.tip(_)), + ss) == True{} : Bool} + ct.step("error: unexpected argument '", + word ++ "' found" ++ "\n\n tip: a similar argument exists: '" ++ ss ++ "'", + "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, + ct.step(word, "' found" ++ "\n\n tip: a similar argument exists: '" ++ ss ++ "'", + "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, + ct.step("' found", "\n\n tip: a similar argument exists: '" ++ ss ++ "'", + "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, + ct.step("\n\n tip: a similar argument exists: '", ss ++ "'", + "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, + ct.lead(ss, "'", "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at)))))) + case S.Missing{+at, +name}: + tp.none(spec, S.Missing{at, name}, ss, h_sug) + case S.NoValue{+at, +name}: + tp.none(spec, S.NoValue{at, name}, ss, h_sug) + case S.BadValue{+at, +name, +value}: + tp.none(spec, S.BadValue{at, name, value}, ss, h_sug) + case S.NeedHelp{+path}: + tp.none(spec, S.NeedHelp{path}, ss, h_sug) + case S.Unexpected{+at, +arg}: + tp.none(spec, S.Unexpected{at, arg}, ss, h_sug) + case S.Repeated{+at, +name}: + tp.none(spec, S.Repeated{at, name}, ss, h_sug) + case S.NeedVersion{}: + tp.none(spec, S.NeedVersion{}, ss, h_sug) diff --git a/proofs/ErrTextShowsUsage.bend b/proofs/ErrTextShowsUsage.bend new file mode 100644 index 0000000..967e65e --- /dev/null +++ b/proofs/ErrTextShowsUsage.bend @@ -0,0 +1,90 @@ +# proofs of laws/ErrTextShowsUsage.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/ErrTextShowsUsage.bend as Laws +import ../laws/defs/HelpPages.bend as D1 + +# the usage walk reads the usage line of the page it reaches +def hl.usage( + path: List<&2, String>, + how: S.How +) -> {S.usage.go(path, how) == D1.usage_line(D1.reach(path, how)) : String}: + match path: + case Nil{}: + match how: + case S.How{_b, _a, _v, _g, _s, _u}: + {==} + case Con{+hh, tt}: + hl.usage(tt, S.help.step(how, hh)) + +def Laws.err_path_at(_at, _word, _name, _value): + {==} + +# the usage line an error's text shows is the one its command's page shows +def et.usage( + spec: Shake.Cli, + +path: List<&2, String> +) -> {S.usage.at(spec, path) == D1.usage_line(D1.reach(path, D1.root(spec))) : String}: + match spec: + case S.Cli{+name, +about, +version, +args, +subs}: + hl.usage(path, S.How{name, about, version, args, subs, name}) + +# an error's text, with its usage line read as its page's +def et.wrap( + +spec: Shake.Cli, + +at: List<&2, String>, + +msg: String +) -> {S.err.wrap(spec, at, msg) + == msg ++ "\n\n" ++ D1.usage_line(D1.reach(at, D1.root(spec))) ++ "\n\n" ++ S.err.hint(at) : String}: + Equal.cong(String, String, uu => msg ++ "\n\n" ++ uu ++ "\n\n" ++ S.err.hint(at), S.usage.at(spec, at), + D1.usage_line(D1.reach(at, D1.root(spec))), et.usage(spec, at)) + +# what `err_text_usage` claims of an error +def et.ty(+spec: Shake.Cli, +err: Shake.ParseErr) -> Type: + &msg: String -> {Shake.err_text(spec, err) + == msg ++ "\n\n" ++ D1.usage_line(D1.reach(Shake.err_path(err), D1.root(spec))) ++ "\n\n" ++ + S.err.hint(Shake.err_path(err)) : String} + +# Unit for None, Empty for Some: a motive that tells them apart +def et.none_ty(mm: Maybe<&2, List<&2, String>>) -> Type: + match mm: + case None{}: + Unit + case Some{_p}: + Empty + +# a request for help has a help path +def et.some_none(+path: List<&2, String>, hh: {Some{path} == None{} : Maybe<&2, List<&2, String>>}) -> Empty: + ee = Equal.sym(Maybe<&2, List<&2, String>>, Some{path}, None{}, hh) + %ee : et.none_ty(_) + Unit{} + +def Laws.err_text_usage(spec, err, h_err, h_ver): + match err: + case S.UnknownFlag{+at, +word}: + ("error: unexpected argument '" ++ word ++ "' found" ++ S.sug.tip(S.sug.word(spec, at, word)), + et.wrap(spec, at, "error: unexpected argument '" ++ word ++ "' found" ++ S.sug.tip(S.sug.word(spec, at, word)))) + case S.Missing{+at, +name}: + ("error: the following required argument was not provided:\n " ++ S.err.name(spec, at, name), + et.wrap(spec, at, "error: the following required argument was not provided:\n " ++ S.err.name(spec, at, name))) + case S.NoValue{+at, +name}: + ("error: a value is required for '" ++ S.err.name(spec, at, name) ++ "' but none was supplied", + et.wrap(spec, at, "error: a value is required for '" ++ S.err.name(spec, at, + name) ++ "' but none was supplied")) + case S.BadValue{+at, +name, +value}: + ("error: invalid value '" ++ value ++ "' for '" ++ S.err.name(spec, at, name) ++ "'" ++ + S.err.values(S.err.find(spec, at, name)), + et.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.name(spec, at, name) ++ "'" ++ + S.err.values(S.err.find(spec, at, name)))) + case S.NeedHelp{+path}: + Empty.absurd(et.ty(spec, S.NeedHelp{path}), et.some_none(path, h_err)) + case S.NeedVersion{}: + Empty.absurd(et.ty(spec, S.NeedVersion{}), Walk.wk.true_false(h_ver)) + case S.Unexpected{+at, +arg}: + ("error: unexpected argument '" ++ arg ++ "' found", + et.wrap(spec, at, "error: unexpected argument '" ++ arg ++ "' found")) + case S.Repeated{+at, +name}: + ("error: the argument '" ++ S.err.name(spec, at, name) ++ "' cannot be used multiple times", + et.wrap(spec, at, "error: the argument '" ++ S.err.name(spec, at, name) ++ "' cannot be used multiple times")) diff --git a/proofs/FailFrom.bend b/proofs/FailFrom.bend new file mode 100644 index 0000000..1caa8d1 --- /dev/null +++ b/proofs/FailFrom.bend @@ -0,0 +1,6 @@ +# proofs of laws/FailFrom.bend +import Base +import ../laws/FailFrom.bend as Laws +import ./lemmas/FailedWalkThrough.bend as M1 + + diff --git a/proofs/FailureIsFinal.bend b/proofs/FailureIsFinal.bend new file mode 100644 index 0000000..c81ae33 --- /dev/null +++ b/proofs/FailureIsFinal.bend @@ -0,0 +1,20 @@ +# proofs of laws/FailureIsFinal.bend +import Base +import ../src/cli.bend as S +import ../laws/FailFrom.bend as L1 +import ../laws/FailureIsFinal.bend as Laws +import ./lemmas/FailedWalkThrough.bend as M1 + +def Laws.fail_stays(spec, ws, more, err, died): + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, + S.version.res(spec, S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec)))), + S.version.res(spec, Fail{err}), + S.version.res(spec, S.parse.finish(S.parse.walk(ws, S.parse.start(spec)))), + Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, + xx => S.version.res(spec, xx), + S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec))), + Fail{err}, M1.fs.raw(spec, ws, more, err, died)), + Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, + xx => S.version.res(spec, xx), Fail{err}, S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), + Equal.sym(Result<&2, &2, S.ParseErr, S.Matched>, S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), Fail{err}, + L1.fail_from(S.parse.walk(ws, S.parse.start(spec)), [], err, died)))) diff --git a/proofs/FlagLongValued.bend b/proofs/FlagLongValued.bend new file mode 100644 index 0000000..f5a01ae --- /dev/null +++ b/proofs/FlagLongValued.bend @@ -0,0 +1,82 @@ +# proofs of laws/FlagLongValued.bend +import Base +import ../src/cli.bend as S +import ../laws/FlagLongValued.bend as Laws +import ../laws/LongArgStep.bend as L1 +import ../laws/Refused.bend as L2 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +# a long option that gives a flag a value fails the step +def flag_long_valued.step( + +body: String, + +name: String, + +vv: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, + h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>}, + h_name: {String.is_empty(name) == False{} : Bool}, + h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, + h_once: {S.bound(bs, nn) == False{} : Bool} +) -> {D1.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen})) + == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), + S.parse.take_long.found(S.by_long(args, name), name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen), + L1.long_arg_step(body, name, Some{vv}, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) + %e1 : {D1.dead_of(_) == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}}, h_found) + %e2 : {D1.dead_of(S.parse.take_long.found(_, name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen)) + == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, h_once) + %e3 : {D1.dead_of(S.parse.once(_, S.St{S.Dead{S.Unexpected{path, "--" ++ body}}, args, up, subs, path, bs, pos, + False{}, seen}, nn, path)) == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.flag_long_valued( + spec, + pre, + more, + body, + name, + vv, + args, + up, + subs, + path, + bs, + pos, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_at, + h_dd, + h_cut, + h_name, + h_found, + h_once +): + L2.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + S.Unexpected{path, "--" ++ body}, h_at, + flag_long_valued.step(body, name, vv, args, up, subs, path, bs, pos, seen, nn, sh, lo, hp, req, dflt, cs, + h_dd, h_cut, h_name, h_found, h_once)) diff --git a/proofs/GetNil.bend b/proofs/GetNil.bend new file mode 100644 index 0000000..03e39ee --- /dev/null +++ b/proofs/GetNil.bend @@ -0,0 +1,6 @@ +# proofs of laws/GetNil.bend +import Base +import ../laws/GetNil.bend as Laws + +def Laws.get_nil(_name): + {==} diff --git a/proofs/GetReadsOwnBindings.bend b/proofs/GetReadsOwnBindings.bend new file mode 100644 index 0000000..5a48e78 --- /dev/null +++ b/proofs/GetReadsOwnBindings.bend @@ -0,0 +1,59 @@ +# proofs of laws/GetReadsOwnBindings.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../laws/BindValuesAppend.bend as L1 +import ../laws/GetReadsOwnBindings.bend as Laws +import ../laws/LastSnoc.bend as L2 +import ./lemmas/AppendedBindings.bend as M1 +import ./lemmas/LastOfSnoc.bend as M2 + +def Laws.get_all_append(bs, cs, name): + L1.bind_values_append(bs, cs, name) + +def Laws.get_all_hit(name, vv): + ee = Equal.sym(Bool, String.eq(name, name), True{}, Eq.string_eq_self(name)) + %ee : {S.get_all.bind.hit(_, vv, []) == [vv] : List<&2, String>} + {==} + +def Laws.get_all_miss(other, name, vv, differ): + ee = Equal.sym(Bool, String.eq(other, name), False{}, differ) + %ee : {S.get_all.bind.hit(_, vv, []) == [] : List<&2, String>} + {==} + +def Laws.get_last(bs, cs, name, vv, after): + e1 = Equal.sym(List<&2, String>, + S.get_all.bind(List.append(&2, S.Bind, bs, S.Bind{name, vv} <> cs), name), + List.append(&2, String, S.get_all.bind(bs, name), S.get_all.bind(S.Bind{name, vv} <> cs, name)), + L1.bind_values_append(bs, S.Bind{name, vv} <> cs, name)) + %e1 : {S.get.last(_, None{}) == Some{vv} : Maybe<&2, String>} + e2 = Equal.sym(Bool, String.eq(name, name), True{}, Eq.string_eq_self(name)) + %e2 : {S.get.last(List.append(&2, String, S.get_all.bind(bs, name), + S.get_all.bind.hit(_, vv, S.get_all.bind(cs, name))), None{}) == Some{vv} + : Maybe<&2, String>} + e3 = Equal.sym(List<&2, String>, S.get_all.bind(cs, name), [], after) + %e3 : {S.get.last(List.append(&2, String, S.get_all.bind(bs, name), vv <> _), None{}) + == Some{vv} : Maybe<&2, String>} + L2.last_snoc(S.get_all.bind(bs, name), vv, None{}) + +def Laws.get_none(bs, name, none): + ee = Equal.sym(List<&2, String>, S.get_all.bind(bs, name), [], none) + %ee : {S.get.last(_, None{}) == None{} : Maybe<&2, String>} + {==} + +def Laws.on_some(found, name, vv, h_get): + ee = Equal.sym(Maybe<&2, String>, Shake.get(found, name), Some{vv}, h_get) + %ee : {S.on.of(_) == String.eq(vv, "true") : Bool} + {==} + +def Laws.on_none(found, name, h_get): + ee = Equal.sym(Maybe<&2, String>, Shake.get(found, name), None{}, h_get) + %ee : {S.on.of(_) == False{} : Bool} + {==} + +def Laws.get_all_node(_bs, _nn, _below, _name): + {==} + +def Laws.get_node(_bs, _nn, _below, _name): + {==} diff --git a/proofs/HelpFollowsPath.bend b/proofs/HelpFollowsPath.bend new file mode 100644 index 0000000..de508b6 --- /dev/null +++ b/proofs/HelpFollowsPath.bend @@ -0,0 +1,17 @@ +# proofs of laws/HelpFollowsPath.bend +import Base +import ../src/cli.bend as S +import ../laws/HelpFollowsPath.bend as Laws + +def Laws.reach_child(bin, about, version, args, subs, usage, name, sn, sa, sargs, ssubs, h_sub): + ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, name), Some{S.Sub{sn, sa, sargs, ssubs}}, h_sub) + %ee : {S.help.step.go(_, bin, about, version, args, subs, name, usage) + == S.How{sn, sa, None{}, sargs, ssubs, usage ++ " " ++ name} : S.How} + {==} + +def Laws.reach_skip(bin, about, version, args, subs, usage, name, h_sub): + ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, name), None{}, h_sub) + %ee : {S.help.step.go(_, bin, about, version, args, subs, name, usage) == S.How{bin, about, version, args, + subs, usage} + : S.How} + {==} diff --git a/proofs/HelpListsEveryArgument.bend b/proofs/HelpListsEveryArgument.bend new file mode 100644 index 0000000..8446edb --- /dev/null +++ b/proofs/HelpListsEveryArgument.bend @@ -0,0 +1,144 @@ +# proofs of laws/HelpListsEveryArgument.bend +import Base +import ../src/cli.bend as S +import ../laws/HelpListsEveryArgument.bend as Laws +import ./lemmas/StringAppendEmpty.bend as M1 + +# the Commands lines are the subcommands' lines, then `help`'s +def hs.cmds( + +subs: List<&2, S.Sub>, + +ww: Nat +) -> {S.help.cmd_lines(subs, ww) == Laws.sub_lines(List.append(&2, S.Sub, subs, [Laws.help_sub()]), ww) : String}: + match subs: + case Nil{}: + Equal.sym(String, S.help.cmd_line("help", "Print this message or the help of the given subcommand(s)", ww) ++ "", + S.help.cmd_line("help", "Print this message or the help of the given subcommand(s)", ww), + M1.hs.nil(S.help.cmd_line("help", "Print this message or the help of the given subcommand(s)", ww))) + case Con{+hh, +tt}: + Equal.cong(String, String, xs => S.help.cmd_line(S.help.sub_name(hh), S.help.sub_about(hh), ww) ++ xs, + S.help.cmd_lines(tt, ww), Laws.sub_lines(List.append(&2, S.Sub, tt, [Laws.help_sub()]), ww), hs.cmds(tt, ww)) + +def Laws.cmds_listed(subs): + Equal.cong(String, String, xs => "Commands:\n" ++ xs ++ "\n", S.help.cmd_lines(subs, S.help.cmd_width(subs, 0n)), + Laws.sub_lines(List.append(&2, S.Sub, subs, [Laws.help_sub()]), S.help.cmd_width(subs, 0n)), + hs.cmds(subs, S.help.cmd_width(subs, 0n))) + +# an option's line, then the lines after it +def hs.opt_next( + +aa: S.Arg, + +tt: List<&2, S.Arg>, + +ww: Nat, + ee: {S.help.opt_lines(tt, ww) == Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, tt), ww) : String} +) -> {S.help.opt_line(aa, ww) ++ "\n" ++ S.help.opt_lines(tt, ww) + == S.help.opt_line(aa, ww) ++ "\n" ++ Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, tt), ww) : String}: + Equal.cong(String, String, xs => S.help.opt_line(aa, ww) ++ "\n" ++ xs, S.help.opt_lines(tt, ww), + Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, tt), ww), ee) + +# the Options lines are the lines of the flags and options, in order +def hs.opts( + +args: List<&2, S.Arg>, + +ww: Nat +) -> {S.help.opt_lines(args, ww) == Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, args), ww) : String}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+n, +s, +l, kind, +h, +r, +d, +c}: + match kind: + case S.Flag{}: + hs.opt_next(S.Arg{n, s, l, S.Flag{}, h, r, d, c}, tt, ww, hs.opts(tt, ww)) + case S.Opt{}: + hs.opt_next(S.Arg{n, s, l, S.Opt{}, h, r, d, c}, tt, ww, hs.opts(tt, ww)) + case S.Many{}: + hs.opt_next(S.Arg{n, s, l, S.Many{}, h, r, d, c}, tt, ww, hs.opts(tt, ww)) + case S.Pos{}: + hs.opts(tt, ww) + case S.Rest{}: + hs.opts(tt, ww) + +def Laws.opts_listed(args): + Equal.cong(String, String, xs => S.help.opts.go(xs), S.help.opt_lines(args, S.help.opt_width(args, 0n)), + Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, args), S.help.opt_width(args, 0n)), + hs.opts(args, S.help.opt_width(args, 0n))) + +# a positional's hint, then the hints after it +def hs.pos_next( + +aa: S.Arg, + +tt: List<&2, S.Arg>, + ee: {S.help.pos(tt) == Laws.marks(List.filter(~S.Arg, ~S.pos.is, tt)) : String} +) -> {S.help.pos.one(aa) ++ S.help.pos(tt) == S.help.pos.one(aa) ++ Laws.marks(List.filter(~S.Arg, ~S.pos.is, tt)) + : String}: + Equal.cong(String, String, xs => S.help.pos.one(aa) ++ xs, S.help.pos(tt), + Laws.marks(List.filter(~S.Arg, ~S.pos.is, tt)), ee) + +# the usage hints are the marks of the positionals, in order +def hs.pos(+args: List<&2, S.Arg>) -> {S.help.pos(args) == Laws.marks(List.filter(~S.Arg, ~S.pos.is, args)) : String}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+n, +s, +l, kind, +h, r, +d, +c}: + match kind: + case S.Flag{}: + hs.pos(tt) + case S.Opt{}: + hs.pos(tt) + case S.Many{}: + hs.pos(tt) + case S.Pos{}: + match r: + case True{}: + hs.pos_next(S.Arg{n, s, l, S.Pos{}, h, True{}, d, c}, tt, hs.pos(tt)) + case False{}: + hs.pos_next(S.Arg{n, s, l, S.Pos{}, h, False{}, d, c}, tt, hs.pos(tt)) + case S.Rest{}: + match r: + case True{}: + hs.pos_next(S.Arg{n, s, l, S.Rest{}, h, True{}, d, c}, tt, hs.pos(tt)) + case False{}: + hs.pos_next(S.Arg{n, s, l, S.Rest{}, h, False{}, d, c}, tt, hs.pos(tt)) + +def Laws.usage_listed(name, args, subs): + Equal.cong(String, String, xs => "Usage: " ++ name ++ S.help.opts_hint(args) ++ xs ++ S.help.cmd_hint(subs), + S.help.pos(args), Laws.marks(List.filter(~S.Arg, ~S.pos.is, args)), hs.pos(args)) + +# a positional's Arguments line, then the lines after it +def ha.next( + +aa: S.Arg, + +tt: List<&2, S.Arg>, + +ww: Nat, + ee: {S.help.arg_lines(tt, ww) == Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, tt), ww) : String} +) -> {S.help.arg_line(aa, ww) ++ "\n" ++ S.help.arg_lines(tt, ww) + == S.help.arg_line(aa, ww) ++ "\n" ++ Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, tt), ww) : String}: + Equal.cong(String, String, xs => S.help.arg_line(aa, ww) ++ "\n" ++ xs, S.help.arg_lines(tt, ww), + Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, tt), ww), ee) + +# the Arguments lines are the lines of the positionals, in order +def ha.lines( + +args: List<&2, S.Arg>, + +ww: Nat +) -> {S.help.arg_lines(args, ww) == Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, args), ww) : String}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+n, +s, +l, kind, +h, +r, +d, +c}: + match kind: + case S.Flag{}: + ha.lines(tt, ww) + case S.Opt{}: + ha.lines(tt, ww) + case S.Many{}: + ha.lines(tt, ww) + case S.Pos{}: + ha.next(S.Arg{n, s, l, S.Pos{}, h, r, d, c}, tt, ww, ha.lines(tt, ww)) + case S.Rest{}: + ha.next(S.Arg{n, s, l, S.Rest{}, h, r, d, c}, tt, ww, ha.lines(tt, ww)) + +def Laws.args_listed(args): + Equal.cong(String, String, xs => S.help.args.go(xs), S.help.arg_lines(args, S.help.arg_width(S.pos_of(args), 0n)), + Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, args), S.help.arg_width(S.pos_of(args), 0n)), + ha.lines(args, S.help.arg_width(S.pos_of(args), 0n))) diff --git a/proofs/HelpPage.bend b/proofs/HelpPage.bend new file mode 100644 index 0000000..1b94217 --- /dev/null +++ b/proofs/HelpPage.bend @@ -0,0 +1,23 @@ +# proofs of laws/HelpPage.bend +import Base +import ../src/cli.bend as S +import ../laws/HelpPage.bend as Laws +import ../laws/defs/HelpPages.bend as D1 + +# the help walk renders the page it reaches +def hl.go( + path: List<&2, String>, + how: S.How +) -> {S.help.go(path, how) == D1.page(D1.reach(path, how)) : String}: + match path: + case Nil{}: + match how: + case S.How{_b, _a, _v, _g, _s, _u}: + {==} + case Con{+hh, tt}: + hl.go(tt, S.help.step(how, hh)) + +def Laws.help_page(spec, path): + match spec: + case S.Cli{+name, +about, +version, +args, +subs}: + hl.go(path, S.How{name, about, version, args, subs, name}) diff --git a/proofs/HelpShowsEnvVar.bend b/proofs/HelpShowsEnvVar.bend new file mode 100644 index 0000000..4caa924 --- /dev/null +++ b/proofs/HelpShowsEnvVar.bend @@ -0,0 +1,9 @@ +# proofs of laws/HelpShowsEnvVar.bend +import Base +import ../laws/HelpShowsEnvVar.bend as Laws + +def Laws.help_env_shown(_nn, _sh, _lo, _kind, _hp, _req, _var, _dd, _cs): + {==} + +def Laws.help_env_none(_nn, _sh, _lo, _kind, _hp, _req, _dd, _cs): + {==} diff --git a/proofs/HelpUnknown.bend b/proofs/HelpUnknown.bend new file mode 100644 index 0000000..7338424 --- /dev/null +++ b/proofs/HelpUnknown.bend @@ -0,0 +1,46 @@ +# proofs of laws/HelpUnknown.bend +import Base +import ../src/cli.bend as S +import ../laws/HelpUnknown.bend as Laws +import ../laws/Refused.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 + +# a word after `help` that names no subcommand where the path stands fails +# the step +def help_unknown.step( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} +) -> {D1.dead_of(S.parse.step(tok, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen})) + == Some{S.Unexpected{path, tok}} : Maybe<&2, S.ParseErr>}: + ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, tok), None{}, h_sub) + %ee : {D1.dead_of(S.parse.step.help_path(_, tok, args, up, subs, path, bs, pos, raw, seen)) + == Some{S.Unexpected{path, tok}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.help_unknown( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + h_at, + h_sub +): + L1.refused(spec, pre, more, tok, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}, + S.Unexpected{path, tok}, h_at, help_unknown.step(tok, args, up, subs, path, bs, pos, raw, seen, h_sub)) diff --git a/proofs/HelpWalk.bend b/proofs/HelpWalk.bend new file mode 100644 index 0000000..b303abb --- /dev/null +++ b/proofs/HelpWalk.bend @@ -0,0 +1,6 @@ +# proofs of laws/HelpWalk.bend +import Base +import ../laws/HelpWalk.bend as Laws +import ./lemmas/HelpPathWalk.bend as M1 + + diff --git a/proofs/HelpWordAsksForHelp.bend b/proofs/HelpWordAsksForHelp.bend new file mode 100644 index 0000000..60c38d4 --- /dev/null +++ b/proofs/HelpWordAsksForHelp.bend @@ -0,0 +1,112 @@ +# proofs of laws/HelpWordAsksForHelp.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../laws/HelpWalk.bend as L1 +import ../laws/HelpWordAsksForHelp.bend as Laws +import ./lemmas/HelpPathWalk.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +def Laws.help_step(spec, pre, args, up, subs, path, bs, pos, h_at): + M2.kept.stepped(spec, pre, "help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}, h_at) + +# the words leave the walker reading the help path `path` +def hp.ended( + +spec: Shake.Cli, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg> +) -> Type: + &kids: List<&2, S.Sub> -> {S.parse.walk(ws, S.parse.start(spec)) + == S.St{S.Help{}, args, up, kids, path, bs, pos, False{}, False{}} : S.St} + +# a help walk that ends reading its path fails with NeedHelp of it +def hp.need( + +spec: Shake.Cli, + +ws: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + ww: hp.ended(spec, ws, args, up, path, bs, pos) +) -> {Shake.parse(spec, ws) == Fail{S.NeedHelp{path}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}: + (k1, e1) = ww + Equal.cong(S.St, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.version.res(spec, S.parse.finish(xx)), + S.parse.walk(ws, S.parse.start(spec)), + S.St{S.Help{}, args, up, k1, path, bs, pos, False{}, False{}}, e1) + +def Laws.help_path(spec, pre, ns, args, up, subs, path, bs, pos, h_at, h_ns): + +ph = List.append(&2, String, pre, ["help"]) + hp.need(spec, List.append(&2, String, ph, ns), args, up, List.append(&2, String, path, ns), bs, pos, + L1.help_walk(spec, ph, ns, args, up, subs, path, bs, pos, False{}, False{}, + Laws.help_step(spec, pre, args, up, subs, path, bs, pos, h_at), h_ns)) + +# a free walker with no long `help` reads `--help` as `help` +def dash_help.step( + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + h_none: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} +) -> {S.parse.step("--help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St}: + ee = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, "help"), None{}, h_none) + %ee : {S.parse.take_long.found(_, "help", None{}, "--help", args, up, subs, path, bs, pos, False{}, False{}) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + {==} + +def Laws.dash_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_none): + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ["--help"]), S.parse.start(spec)), + S.parse.step("--help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}), + S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}}, + M2.kept.stepped(spec, pre, "--help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}, h_at), + dash_help.step(args, up, subs, path, bs, pos, h_none)) + +def Laws.dash_help_path(spec, pre, ns, args, up, subs, path, bs, pos, h_at, h_none, h_ns): + +ph = List.append(&2, String, pre, ["--help"]) + hp.need(spec, List.append(&2, String, ph, ns), args, up, List.append(&2, String, path, ns), bs, pos, + L1.help_walk(spec, ph, ns, args, up, subs, path, bs, pos, False{}, False{}, + Laws.dash_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_none), h_ns)) + +# a free walker whose command spells neither `-h` nor `--help` reads `-h` as +# `help` +def short_help.step( + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + h_short: {S.by_short(args, "h") == None{} : Maybe<&2, S.Arg>}, + h_long: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} +) -> {S.parse.step("-h", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St}: + e1 = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, "h"), None{}, h_short) + %e1 : {S.parse.take_short.go(Bool.and(True{}, Bool.and(True{}, Bool.and(Maybe.is_none(&2, S.Arg, _), + Maybe.is_none(&2, S.Arg, S.by_long(args, "help"))))), "h", "-h", args, up, subs, path, bs, pos, False{}, + False{}) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, "help"), None{}, h_long) + %e2 : {S.parse.take_short.go(Bool.and(True{}, Bool.and(True{}, Bool.and(True{}, Maybe.is_none(&2, S.Arg, _)))), + "h", "-h", args, up, subs, path, bs, pos, False{}, False{}) + == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} + {==} + +def Laws.short_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_short, h_long): + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ["-h"]), S.parse.start(spec)), + S.parse.step("-h", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}), + S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}}, + M2.kept.stepped(spec, pre, "-h", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}, h_at), + short_help.step(args, up, subs, path, bs, pos, h_short, h_long)) + +def Laws.short_help_path(spec, pre, ns, args, up, subs, path, bs, pos, h_at, h_short, h_long, h_ns): + +ph = List.append(&2, String, pre, ["-h"]) + hp.need(spec, List.append(&2, String, ph, ns), args, up, List.append(&2, String, path, ns), bs, pos, + L1.help_walk(spec, ph, ns, args, up, subs, path, bs, pos, False{}, False{}, + Laws.short_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_short, h_long), h_ns)) diff --git a/proofs/HitAppend.bend b/proofs/HitAppend.bend new file mode 100644 index 0000000..6a7b454 --- /dev/null +++ b/proofs/HitAppend.bend @@ -0,0 +1,6 @@ +# proofs of laws/HitAppend.bend +import Base +import ../laws/HitAppend.bend as Laws +import ./lemmas/AppendedBindings.bend as M1 + + diff --git a/proofs/Kept.bend b/proofs/Kept.bend new file mode 100644 index 0000000..5e33368 --- /dev/null +++ b/proofs/Kept.bend @@ -0,0 +1,6 @@ +# proofs of laws/Kept.bend +import Base +import ../laws/Kept.bend as Laws +import ./lemmas/BindingsKept.bend as M1 + + diff --git a/proofs/KeptAll.bend b/proofs/KeptAll.bend new file mode 100644 index 0000000..ba433a4 --- /dev/null +++ b/proofs/KeptAll.bend @@ -0,0 +1,6 @@ +# proofs of laws/KeptAll.bend +import Base +import ../laws/KeptAll.bend as Laws +import ./lemmas/BindingsKept.bend as M1 + + diff --git a/proofs/KeptSet.bend b/proofs/KeptSet.bend new file mode 100644 index 0000000..e21f881 --- /dev/null +++ b/proofs/KeptSet.bend @@ -0,0 +1,6 @@ +# proofs of laws/KeptSet.bend +import Base +import ../laws/KeptSet.bend as Laws + +def Laws.kept_set(_nn, _vv, _rest): + {==} diff --git a/proofs/KeptUnset.bend b/proofs/KeptUnset.bend new file mode 100644 index 0000000..2a3733e --- /dev/null +++ b/proofs/KeptUnset.bend @@ -0,0 +1,6 @@ +# proofs of laws/KeptUnset.bend +import Base +import ../laws/KeptUnset.bend as Laws + +def Laws.kept_unset(_nn, _ee, _rest): + {==} diff --git a/proofs/LastSnoc.bend b/proofs/LastSnoc.bend new file mode 100644 index 0000000..eb5c004 --- /dev/null +++ b/proofs/LastSnoc.bend @@ -0,0 +1,6 @@ +# proofs of laws/LastSnoc.bend +import Base +import ../laws/LastSnoc.bend as Laws +import ./lemmas/LastOfSnoc.bend as M1 + + diff --git a/proofs/LetterFlagAgain.bend b/proofs/LetterFlagAgain.bend new file mode 100644 index 0000000..7f4e788 --- /dev/null +++ b/proofs/LetterFlagAgain.bend @@ -0,0 +1,44 @@ +# proofs of laws/LetterFlagAgain.bend +import Base +import ../src/cli.bend as S +import ../laws/LetterFlagAgain.bend as Laws +import ../laws/OnceDead.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/ClusterSteps.bend as M1 +import ./lemmas/FailedWalkThrough.bend as M2 +import ./lemmas/FailedWalkerStays.bend as M3 + +def Laws.letter_flag_again( + cc, + rest, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_found, + h_again +): + +aa = {S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs} : S.Arg} + +vv = S.cluster.val.flag(String.starts_with(rest, "="), rest) + e0 = M1.cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {D1.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) + %e2 : {D1.dead_of(S.cluster(rest, orig, S.parse.once(_, S.parse.take_flag(vv, nn, orig, args, up, subs, + path, bs, pos, + raw, seen), nn, path), String.starts_with(rest, "="))) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + M2.cl.dead(rest, orig, S.parse.once(True{}, S.parse.take_flag(vv, nn, orig, args, up, subs, path, bs, pos, raw, + seen), nn, + path), String.starts_with(rest, "="), S.Repeated{path, nn}, + L1.once_dead(S.parse.take_flag(vv, nn, orig, args, up, subs, path, bs, pos, raw, seen), nn, path)) diff --git a/proofs/LetterFlagEq.bend b/proofs/LetterFlagEq.bend new file mode 100644 index 0000000..44e3a5e --- /dev/null +++ b/proofs/LetterFlagEq.bend @@ -0,0 +1,42 @@ +# proofs of laws/LetterFlagEq.bend +import Base +import ../src/cli.bend as S +import ../laws/ClusterDone.bend as L1 +import ../laws/LetterFlagEq.bend as Laws +import ./lemmas/ClusterEnd.bend as M1 +import ./lemmas/ClusterSteps.bend as M2 + +def Laws.letter_flag_eq( + cc, + rest, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_found, + h_once, + h_eq +): + +aa = {S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs} : S.Arg} + +dd = {S.St{S.Dead{S.Unexpected{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} + e0 = M2.cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {_ == dd : S.St} + e1 = Equal.sym(Bool, String.starts_with(rest, "="), True{}, h_eq) + %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Flag{}, nn, S.cluster.val.flag(_, rest), orig, args, up, + subs, path, + bs, pos, raw, seen), _) == dd : S.St} + e2 = Equal.sym(Bool, S.bound(bs, nn), False{}, h_once) + %e2 : {S.cluster(rest, orig, S.parse.once(_, dd, nn, path), True{}) == dd : S.St} + L1.cluster_done(rest, orig, dd) diff --git a/proofs/LetterOptAgain.bend b/proofs/LetterOptAgain.bend new file mode 100644 index 0000000..631ab14 --- /dev/null +++ b/proofs/LetterOptAgain.bend @@ -0,0 +1,43 @@ +# proofs of laws/LetterOptAgain.bend +import Base +import ../src/cli.bend as S +import ../laws/LetterOptAgain.bend as Laws +import ../laws/OnceDead.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/ClusterSteps.bend as M1 +import ./lemmas/FailedWalkThrough.bend as M2 +import ./lemmas/FailedWalkerStays.bend as M3 + +def Laws.letter_opt_again( + cc, + rest, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_found, + h_again +): + +aa = {S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs} : S.Arg} + +vv = S.short_of.val(String.starts_with(rest, "="), rest) + e0 = M1.cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {D1.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) + %e2 : {D1.dead_of(S.cluster(rest, orig, S.parse.once(_, S.parse.take_opt(vv, nn, args, up, subs, path, bs, pos, raw, + seen), nn, path), True{})) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + M2.cl.dead(rest, orig, S.parse.once(True{}, S.parse.take_opt(vv, nn, args, up, subs, path, bs, pos, raw, seen), + nn, path), + True{}, S.Repeated{path, nn}, L1.once_dead(S.parse.take_opt(vv, nn, args, up, subs, path, bs, pos, raw, + seen), nn, path)) diff --git a/proofs/LetterUnknown.bend b/proofs/LetterUnknown.bend new file mode 100644 index 0000000..e2b99b4 --- /dev/null +++ b/proofs/LetterUnknown.bend @@ -0,0 +1,14 @@ +# proofs of laws/LetterUnknown.bend +import Base +import ../src/cli.bend as S +import ../laws/ClusterDone.bend as L1 +import ../laws/LetterUnknown.bend as Laws +import ./lemmas/ClusterEnd.bend as M1 + +def Laws.letter_unknown(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, h_none): + +dd = {S.St{S.Dead{S.UnknownFlag{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, SCon{cc, SNil{}}), None{}, h_none) + %e2 : {S.cluster(rest, orig, + S.parse.take_arg(_, S.cluster.val(_, rest), orig, args, up, subs, path, bs, pos, raw, seen), + S.cluster.ends(_, rest)) == dd : S.St} + L1.cluster_done(rest, orig, dd) diff --git a/proofs/LetterValue.bend b/proofs/LetterValue.bend new file mode 100644 index 0000000..671ff61 --- /dev/null +++ b/proofs/LetterValue.bend @@ -0,0 +1,123 @@ +# proofs of laws/LetterValue.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/ClusterDone.bend as L1 +import ../laws/LetterValue.bend as Laws +import ../laws/defs/ArgumentLists.bend as D1 +import ./lemmas/ClusterEnd.bend as M1 +import ./lemmas/ClusterSteps.bend as M2 + +# what `lv` claims of an argument kind +def lv.ty( + kk: S.ArgKind, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String +) -> Type: + {S.cluster(rest, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, rest), orig, args, up, subs, path, + bs, pos, raw, seen), + S.cluster.ends.kind(kk, rest)) == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, + rest} <> bs, pos, raw, seen} : S.St} + +# a letter spelling an option that takes a value now, with a glued value its +# choices allow, by the option's kind +def lv( + kk: S.ArgKind, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String, + h_kind: {D1.takes(kk, S.bound(bs, nn)) == True{} : Bool}, + h_noeq: {String.starts_with(rest, "=") == False{} : Bool}, + h_some: {String.is_empty(rest) == False{} : Bool}, + h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == True{} : Bool} +) -> lv.ty(kk, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn): + match kk: + case S.Opt{}: + +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} + e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) + %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, S.short_of.val(_, rest), orig, args, up, + subs, path, bs, pos, raw, seen), True{}) + == T : S.St} + e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) + %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, + Bool.pick(Maybe<&2, String>, _, None{}, + Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} + e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) + %e3 : {S.cluster(rest, orig, S.parse.once(_, S.parse.take_opt(Some{rest}, nn, args, up, subs, path, bs, + pos, raw, seen), nn, path), True{}) == T + : S.St} + e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), True{}, h_ok) + %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), + True{}) == T : S.St} + L1.cluster_done(rest, orig, T) + case S.Many{}: + +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} + e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) + %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, S.short_of.val(_, rest), orig, args, up, + subs, path, bs, pos, raw, seen), True{}) + == T : S.St} + e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) + %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, + Bool.pick(Maybe<&2, String>, _, None{}, + Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} + e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), True{}, h_ok) + %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), + True{}) == T : S.St} + L1.cluster_done(rest, orig, T) + case S.Flag{}: + Empty.absurd(lv.ty(S.Flag{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), + Walk.wk.false_true(h_kind)) + case S.Pos{}: + Empty.absurd(lv.ty(S.Pos{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + case S.Rest{}: + Empty.absurd(lv.ty(S.Rest{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), + Walk.wk.false_true(h_kind)) + +def Laws.letter_value( + cc, + rest, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + h_found, + h_kind, + h_noeq, + h_some, + h_ok +): + +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} + +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} + e0 = M2.cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {_ == T : S.St} + lv(kind, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind, h_noeq, h_some, h_ok) diff --git a/proofs/LetterValueBad.bend b/proofs/LetterValueBad.bend new file mode 100644 index 0000000..26f5553 --- /dev/null +++ b/proofs/LetterValueBad.bend @@ -0,0 +1,38 @@ +# proofs of laws/LetterValueBad.bend +import Base +import ../src/cli.bend as S +import ../laws/LetterValueBad.bend as Laws +import ./lemmas/ClusterEnd.bend as M1 +import ./lemmas/ClusterSteps.bend as M2 + +def Laws.letter_value_bad( + cc, + rest, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + h_found, + h_kind, + h_noeq, + h_some, + h_bad +): + +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} + +T = {S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} + e0 = M2.cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {_ == T : S.St} + M1.lb(kind, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind, h_noeq, h_some, h_bad) diff --git a/proofs/LetterValueEq.bend b/proofs/LetterValueEq.bend new file mode 100644 index 0000000..d56197d --- /dev/null +++ b/proofs/LetterValueEq.bend @@ -0,0 +1,118 @@ +# proofs of laws/LetterValueEq.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/ClusterDone.bend as L1 +import ../laws/DropZero.bend as L2 +import ../laws/LetterValueEq.bend as Laws +import ../laws/PrefixEmpty.bend as L3 +import ../laws/defs/ArgumentLists.bend as D1 +import ./lemmas/ClusterEnd.bend as M1 +import ./lemmas/ClusterSteps.bend as M2 +import ./lemmas/WalkSteps.bend as M3 + +# what `le` claims of an argument kind +def le.ty( + kk: S.ArgKind, + +vv: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String +) -> Type: + {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, "=" ++ vv), orig, args, up, + subs, path, bs, pos, raw, seen), + S.cluster.ends.kind(kk, "=" ++ vv)) == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, + vv} <> bs, pos, raw, seen} : S.St} + +# a letter spelling an option that takes a value now, then `=` and a value +def le( + kk: S.ArgKind, + +vv: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String, + h_kind: {D1.takes(kk, S.bound(bs, nn)) == True{} : Bool}, + h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} +) -> le.ty(kk, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn): + match kk: + case S.Opt{}: + +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} + ep = Equal.sym(Bool, String.starts_with(vv, ""), True{}, L3.prefix_empty(vv)) + %ep : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Opt{}, nn, S.short_of.val(_, "=" ++ vv), orig, args, up, + subs, path, bs, pos, raw, seen), True{}) == T : S.St} + ed = Equal.sym(String, String.drop(vv, 0n), vv, L2.drop_zero(vv)) + %ed : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Opt{}, nn, Some{_}, orig, args, up, subs, path, + bs, pos, raw, seen), True{}) == T + : S.St} + e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) + %e3 : {S.cluster("=" ++ vv, orig, S.parse.once(_, S.parse.take_opt(Some{vv}, nn, args, up, subs, path, bs, + pos, raw, seen), nn, path), True{}) == T + : S.St} + e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) + %e4 : {S.cluster("=" ++ vv, orig, S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, raw, + seen), True{}) == T : S.St} + L1.cluster_done("=" ++ vv, orig, T) + case S.Many{}: + +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} + ep = Equal.sym(Bool, String.starts_with(vv, ""), True{}, L3.prefix_empty(vv)) + %ep : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Many{}, nn, S.short_of.val(_, "=" ++ vv), orig, + args, up, + subs, path, bs, pos, raw, seen), True{}) == T : S.St} + ed = Equal.sym(String, String.drop(vv, 0n), vv, L2.drop_zero(vv)) + %ed : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Many{}, nn, Some{_}, orig, args, up, subs, path, + bs, pos, raw, seen), True{}) == T + : S.St} + e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) + %e4 : {S.cluster("=" ++ vv, orig, S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, raw, + seen), True{}) == T : S.St} + L1.cluster_done("=" ++ vv, orig, T) + case S.Flag{}: + Empty.absurd(le.ty(S.Flag{}, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + case S.Pos{}: + Empty.absurd(le.ty(S.Pos{}, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + case S.Rest{}: + Empty.absurd(le.ty(S.Rest{}, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + +def Laws.letter_value_eq( + cc, + vv, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + h_found, + h_kind, + h_ok +): + +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} + +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} + e0 = M2.cl.back(cc, "=" ++ vv, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {_ == T : S.St} + le(kind, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind, h_ok) diff --git a/proofs/LongArgStep.bend b/proofs/LongArgStep.bend new file mode 100644 index 0000000..8ae6a20 --- /dev/null +++ b/proofs/LongArgStep.bend @@ -0,0 +1,6 @@ +# proofs of laws/LongArgStep.bend +import Base +import ../laws/LongArgStep.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + + diff --git a/proofs/LongBinds.bend b/proofs/LongBinds.bend new file mode 100644 index 0000000..d8a68f1 --- /dev/null +++ b/proofs/LongBinds.bend @@ -0,0 +1,146 @@ +# proofs of laws/LongBinds.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../src/grow.bend as Grow +import ../laws/Kept.bend as L1 +import ../laws/LongArgStep.bend as L2 +import ../laws/LongBinds.bend as Laws +import ../laws/defs/ArgumentLists.bend as D1 +import ./lemmas/BindingsKept.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +# what `long.kind` claims of an argument kind +def long.kind.ty( + kk: S.ArgKind, + +nn: String, + +vv: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> Type: + {S.parse.take_arg.kind(kk, nn, Some{vv}, orig, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} + +# an option that takes a value now binds an allowed one +def long.kind( + kk: S.ArgKind, + +nn: String, + +vv: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + h_kind: {D1.takes(kk, S.bound(bs, nn)) == True{} : Bool}, + h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} +) -> long.kind.ty(kk, nn, vv, orig, args, up, subs, path, bs, pos, seen): + match kk: + case S.Opt{}: + e1 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) + %e1 : {S.parse.once(_, S.parse.take_opt(Some{vv}, nn, args, up, subs, path, bs, pos, False{}, seen), nn, path) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} + e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) + %e2 : {S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} + {==} + case S.Many{}: + e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) + %e2 : {S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} + {==} + case S.Flag{}: + Empty.absurd(long.kind.ty(S.Flag{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), + Walk.wk.false_true(h_kind)) + case S.Pos{}: + Empty.absurd(long.kind.ty(S.Pos{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), + Walk.wk.false_true(h_kind)) + case S.Rest{}: + Empty.absurd(long.kind.ty(S.Rest{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), + Walk.wk.false_true(h_kind)) + +# a long option `--name=vv` that takes a value binds an allowed one +def long.step( + +body: String, + +name: String, + +vv: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, + h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>}, + h_name: {String.is_empty(name) == False{} : Bool}, + h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, + h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool}, + h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} +) -> {S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St}: + e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), + S.parse.take_long.found(S.by_long(args, name), name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen), + L2.long_arg_step(body, name, Some{vv}, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) + %e1 : {_ == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}}, h_found) + %e2 : {S.parse.take_long.found(_, name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} + long.kind(kind, nn, vv, "--" ++ body, args, up, subs, path, bs, pos, seen, h_kind, h_ok) + +def Laws.long_binds( + spec, + pre, + more, + body, + name, + vv, + args, + up, + subs, + path, + bs, + pos, + seen, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + found, + h_at, + h_dd, + h_cut, + h_name, + h_found, + h_kind, + h_ok, + h_done +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + L1.kept(spec, pre, more, "--" ++ body, st, args, up, path, nn, vv, bs, found, h_at, + Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step("--" ++ body, st), + S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen}, + long.step(body, name, vv, args, up, subs, path, bs, pos, seen, nn, sh, lo, kind, hp, req, dflt, cs, h_dd, + h_cut, h_name, h_found, h_kind, h_ok)), + h_done) diff --git a/proofs/LongNoName.bend b/proofs/LongNoName.bend new file mode 100644 index 0000000..6f9a4a9 --- /dev/null +++ b/proofs/LongNoName.bend @@ -0,0 +1,146 @@ +# proofs of laws/LongNoName.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../src/walk.bend as Walk +import ../laws/DropZero.bend as L1 +import ../laws/LongNoName.bend as Laws +import ../laws/PrefixEmpty.bend as L2 +import ../laws/Refused.bend as L3 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +# `--=...` fails the step +def long_no_name.step( + +body: String, + +name: String, + +val: Maybe<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, + h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + h_empty: {String.is_empty(name) == True{} : Bool} +) -> {D1.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen})) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(Bool, String.eq("--" ++ body, "--"), False{}, h_dd) + %e1 : {D1.dead_of(S.parse.step.end(_, "--" ++ body, args, up, subs, path, bs, pos, seen)) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + e0 = Equal.sym(Bool, String.starts_with(body, ""), True{}, L2.prefix_empty(body)) + %e0 : {D1.dead_of(S.parse.step.kind.go(_, True{}, "--" ++ body, args, up, subs, path, bs, pos, seen)) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + ed = Equal.sym(String, String.drop(body, 0n), body, L1.drop_zero(body)) + %ed : {D1.dead_of(S.parse.take_long(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) + %e2 : {D1.dead_of(S.parse.take_long.cut(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + e3 = Equal.sym(Bool, String.is_empty(name), True{}, h_empty) + %e3 : {D1.dead_of(S.parse.take_long.named(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, + False{}, seen)) + == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + {==} + +# a word `--=...` read as `--version` would cut to the name `version`, not +# to no name +def lnv.clash( + +body: String, + +name: String, + +val: Maybe<&2, String>, + +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + +h_empty: {String.is_empty(name) == True{} : Bool}, + e: {String.eq("--" ++ body, "--version") == True{} : Bool} +) -> Empty: + ew = Eq.string_eq_true("--" ++ body, "--version", e) + eb = Equal.trans(String, body, String.drop(body, 0n), "version", + Equal.sym(String, String.drop(body, 0n), body, L1.drop_zero(body)), + Equal.cong(String, String, ss => String.drop(ss, 2n), "--" ++ body, "--version", ew)) + ec = Equal.trans(String & Maybe<&2, String>, (name, val), S.cut_eq(body), S.cut_eq("version"), + Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut), + Equal.cong(String, String & Maybe<&2, String>, ss => S.cut_eq(ss), body, "version", eb)) + en = Equal.cong(String & Maybe<&2, String>, Bool, cv => String.is_empty(Pair.fst(String, Maybe<&2, String>, cv)), + (name, val), S.cut_eq("version"), ec) + Walk.wk.true_false(Equal.trans(Bool, True{}, String.is_empty(name), False{}, + Equal.sym(Bool, String.is_empty(name), True{}, h_empty), en)) + +# the same, as an answer told apart by cases +def lnv.go( + bb: Bool, + +body: String, + +name: String, + +val: Maybe<&2, String>, + +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + +h_empty: {String.is_empty(name) == True{} : Bool}, + e: {String.eq("--" ++ body, "--version") == bb : Bool} +) -> {String.eq("--" ++ body, "--version") == False{} : Bool}: + match bb: + case False{}: + e + case True{}: + Empty.absurd({String.eq("--" ++ body, "--version") == False{} : Bool}, + lnv.clash(body, name, val, h_cut, h_empty, e)) + +# a word `--=...` is not `--version` +def lnv.word( + +body: String, + +name: String, + +val: Maybe<&2, String>, + +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + +h_empty: {String.is_empty(name) == True{} : Bool} +) -> {String.eq("--" ++ body, "--version") == False{} : Bool}: + lnv.go(String.eq("--" ++ body, "--version"), body, name, val, h_cut, h_empty, {==}) + +# so it asks for no version +def lnv.asks( + +spec: Shake.Cli, + +path: List<&2, String>, + +body: String, + name: String, + val: Maybe<&2, String>, + h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + h_empty: {String.is_empty(name) == True{} : Bool} +) -> {S.version.asks(spec, path, "--" ++ body) == False{} : Bool}: + match spec: + case S.Cli{+nn, +aa, +vv, +gg, +ss}: + ee = Equal.sym(Bool, String.eq("--" ++ body, "--version"), False{}, lnv.word(body, name, val, h_cut, h_empty)) + %ee : {Bool.and(Bool.or(_, Bool.and(String.eq("--" ++ body, "-V"), + Maybe.is_none(&2, S.Arg, S.by_long(gg, "version")))), + Bool.and(List.is_empty(&2, String, path), Maybe.is_some(&2, String, vv))) == False{} : Bool} + el = Equal.sym(Cmp, String.order.go(body, "", LT{}), LT{}, Eq.string_order_lt(body, "")) + %el : {Bool.and(Bool.and(Cmp.is_eq(_), Bool.not(Maybe.is_some(&2, S.Arg, S.by_long(gg, "version")))), + Bool.and(List.is_empty(&2, String, path), Maybe.is_some(&2, String, vv))) == False{} : Bool} + {==} + +def Laws.long_no_name( + spec, + pre, + more, + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + h_at, + h_dd, + h_cut, + h_empty +): + +er = {S.UnknownFlag{path, "--" ++ body} : S.ParseErr} + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, + Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)), + Fail{S.version.err(spec, er)}, Fail{er}, + L3.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + er, h_at, + long_no_name.step(body, name, val, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_empty)), + M1.fs.keep(spec, path, "--" ++ body, lnv.asks(spec, path, body, name, val, h_cut, h_empty))) diff --git a/proofs/LongOptionSplitsAtEquals.bend b/proofs/LongOptionSplitsAtEquals.bend new file mode 100644 index 0000000..d3ce677 --- /dev/null +++ b/proofs/LongOptionSplitsAtEquals.bend @@ -0,0 +1,175 @@ +# proofs of laws/LongOptionSplitsAtEquals.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/LongOptionSplitsAtEquals.bend as Laws + +# a string read back from its chars is itself +def cut.from_to(ss: String) -> {String.from_list(String.to_list(ss)) == ss : String}: + match ss: + case SNil{}: + {==} + case SCon{+hh, tt}: + Equal.cong(String, String, xx => SCon{hh, xx}, String.from_list(String.to_list(tt)), tt, cut.from_to(tt)) + +# appending two lists, then a third, is appending the first to the rest +def cut.app( + +aa: List<&2, Char>, + +bb: List<&2, Char>, + +cc: List<&2, Char> +) -> {List.append(&2, Char, List.append(&2, Char, aa, bb), cc) + == List.append(&2, Char, aa, List.append(&2, Char, bb, cc)) : List<&2, Char>}: + match aa: + case Nil{}: + {==} + case Con{+hh, +tt}: + Equal.cong(List<&2, Char>, List<&2, Char>, xs => hh <> xs, + List.append(&2, Char, List.append(&2, Char, tt, bb), cc), + List.append(&2, Char, tt, List.append(&2, Char, bb, cc)), cut.app(tt, bb, cc)) + +# reversing onto a list is reversing, then appending it +def cut.rev_acc( + xs: List<&2, Char>, + +acc: List<&2, Char> +) -> {List.reverse.go(&2, Char, xs, acc) + == List.append(&2, Char, List.reverse.go(&2, Char, xs, []), acc) : List<&2, Char>}: + match xs: + case Nil{}: + {==} + case Con{+hh, +tt}: + Equal.trans(List<&2, Char>, List.reverse.go(&2, Char, tt, hh <> acc), + List.append(&2, Char, List.reverse.go(&2, Char, tt, []), hh <> acc), + List.append(&2, Char, List.reverse.go(&2, Char, tt, [hh]), acc), + cut.rev_acc(tt, hh <> acc), + Equal.sym(List<&2, Char>, List.append(&2, Char, List.reverse.go(&2, Char, tt, [hh]), acc), + List.append(&2, Char, List.reverse.go(&2, Char, tt, []), hh <> acc), + Equal.trans(List<&2, Char>, + List.append(&2, Char, List.reverse.go(&2, Char, tt, [hh]), acc), + List.append(&2, Char, List.append(&2, Char, List.reverse.go(&2, Char, tt, []), [hh]), + acc), + List.append(&2, Char, List.reverse.go(&2, Char, tt, []), hh <> acc), + Equal.cong(List<&2, Char>, List<&2, Char>, ys => List.append(&2, Char, ys, acc), + List.reverse.go(&2, Char, tt, [hh]), + List.append(&2, Char, List.reverse.go(&2, Char, tt, []), [hh]), cut.rev_acc(tt, [hh])), + cut.app(List.reverse.go(&2, Char, tt, []), [hh], acc)))) + +# a char after a buffer is that char in front of the buffer folded onto acc +def cut.snoc( + xs: List<&2, Char>, + +hh: Char, + +acc: String +) -> {S.from.rev.go(List.append(&2, Char, xs, [hh]), acc) == SCon{hh, S.from.rev.go(xs, acc)} : String}: + match xs: + case Nil{}: + {==} + case Con{+xx, tt}: + cut.snoc(tt, hh, SCon{xx, acc}) + +# folding a string's chars, reversed, onto acc is the string in front of acc +def cut.fold( + ss: String, + +acc: String +) -> {S.from.rev.go(List.reverse.go(&2, Char, String.to_list(ss), []), acc) == String.append(ss, acc) : String}: + match ss: + case SNil{}: + {==} + case SCon{+hh, +tt}: + +rev = List.reverse.go(&2, Char, String.to_list(tt), []) + Equal.trans(String, + S.from.rev.go(List.reverse.go(&2, Char, String.to_list(tt), [hh]), acc), + S.from.rev.go(List.append(&2, Char, rev, [hh]), acc), + String.append(SCon{hh, tt}, acc), + Equal.cong(List<&2, Char>, String, xs => S.from.rev.go(xs, acc), + List.reverse.go(&2, Char, String.to_list(tt), [hh]), + List.append(&2, Char, rev, [hh]), + cut.rev_acc(String.to_list(tt), [hh])), + Equal.trans(String, + S.from.rev.go(List.append(&2, Char, rev, [hh]), acc), + SCon{hh, S.from.rev.go(rev, acc)}, + SCon{hh, String.append(tt, acc)}, + cut.snoc(rev, hh, acc), + Equal.cong(String, String, xx => SCon{hh, xx}, + S.from.rev.go(rev, acc), String.append(tt, acc), cut.fold(tt, acc)))) + +# a string in front of nothing is the string +def cut.app_nil(ss: String) -> {String.append(ss, SNil{}) == ss : String}: + match ss: + case SNil{}: + {==} + case SCon{+hh, tt}: + Equal.cong(String, String, xx => SCon{hh, xx}, String.append(tt, SNil{}), tt, cut.app_nil(tt)) + +# a char a string with no `=` starts with is not `=` +def cut.head( + +cc: Char, + +tt: String, + h_name: {Laws.no_eq(SCon{cc, tt}) == True{} : Bool} +) -> {Char.is_eq(cc, '=') == False{} : Bool}: + Walk.wk.not_true(Char.is_eq(cc, '='), Walk.wk.and_l(Bool.not(Char.is_eq(cc, '=')), Laws.no_eq(tt), h_name)) + +# the rest of a string with no `=` has none either +def cut.tail( + +cc: Char, + +tt: String, + h_name: {Laws.no_eq(SCon{cc, tt}) == True{} : Bool} +) -> {Laws.no_eq(tt) == True{} : Bool}: + Walk.wk.and_r(Bool.not(Char.is_eq(cc, '=')), Laws.no_eq(tt), h_name) + +# the cut, char by char: every char before the first `=` goes onto the +# accumulator, and the `=` ends the name +def cut.go_eq( + name: String, + +vv: String, + +acc: List<&2, Char>, + +h_name: {Laws.no_eq(name) == True{} : Bool} +) -> {S.cut_eq.go(String.to_list(name ++ ("=" ++ vv)), acc) + == (S.from.rev(List.reverse.go(&2, Char, String.to_list(name), acc)), Some{String.from_list(String.to_list(vv))}) + : String & Maybe<&2, String>}: + match name: + case SNil{}: + {==} + case SCon{+cc, +tt}: + +rest = String.to_list(tt ++ ("=" ++ vv)) + ee = Equal.sym(Bool, Char.is_eq(cc, '='), False{}, cut.head(cc, tt, h_name)) + %ee : {S.cut_eq.pick(_, acc, rest, S.cut_eq.go(rest, cc <> acc)) + == (S.from.rev(List.reverse.go(&2, Char, String.to_list(tt), cc <> acc)), + Some{String.from_list(String.to_list(vv))}) : String & Maybe<&2, String>} + cut.go_eq(tt, vv, cc <> acc, cut.tail(cc, tt, h_name)) + +# the cut, char by char, of a string with no `=` +def cut.go_none( + name: String, + +acc: List<&2, Char>, + +h_name: {Laws.no_eq(name) == True{} : Bool} +) -> {S.cut_eq.go(String.to_list(name), acc) + == (S.from.rev(List.reverse.go(&2, Char, String.to_list(name), acc)), None{}) : String & Maybe<&2, String>}: + match name: + case SNil{}: + {==} + case SCon{+cc, +tt}: + +rest = String.to_list(tt) + ee = Equal.sym(Bool, Char.is_eq(cc, '='), False{}, cut.head(cc, tt, h_name)) + %ee : {S.cut_eq.pick(_, acc, rest, S.cut_eq.go(rest, cc <> acc)) + == (S.from.rev(List.reverse.go(&2, Char, String.to_list(tt), cc <> acc)), None{}) + : String & Maybe<&2, String>} + cut.go_none(tt, cc <> acc, cut.tail(cc, tt, h_name)) + +# the name the cut reads back is the name +def cut.name(+name: String) -> {S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])) == name : String}: + Equal.trans(String, S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])), + String.append(name, SNil{}), name, cut.fold(name, SNil{}), cut.app_nil(name)) + +def Laws.cut_first_eq(name, vv, h_name): + +nm = S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])) + +vl = String.from_list(String.to_list(vv)) + Equal.trans(String & Maybe<&2, String>, S.cut_eq(name ++ ("=" ++ vv)), (nm, Some{vl}), (name, Some{vv}), + cut.go_eq(name, vv, [], h_name), + Equal.trans(String & Maybe<&2, String>, (nm, Some{vl}), (name, Some{vl}), (name, Some{vv}), + Equal.cong(String, String & Maybe<&2, String>, xx => (xx, Some{vl}), nm, name, cut.name(name)), + Equal.cong(String, String & Maybe<&2, String>, xx => (name, Some{xx}), vl, vv, cut.from_to(vv)))) + +def Laws.cut_no_eq(name, h_name): + +nm = S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])) + Equal.trans(String & Maybe<&2, String>, S.cut_eq(name), (nm, None{}), (name, None{}), + cut.go_none(name, [], h_name), + Equal.cong(String, String & Maybe<&2, String>, xx => (xx, None{}), nm, name, cut.name(name))) diff --git a/proofs/LongRefused.bend b/proofs/LongRefused.bend new file mode 100644 index 0000000..ab4cbb6 --- /dev/null +++ b/proofs/LongRefused.bend @@ -0,0 +1,142 @@ +# proofs of laws/LongRefused.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/LongArgStep.bend as L1 +import ../laws/LongRefused.bend as Laws +import ../laws/Refused.bend as L2 +import ../laws/defs/ArgumentLists.bend as D1 +import ../laws/defs/WalkerFailure.bend as D2 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +# what `long_refused.kind` claims of an argument kind +def long_refused.kind.ty( + kk: S.ArgKind, + +nn: String, + +vv: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool +) -> Type: + {D2.dead_of(S.parse.take_arg.kind(kk, nn, Some{vv}, orig, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} + +# an option that takes a value now refuses one outside its choices +def long_refused.kind( + kk: S.ArgKind, + +nn: String, + +vv: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + h_kind: {D1.takes(kk, S.bound(bs, nn)) == True{} : Bool}, + h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == False{} : Bool} +) -> long_refused.kind.ty(kk, nn, vv, orig, args, up, subs, path, bs, pos, seen): + match kk: + case S.Opt{}: + e1 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) + %e1 : {D2.dead_of(S.parse.once(_, S.parse.take_opt(Some{vv}, nn, args, up, subs, path, bs, pos, False{}, + seen), nn, path)) == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), False{}, h_bad) + %e2 : {D2.dead_of(S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} + {==} + case S.Many{}: + e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), False{}, h_bad) + %e2 : {D2.dead_of(S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} + {==} + case S.Flag{}: + Empty.absurd(long_refused.kind.ty(S.Flag{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), + Walk.wk.false_true(h_kind)) + case S.Pos{}: + Empty.absurd(long_refused.kind.ty(S.Pos{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), + Walk.wk.false_true(h_kind)) + case S.Rest{}: + Empty.absurd(long_refused.kind.ty(S.Rest{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), + Walk.wk.false_true(h_kind)) + +# a long option `--name=vv` that takes a value refuses one outside its choices +def long_refused.step( + +body: String, + +name: String, + +vv: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, + h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>}, + h_name: {String.is_empty(name) == False{} : Bool}, + h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, + h_kind: {D1.takes(kind, S.bound(bs, nn)) == True{} : Bool}, + h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == False{} : Bool} +) -> {D2.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen})) + == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), + S.parse.take_long.found(S.by_long(args, name), name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen), + L1.long_arg_step(body, name, Some{vv}, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) + %e1 : {D2.dead_of(_) == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}}, h_found) + %e2 : {D2.dead_of(S.parse.take_long.found(_, name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen)) + == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} + long_refused.kind(kind, nn, vv, "--" ++ body, args, up, subs, path, bs, pos, seen, h_kind, h_bad) + +def Laws.long_refused( + spec, + pre, + more, + body, + name, + vv, + args, + up, + subs, + path, + bs, + pos, + seen, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + h_at, + h_dd, + h_cut, + h_name, + h_found, + h_kind, + h_bad +): + L2.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + S.BadValue{path, nn, vv}, h_at, + long_refused.step(body, name, vv, args, up, subs, path, bs, pos, seen, nn, sh, lo, kind, hp, req, dflt, cs, + h_dd, h_cut, h_name, h_found, h_kind, h_bad)) diff --git a/proofs/LooksFlagLong.bend b/proofs/LooksFlagLong.bend new file mode 100644 index 0000000..1259246 --- /dev/null +++ b/proofs/LooksFlagLong.bend @@ -0,0 +1,6 @@ +# proofs of laws/LooksFlagLong.bend +import Base +import ../laws/LooksFlagLong.bend as Laws + +def Laws.looks_flag_long(_ss): + {==} diff --git a/proofs/NoPosLeft.bend b/proofs/NoPosLeft.bend new file mode 100644 index 0000000..d14c7dd --- /dev/null +++ b/proofs/NoPosLeft.bend @@ -0,0 +1,51 @@ +# proofs of laws/NoPosLeft.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/NoPosLeft.bend as Laws +import ../laws/Refused.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 + +# a plain word with no positional left fails the step with Unexpected +def no_pos_left.step( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + h_dd: {String.eq(tok, "--") == False{} : Bool}, + h_help: {String.eq(tok, "help") == False{} : Bool}, + +h_dash: {String.starts_with(tok, "-") == False{} : Bool}, + h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} +) -> {D1.dead_of(S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, [], False{}, + seen})) == Some{S.Unexpected{path, tok}} + : Maybe<&2, S.ParseErr>}: + ee = Equal.sym(S.St, S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, [], False{}, seen}), + S.parse.take_pos(tok, args, up, subs, path, bs, [], False{}), + Walk.wk.word_step(tok, args, up, subs, path, bs, [], seen, h_dd, h_help, h_dash, h_sub)) + %ee : {D1.dead_of(_) == Some{S.Unexpected{path, tok}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.no_pos_left( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + seen, + h_at, + h_dd, + h_help, + h_dash, + h_sub +): + L1.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, [], False{}, seen}, + S.Unexpected{path, tok}, h_at, + no_pos_left.step(tok, args, up, subs, path, bs, seen, h_dd, h_help, h_dash, h_sub)) diff --git a/proofs/OnNil.bend b/proofs/OnNil.bend new file mode 100644 index 0000000..89f838d --- /dev/null +++ b/proofs/OnNil.bend @@ -0,0 +1,6 @@ +# proofs of laws/OnNil.bend +import Base +import ../laws/OnNil.bend as Laws + +def Laws.on_nil(_name): + {==} diff --git a/proofs/OnceDead.bend b/proofs/OnceDead.bend new file mode 100644 index 0000000..0441285 --- /dev/null +++ b/proofs/OnceDead.bend @@ -0,0 +1,6 @@ +# proofs of laws/OnceDead.bend +import Base +import ../laws/OnceDead.bend as Laws +import ./lemmas/FailedWalkerStays.bend as M1 + + diff --git a/proofs/PlainWordIsNeverOption.bend b/proofs/PlainWordIsNeverOption.bend new file mode 100644 index 0000000..bffac36 --- /dev/null +++ b/proofs/PlainWordIsNeverOption.bend @@ -0,0 +1,20 @@ +# proofs of laws/PlainWordIsNeverOption.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/PlainWordIsNeverOption.bend as Laws + +def Laws.plain_word_step(tok, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_help): + +rhs = S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) + e1 = Equal.sym(Bool, String.eq(tok, "--"), False{}, h_dd) + %e1 : {S.parse.step.end(_, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + e2 = Equal.sym(Bool, Bool.and(String.eq(tok, "help"), Bool.not(seen)), False{}, h_help) + %e2 : {S.parse.step.help(_, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + e3 = Equal.sym(Bool, String.starts_with(tok, "--"), False{}, Walk.wk.dash(tok, h_dash)) + %e3 : {S.parse.step.kind.go(_, String.starts_with(tok, "-"), tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + e4 = Equal.sym(Bool, String.starts_with(tok, "-"), False{}, h_dash) + %e4 : {S.parse.step.kind.go(False{}, _, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + {==} + +def Laws.dash_word_step(_args, _up, _subs, _path, _bs, _pos, _seen): + {==} diff --git a/proofs/PosBinds.bend b/proofs/PosBinds.bend new file mode 100644 index 0000000..04bacbc --- /dev/null +++ b/proofs/PosBinds.bend @@ -0,0 +1,48 @@ +# proofs of laws/PosBinds.bend +import Base +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ../laws/Kept.bend as L1 +import ../laws/PosBinds.bend as Laws +import ./lemmas/BindingsKept.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +def Laws.pos_binds( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + seen, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + found, + h_at, + h_dd, + h_help, + h_dash, + h_sub, + h_ok, + h_done +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, + seen} : S.St} + L1.kept(spec, pre, more, tok, st, args, up, path, name, tok, bs, found, h_at, + Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step(tok, st), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, + M2.pos.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, rest, h_dd, h_help, + h_dash, h_sub, h_ok)), + h_done) diff --git a/proofs/PosNext.bend b/proofs/PosNext.bend new file mode 100644 index 0000000..c070ff3 --- /dev/null +++ b/proofs/PosNext.bend @@ -0,0 +1,41 @@ +# proofs of laws/PosNext.bend +import Base +import ../src/cli.bend as S +import ../laws/PosNext.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + +def Laws.pos_next( + spec, + pre, + tok, + args, + up, + subs, + path, + bs, + seen, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + h_at, + h_dd, + h_help, + h_dash, + h_sub, + h_ok +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, + seen} : S.St} + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), S.parse.step(tok, st), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, + M1.kept.stepped(spec, pre, tok, st, h_at), + M1.pos.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, rest, h_dd, h_help, h_dash, + h_sub, h_ok)) diff --git a/proofs/PositionalsBindInOrder.bend b/proofs/PositionalsBindInOrder.bend new file mode 100644 index 0000000..2d9d077 --- /dev/null +++ b/proofs/PositionalsBindInOrder.bend @@ -0,0 +1,161 @@ +# proofs of laws/PositionalsBindInOrder.bend +import Base +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ../laws/Kept.bend as L1 +import ../laws/PositionalsBindInOrder.bend as Laws +import ./lemmas/BindingsKept.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +def Laws.start_state(_name, _about, _version, _args, _subs): + {==} + +# keeping an argument in front of two lists is keeping it in front of the first +def pof.put_app( + keep: Bool, + +hh: S.Arg, + +aa: List<&2, S.Arg>, + +bb: List<&2, S.Arg> +) -> {S.pos_of.put(keep, hh, List.append(&2, S.Arg, aa, bb)) + == List.append(&2, S.Arg, S.pos_of.put(keep, hh, aa), bb) : List<&2, S.Arg>}: + match keep: + case True{}: + {==} + case False{}: + {==} + +def Laws.pos_of_app(xs, ys): + match xs: + case Nil{}: + {==} + case Con{+hh, +tt}: + Equal.trans(List<&2, S.Arg>, S.pos_of.put(S.pos.is(hh), hh, S.pos_of(List.append(&2, S.Arg, tt, ys))), + S.pos_of.put(S.pos.is(hh), hh, List.append(&2, S.Arg, S.pos_of(tt), S.pos_of(ys))), + List.append(&2, S.Arg, S.pos_of.put(S.pos.is(hh), hh, S.pos_of(tt)), S.pos_of(ys)), + Equal.cong(List<&2, S.Arg>, List<&2, S.Arg>, zs => S.pos_of.put(S.pos.is(hh), hh, zs), + S.pos_of(List.append(&2, S.Arg, tt, ys)), List.append(&2, S.Arg, S.pos_of(tt), S.pos_of(ys)), + Laws.pos_of_app(tt, ys)), + pof.put_app(S.pos.is(hh), hh, S.pos_of(tt), S.pos_of(ys))) + +def Laws.pos_of_pos(aa, h_pos): + ee = Equal.sym(Bool, S.pos.is(aa), True{}, h_pos) + %ee : {S.pos_of.put(_, aa, []) == [aa] : List<&2, S.Arg>} + {==} + +def Laws.pos_of_opt(aa, h_opt): + ee = Equal.sym(Bool, S.pos.is(aa), False{}, h_opt) + %ee : {S.pos_of.put(_, aa, []) == [] : List<&2, S.Arg>} + {==} + +# after a positional is bound, `help` reaches the positional it binds +def hw.step( + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>} +) -> {S.parse.step("help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, True{}}) + == S.parse.take_pos("help", args, up, subs, path, bs, pos, False{}) : S.St}: + ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, "help"), None{}, h_sub) + %ee : {S.parse.word.go(_, "help", args, up, subs, path, bs, pos, False{}, True{}) + == S.parse.take_pos("help", args, up, subs, path, bs, pos, False{}) : S.St} + {==} + +# `help` taken by the next positional +def hw.full( + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +name: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +rest: List<&2, S.Arg>, + h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>}, + h_ok: {S.allowed(cs, "help") == True{} : Bool} +) -> {S.parse.step("help", S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, + cs} <> rest, + False{}, True{}}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St}: + +pp = {S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest : List<&2, S.Arg>} + Equal.trans(S.St, S.parse.step("help", S.St{S.Free{}, args, up, subs, path, bs, pp, False{}, True{}}), + S.parse.take_pos("help", args, up, subs, path, bs, pp, False{}), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, + hw.step(args, up, subs, path, bs, pp, h_sub), + M2.pos.take("help", args, up, subs, path, bs, False{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)) + +def Laws.help_word_next( + spec, + pre, + args, + up, + subs, + path, + bs, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + h_at, + h_sub, + h_ok +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, True{}} + : S.St} + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ["help"]), S.parse.start(spec)), S.parse.step("help", st), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, + M2.kept.stepped(spec, pre, "help", st, h_at), + hw.full(args, up, subs, path, bs, name, sh, lo, kind, hp, req, dflt, cs, rest, h_sub, h_ok)) + +def Laws.help_word_binds( + spec, + pre, + more, + args, + up, + subs, + path, + bs, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + found, + h_at, + h_sub, + h_ok, + h_done +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, True{}} + : S.St} + L1.kept(spec, pre, more, "help", st, args, up, path, name, "help", bs, found, h_at, + Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step("help", st), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, + hw.full(args, up, subs, path, bs, name, sh, lo, kind, hp, req, dflt, cs, rest, h_sub, h_ok)), + h_done) diff --git a/proofs/PrefixEmpty.bend b/proofs/PrefixEmpty.bend new file mode 100644 index 0000000..fee7681 --- /dev/null +++ b/proofs/PrefixEmpty.bend @@ -0,0 +1,6 @@ +# proofs of laws/PrefixEmpty.bend +import Base +import ../laws/PrefixEmpty.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + + diff --git a/proofs/RawBinds.bend b/proofs/RawBinds.bend new file mode 100644 index 0000000..279e159 --- /dev/null +++ b/proofs/RawBinds.bend @@ -0,0 +1,43 @@ +# proofs of laws/RawBinds.bend +import Base +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ../laws/Kept.bend as L1 +import ../laws/RawBinds.bend as Laws +import ./lemmas/BindingsKept.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +def Laws.raw_binds( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + seen, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + found, + h_at, + h_ok, + h_done +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, True{}, + seen} : S.St} + L1.kept(spec, pre, more, tok, st, args, up, path, name, tok, bs, found, h_at, + Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step(tok, st), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{}}, + M2.pos.take(tok, args, up, subs, path, bs, True{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)), + h_done) diff --git a/proofs/RawNext.bend b/proofs/RawNext.bend new file mode 100644 index 0000000..b78ee89 --- /dev/null +++ b/proofs/RawNext.bend @@ -0,0 +1,36 @@ +# proofs of laws/RawNext.bend +import Base +import ../src/cli.bend as S +import ../laws/RawNext.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + +def Laws.raw_next( + spec, + pre, + tok, + args, + up, + subs, + path, + bs, + seen, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + h_at, + h_ok +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, True{}, + seen} : S.St} + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), S.parse.step(tok, st), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{}}, + M1.kept.stepped(spec, pre, tok, st, h_at), + M1.pos.take(tok, args, up, subs, path, bs, True{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)) diff --git a/proofs/RawNoPos.bend b/proofs/RawNoPos.bend new file mode 100644 index 0000000..8c26b3a --- /dev/null +++ b/proofs/RawNoPos.bend @@ -0,0 +1,10 @@ +# proofs of laws/RawNoPos.bend +import Base +import ../src/cli.bend as S +import ../laws/RawNoPos.bend as Laws +import ../laws/Refused.bend as L1 +import ./lemmas/FailedWalkThrough.bend as M1 + +def Laws.raw_no_pos(spec, pre, more, tok, args, up, subs, path, bs, seen, h_at): + L1.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, [], True{}, seen}, + S.Unexpected{path, tok}, h_at, {==}) diff --git a/proofs/RawRefused.bend b/proofs/RawRefused.bend new file mode 100644 index 0000000..593ac7c --- /dev/null +++ b/proofs/RawRefused.bend @@ -0,0 +1,62 @@ +# proofs of laws/RawRefused.bend +import Base +import ../src/cli.bend as S +import ../laws/RawRefused.bend as Laws +import ../laws/Refused.bend as L1 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 + +# after `--`, a word the next positional's choices refuse fails the step +def raw_refused.step( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + +name: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +rest: List<&2, S.Arg>, + h_bad: {S.allowed(cs, tok) == False{} : Bool} +) -> {D1.dead_of(S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, + req, dflt, cs} <> rest, True{}, seen})) == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>}: + e2 = Equal.sym(Bool, S.allowed(cs, tok), False{}, h_bad) + %e2 : {D1.dead_of(S.parse.take_choice(_, name, tok, args, up, subs, path, bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{})) + == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.raw_refused( + spec, + pre, + more, + tok, + args, + up, + subs, + path, + bs, + seen, + name, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + rest, + h_at, + h_bad +): + L1.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, + dflt, cs} <> rest, True{}, seen}, S.BadValue{path, name, tok}, h_at, + raw_refused.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, rest, h_bad)) diff --git a/proofs/Refused.bend b/proofs/Refused.bend new file mode 100644 index 0000000..0dcb007 --- /dev/null +++ b/proofs/Refused.bend @@ -0,0 +1,6 @@ +# proofs of laws/Refused.bend +import Base +import ../laws/Refused.bend as Laws +import ./lemmas/FailedWalkThrough.bend as M1 + + diff --git a/proofs/RepeatedLongFlag.bend b/proofs/RepeatedLongFlag.bend new file mode 100644 index 0000000..6f569e6 --- /dev/null +++ b/proofs/RepeatedLongFlag.bend @@ -0,0 +1,83 @@ +# proofs of laws/RepeatedLongFlag.bend +import Base +import ../src/cli.bend as S +import ../laws/LongArgStep.bend as L1 +import ../laws/OnceDead.bend as L2 +import ../laws/Refused.bend as L3 +import ../laws/RepeatedLongFlag.bend as Laws +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/FailedWalkerStays.bend as M2 +import ./lemmas/WalkSteps.bend as M3 + +# a long option given again fails the step +def repeated_long_flag.step( + +body: String, + +name: String, + +val: Maybe<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, + h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + h_name: {String.is_empty(name) == False{} : Bool}, + h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, + h_again: {S.bound(bs, nn) == True{} : Bool} +) -> {D1.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, + seen})) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), + S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen), + L1.long_arg_step(body, name, val, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) + %e1 : {D1.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}}, h_found) + %e2 : {D1.dead_of(S.parse.take_long.found(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + e3 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) + %e3 : {D1.dead_of(S.parse.once(_, S.parse.take_flag(val, nn, "--" ++ body, args, up, subs, path, bs, pos, + False{}, seen), nn, path)) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + L2.once_dead(S.parse.take_flag(val, nn, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen), nn, path) + +def Laws.repeated_long_flag( + spec, + pre, + more, + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_at, + h_dd, + h_cut, + h_name, + h_found, + h_again +): + L3.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + S.Repeated{path, nn}, h_at, + repeated_long_flag.step(body, name, val, args, up, subs, path, bs, pos, seen, nn, sh, lo, hp, req, dflt, cs, + h_dd, h_cut, h_name, h_found, h_again)) diff --git a/proofs/RepeatedLongOpt.bend b/proofs/RepeatedLongOpt.bend new file mode 100644 index 0000000..86a7339 --- /dev/null +++ b/proofs/RepeatedLongOpt.bend @@ -0,0 +1,83 @@ +# proofs of laws/RepeatedLongOpt.bend +import Base +import ../src/cli.bend as S +import ../laws/LongArgStep.bend as L1 +import ../laws/OnceDead.bend as L2 +import ../laws/Refused.bend as L3 +import ../laws/RepeatedLongOpt.bend as Laws +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/FailedWalkerStays.bend as M2 +import ./lemmas/WalkSteps.bend as M3 + +# a long option given again fails the step +def repeated_long_opt.step( + +body: String, + +name: String, + +val: Maybe<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, + h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, + h_name: {String.is_empty(name) == False{} : Bool}, + h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, + h_again: {S.bound(bs, nn) == True{} : Bool} +) -> {D1.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, + seen})) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), + S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen), + L1.long_arg_step(body, name, val, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) + %e1 : {D1.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}}, h_found) + %e2 : {D1.dead_of(S.parse.take_long.found(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) + == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + e3 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) + %e3 : {D1.dead_of(S.parse.once(_, S.parse.take_opt(val, nn, args, up, subs, path, bs, pos, False{}, + seen), nn, path)) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} + L2.once_dead(S.parse.take_opt(val, nn, args, up, subs, path, bs, pos, False{}, seen), nn, path) + +def Laws.repeated_long_opt( + spec, + pre, + more, + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_at, + h_dd, + h_cut, + h_name, + h_found, + h_again +): + L3.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + S.Repeated{path, nn}, h_at, + repeated_long_opt.step(body, name, val, args, up, subs, path, bs, pos, seen, nn, sh, lo, hp, req, dflt, cs, + h_dd, h_cut, h_name, h_found, h_again)) diff --git a/proofs/RequiredMissing.bend b/proofs/RequiredMissing.bend new file mode 100644 index 0000000..75d68ba --- /dev/null +++ b/proofs/RequiredMissing.bend @@ -0,0 +1,181 @@ +# proofs of laws/RequiredMissing.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../src/grow.bend as Grow +import ../laws/RequiredMissing.bend as Laws +import ../laws/defs/FreeWalker.bend as D1 +import ./lemmas/ClusterEnd.bend as M1 + +# a walk that finishes missing a name, as a type +def vr.miss_ty(+spec: Shake.Cli, +ws: List<&2, String>, +path: List<&2, String>) -> Type: + &nm: String -> {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == Fail{S.Missing{path, nm}} + : Result<&2, &2, S.ParseErr, S.Matched>} + +# a walk that finishes missing a name parses missing it +def vr.miss( + +spec: Shake.Cli, + +ws: List<&2, String>, + +path: List<&2, String>, + ww: vr.miss_ty(spec, ws, path) +) -> &nm: String -> {Shake.parse(spec, ws) == Fail{S.Missing{path, nm}} : Result<&2, &2, S.ParseErr, S.Matched>}: + (n1, e1) = ww + +nm = {n1 : String} + (nm, Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, + xx => S.version.res(spec, xx), S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), + Fail{S.Missing{path, nm}}, e1)) + +# a check that found something here finds something +def fin.one_some( + need: Bool, + +mm: String, + +rest: Maybe<&2, String>, + ww: &nm: String -> {rest == Some{nm} : Maybe<&2, String>} +) -> &nm: String -> {S.miss.one(need, mm, rest) == Some{nm} : Maybe<&2, String>}: + match need: + case True{}: + (mm, {==}) + case False{}: + ww + +# the required, unbound argument itself is found missing +def fin.miss_here( + +nn: String, + +fb: List<&2, S.Bind>, + +rest: Maybe<&2, String>, + h_unbound: {S.bound(fb, nn) == False{} : Bool} +) -> {S.miss.one(Bool.and(True{}, Bool.not(S.bound(fb, nn))), nn, rest) == Some{nn} : Maybe<&2, String>}: + ee = Equal.sym(Bool, S.bound(fb, nn), False{}, h_unbound) + %ee : {S.miss.one(Bool.and(True{}, Bool.not(_)), nn, rest) == Some{nn} : Maybe<&2, String>} + {==} + +# a required argument its command left unbound is found missing +def fin.miss_args_some( + pa: List<&2, S.Arg>, + +qa: List<&2, S.Arg>, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +dflt: S.Fallback, + +cs: List<&2, String>, + +fb: List<&2, S.Bind>, + +h_unbound: {S.bound(fb, nn) == False{} : Bool} +) -> &nm: String -> {S.miss.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), fb) + == Some{nm} : Maybe<&2, String>}: + match pa: + case Nil{}: + (nn, fin.miss_here(nn, fb, S.miss.args(qa, fb), h_unbound)) + case Con{hd, +tt}: + match hd: + case S.Arg{+mm, _s, _l, _k, _h, +rq, _d, _c}: + fin.one_some(Bool.and(rq, Bool.not(S.bound(fb, mm))), mm, + S.miss.args(List.append(&2, S.Arg, tt, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), fb), + fin.miss_args_some(tt, qa, nn, sh, lo, kind, hp, dflt, cs, fb, h_unbound)) + +# a first-required pick with something found here or further on finds it +def fin.first_some( + here: Maybe<&2, String>, + +rest: Maybe<&2, String>, + ww: &nm: String -> {rest == Some{nm} : Maybe<&2, String>} +) -> &nm: String -> {S.parse.first_req.at(here, rest) == Some{nm} : Maybe<&2, String>}: + match here: + case Some{+nm}: + (nm, {==}) + case None{}: + ww + +# a required check over the levels finds what one level's check finds, or an +# earlier one +def fin.miss_levels_some( + r0: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +fb: List<&2, S.Bind>, + +rq: List<&2, S.Level>, + ww: &nm: String -> {S.miss.args(args, fb) == Some{nm} : Maybe<&2, String>} +) -> &nm: String -> {S.miss.levels(List.append(&2, S.Level, r0, S.Level{args, fb} <> rq)) == Some{nm} + : Maybe<&2, String>}: + match r0: + case Nil{}: + (n1, e1) = ww + +nm = {n1 : String} + (nm, Equal.cong(Maybe<&2, String>, Maybe<&2, String>, xx => S.parse.first_req.at(xx, S.miss.levels(rq)), + S.miss.args(args, fb), Some{nm}, e1)) + case Con{ll, +tt}: + match ll: + case S.Level{+la, +lb}: + fin.first_some(S.miss.args(la, lb), S.miss.levels(List.append(&2, S.Level, tt, S.Level{args, fb} <> rq)), + fin.miss_levels_some(tt, args, fb, rq, ww)) + +# what `required_missing` claims of a finished walker +def fin.missing.ty(+fs: S.St, +path: List<&2, String>) -> Type: + &nm: String -> {S.parse.finish(fs) == Fail{S.Missing{path, nm}} : Result<&2, &2, S.ParseErr, S.Matched>} + +# a free walker whose levels' required check finds a name fails with it +def fin.missing( + fs: S.St, + +lvs: List<&2, S.Level>, + +path: List<&2, String>, + +ll: List<&2, S.Level>, + h_free: {D1.free(fs) == True{} : Bool}, + h_fr: {Grow.gw.frame(fs) == Grow.Frame{lvs, path} : Grow.Frame}, + e_ll: {List.reverse(&2, S.Level, S.fill.levels(lvs)) == ll : List<&2, S.Level>}, + ww: &nm: String -> {S.miss.levels(ll) == Some{nm} : Maybe<&2, String>} +) -> fin.missing.ty(fs, path): + match fs: + case S.St{S.Free{}, +args, +up, +subs, +pp, +binds, +pos, +raw, +seen}: + (n1, e1) = ww + +nm = {n1 : String} + +l0 = List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up)) + +h_fr2 = {h_fr : {Grow.Frame{S.Level{args, binds} <> up, pp} == Grow.Frame{lvs, path} : Grow.Frame}} + em = Equal.trans(Maybe<&2, String>, S.miss.levels(l0), S.miss.levels(ll), Some{nm}, + Equal.cong(List<&2, S.Level>, Maybe<&2, String>, ls => S.miss.levels(ls), l0, ll, + Equal.trans(List<&2, S.Level>, l0, List.reverse(&2, S.Level, S.fill.levels(lvs)), ll, + Equal.cong(Grow.Frame, List<&2, S.Level>, fr => List.reverse(&2, S.Level, + S.fill.levels(Grow.gw.flevels(fr))), + Grow.Frame{S.Level{args, binds} <> up, pp}, Grow.Frame{lvs, path}, h_fr2), e_ll)), e1) + ep = Equal.cong(Grow.Frame, List<&2, String>, fr => Grow.gw.fpath(fr), Grow.Frame{S.Level{args, binds} <> up, pp}, + Grow.Frame{lvs, path}, h_fr2) + (nm, Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, S.parse.finish.miss(S.miss.levels(l0), pp, l0), + Fail{S.Missing{pp, nm}}, Fail{S.Missing{path, nm}}, + Equal.cong(Maybe<&2, String>, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.parse.finish.miss(xx, pp, l0), + S.miss.levels(l0), Some{nm}, em), + Equal.cong(List<&2, String>, Result<&2, &2, S.ParseErr, S.Matched>, qq => Fail{S.Missing{qq, nm}}, pp, path, + ep))) + case S.St{S.Dead{_e}, _a, _g, _s, _p, _b, _o, _r, _n}: + Empty.absurd(fin.missing.ty(S.St{S.Dead{_e}, _a, _g, _s, _p, _b, _o, _r, _n}, path), Walk.wk.false_true(h_free)) + case S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}: + Empty.absurd(fin.missing.ty(S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}, path), Walk.wk.false_true(h_free)) + case S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}: + Empty.absurd(fin.missing.ty(S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}, path), Walk.wk.false_true(h_free)) + +def Laws.required_missing( + spec, + ws, + qs, + pa, + qa, + nn, + sh, + lo, + kind, + hp, + dflt, + cs, + bs, + up, + path, + h_free, + h_fr, + h_unbound +): + +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa) + +fb = S.fill.args(args, bs) + +r0 = List.reverse(&2, S.Level, S.fill.levels(up)) + +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) + vr.miss(spec, ws, path, fin.missing(S.parse.walk(ws, S.parse.start(spec)), + List.append(&2, S.Level, qs, S.Level{args, bs} <> up), path, + List.append(&2, S.Level, r0, S.Level{args, fb} <> rq), h_free, h_fr, M1.fin.levels(qs, args, bs, up), + fin.miss_levels_some(r0, args, fb, rq, fin.miss_args_some(pa, qa, nn, sh, lo, kind, hp, dflt, cs, fb, h_unbound)))) diff --git a/proofs/RequiredMustBind.bend b/proofs/RequiredMustBind.bend new file mode 100644 index 0000000..29db9cc --- /dev/null +++ b/proofs/RequiredMustBind.bend @@ -0,0 +1,288 @@ +# proofs of laws/RequiredMustBind.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ../laws/RequiredMustBind.bend as Laws +import ./lemmas/ClusterEnd.bend as M1 +import ./lemmas/FinishedWalker.bend as M2 + +# anything and False is False +def fin.and_false(bb: Bool) -> {Bool.and(bb, False{}) == False{} : Bool}: + match bb: + case True{}: + {==} + case False{}: + {==} + +# what `fin.empty_rev` says after one char +def fin.empty_rev.ih(+tt: List<&2, String>, +hh: String, +acc: List<&2, String>) -> Type: + {List.is_empty(&2, String, List.reverse.go(&2, String, tt, hh <> acc)) + == Bool.and(List.is_empty(&2, String, tt), False{}) : Bool} + +# a nonempty accumulator keeps the reversal nonempty +def fin.empty_rev.cons( + +tt: List<&2, String>, + +hh: String, + +acc: List<&2, String>, + ih: fin.empty_rev.ih(tt, hh, acc) +) -> {List.is_empty(&2, String, List.reverse.go(&2, String, tt, hh <> acc)) == False{} : Bool}: + Equal.trans(Bool, List.is_empty(&2, String, List.reverse.go(&2, String, tt, hh <> acc)), + Bool.and(List.is_empty(&2, String, tt), False{}), False{}, ih, fin.and_false(List.is_empty(&2, String, tt))) + +# anything and True is itself +def fin.and_true(bb: Bool) -> {Bool.and(bb, True{}) == bb : Bool}: + match bb: + case True{}: + {==} + case False{}: + {==} + +# a list reversed onto another is empty exactly when both are +def fin.empty_rev( + xs: List<&2, String>, + +acc: List<&2, String> +) -> {List.is_empty(&2, String, List.reverse.go(&2, String, xs, acc)) + == Bool.and(List.is_empty(&2, String, xs), List.is_empty(&2, String, acc)) : Bool}: + match xs: + case Nil{}: + match acc: + case Nil{}: + {==} + case Con{_x, _t}: + {==} + case Con{+hh, +tt}: + fin.empty_rev.cons(tt, hh, acc, fin.empty_rev(tt, hh <> acc)) + +# a reversed list is empty exactly when the list is +def fin.empty_rev0( + +xs: List<&2, String> +) -> {List.is_empty(&2, String, List.reverse(&2, String, xs)) + == List.is_empty(&2, String, xs) : Bool}: + Equal.trans(Bool, List.is_empty(&2, String, List.reverse(&2, String, xs)), + Bool.and(List.is_empty(&2, String, xs), True{}), List.is_empty(&2, String, xs), fin.empty_rev(xs, []), + fin.and_true(List.is_empty(&2, String, xs))) + +# Unit for None, Empty for Some: a motive that tells them apart +def fin.none_ty(mm: Maybe<&2, String>) -> Type: + match mm: + case None{}: + Unit + case Some{_n}: + Empty + +# Some is never None +def fin.some_none(+nn: String, hh: {Some{nn} == None{} : Maybe<&2, String>}) -> Empty: + ee = Equal.sym(Maybe<&2, String>, Some{nn}, None{}, hh) + %ee : fin.none_ty(_) + Unit{} + +# a required check that found nothing here found nothing +def fin.miss_none( + mm: Maybe<&2, String>, + +path: List<&2, String>, + +lvs: List<&2, S.Level>, + +found: Shake.Matched, + hh: {S.parse.finish.miss(mm, path, lvs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} +) -> {mm == None{} : Maybe<&2, String>}: + match mm: + case None{}: + {==} + case Some{+nn}: + Empty.absurd({Some{nn} == None{} : Maybe<&2, String>}, Grow.gw.fail_done(S.Missing{path, nn}, found, hh)) + +# a walker that finishes as a success found no required argument missing +def fin.finished_miss( + fs: S.St, + +found: Shake.Matched, + hh: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} +) -> {S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs))))) == None{} + : Maybe<&2, String>}: + match fs: + case S.St{S.Free{}, +args, +up, _s, +path, +binds, _o, _r, _n}: + +lvs = List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up)) + fin.miss_none(S.miss.levels(lvs), path, lvs, found, hh) + case S.St{S.Dead{+ee}, +args, +up, _s, _p, +binds, _o, _r, _n}: + Empty.absurd({S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up))) == None{} + : Maybe<&2, String>}, Grow.gw.fail_done(ee, found, hh)) + case S.St{S.Need{+nn}, +args, +up, _s, +path, +binds, _o, _r, _n}: + Empty.absurd({S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up))) == None{} + : Maybe<&2, String>}, Grow.gw.fail_done(S.NoValue{path, nn}, found, hh)) + case S.St{S.Help{}, +args, +up, _s, +path, +binds, _o, _r, _n}: + Empty.absurd({S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up))) == None{} + : Maybe<&2, String>}, Grow.gw.fail_done(S.NeedHelp{path}, found, hh)) + +# a first-required pick that found nothing found nothing in either part +def fin.first_none_l( + here: Maybe<&2, String>, + +rest: Maybe<&2, String>, + hh: {S.parse.first_req.at(here, rest) == None{} : Maybe<&2, String>} +) -> {here == None{} : Maybe<&2, String>}: + match here: + case None{}: + {==} + case Some{+nn}: + Empty.absurd({Some{nn} == None{} : Maybe<&2, String>}, fin.some_none(nn, hh)) + +# the same, for the rest +def fin.first_none_r( + here: Maybe<&2, String>, + +rest: Maybe<&2, String>, + hh: {S.parse.first_req.at(here, rest) == None{} : Maybe<&2, String>} +) -> {rest == None{} : Maybe<&2, String>}: + match here: + case None{}: + hh + case Some{+nn}: + Empty.absurd({rest == None{} : Maybe<&2, String>}, fin.some_none(nn, hh)) + +# a required check over the levels that found nothing found nothing in this one +def fin.miss_mid( + r0: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +fb: List<&2, S.Bind>, + +rq: List<&2, S.Level>, + hh: {S.miss.levels(List.append(&2, S.Level, r0, S.Level{args, fb} <> rq)) == None{} : Maybe<&2, String>} +) -> {S.miss.args(args, fb) == None{} : Maybe<&2, String>}: + match r0: + case Nil{}: + fin.first_none_l(S.miss.args(args, fb), S.miss.levels(rq), hh) + case Con{ll, +tt}: + match ll: + case S.Level{+la, +lb}: + fin.miss_mid(tt, args, fb, rq, + fin.first_none_r(S.miss.args(la, lb), S.miss.levels(List.append(&2, S.Level, tt, S.Level{args, fb} <> rq)), + hh)) + +# a required check that found nothing found nothing further on +def fin.one_none( + need: Bool, + +nm: String, + +rest: Maybe<&2, String>, + hh: {S.miss.one(need, nm, rest) == None{} : Maybe<&2, String>} +) -> {rest == None{} : Maybe<&2, String>}: + match need: + case True{}: + Empty.absurd({rest == None{} : Maybe<&2, String>}, fin.some_none(nm, hh)) + case False{}: + hh + +# a required argument the check passed is bound +def fin.req_bound( + bb: Bool, + +nm: String, + +rest: Maybe<&2, String>, + hh: {S.miss.one(Bool.not(bb), nm, rest) == None{} : Maybe<&2, String>} +) -> {bb == True{} : Bool}: + match bb: + case True{}: + {==} + case False{}: + Empty.absurd({False{} == True{} : Bool}, fin.some_none(nm, hh)) + +# a command's arguments around a required one named `nn` +def fin.req_args( + +pa: List<&2, S.Arg>, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +dflt: S.Fallback, + +cs: List<&2, String>, + +qa: List<&2, S.Arg> +) -> List<&2, S.Arg>: + List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa) + +# a required check that found nothing leaves every required argument bound +def fin.miss_req( + pa: List<&2, S.Arg>, + +qa: List<&2, S.Arg>, + +nn: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +dflt: S.Fallback, + +cs: List<&2, String>, + +fb: List<&2, S.Bind>, + hh: {S.miss.args(fin.req_args(pa, nn, sh, lo, kind, hp, dflt, cs, qa), fb) == None{} : Maybe<&2, String>} +) -> {S.bound(fb, nn) == True{} : Bool}: + match pa: + case Nil{}: + fin.req_bound(S.bound(fb, nn), nn, S.miss.args(qa, fb), hh) + case Con{hd, +tt}: + match hd: + case S.Arg{+mm, _s, _l, _k, _h, +rq, _d, _c}: + fin.miss_req(tt, qa, nn, sh, lo, kind, hp, dflt, cs, fb, + fin.one_none(Bool.and(rq, Bool.not(S.bound(fb, mm))), mm, + S.miss.args(List.append(&2, S.Arg, tt, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), fb), hh)) + +def Laws.required_present( + spec, + ws, + found, + qs, + pa, + qa, + nn, + sh, + lo, + kind, + hp, + dflt, + cs, + bs, + up, + path, + ms, + h_done, + h_fr, + h_len +): + +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa) + +fb = S.fill.args(args, bs) + +fs = S.parse.walk(ws, S.parse.start(spec)) + +lvs = List.append(&2, S.Level, qs, S.Level{args, bs} <> up) + +r0 = List.reverse(&2, S.Level, S.fill.levels(up)) + +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) + +f0 = {S.Level{args, fb} : S.Level} + +bm = S.matched.build(f0 <> rq, ms) + +hd = {h_done : {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}} + +h_fr2 = {h_fr : {Grow.gw.frame(fs) == Grow.Frame{lvs, List.append(&2, String, path, ms)} : Grow.Frame}} + mn = Equal.trans(Maybe<&2, String>, S.miss.levels(List.append(&2, S.Level, r0, f0 <> rq)), + S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs))))), None{}, + Equal.cong(List<&2, S.Level>, Maybe<&2, String>, ls => S.miss.levels(ls), List.append(&2, S.Level, r0, f0 <> rq), + List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs)))), + Equal.sym(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs)))), + List.append(&2, S.Level, r0, f0 <> rq), + Equal.trans(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs)))), + List.reverse(&2, S.Level, S.fill.levels(lvs)), List.append(&2, S.Level, r0, f0 <> rq), + Equal.cong(Grow.Frame, List<&2, S.Level>, fr => List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(fr))), + Grow.gw.frame(fs), Grow.Frame{lvs, List.append(&2, String, path, ms)}, h_fr2), + M1.fin.levels(qs, args, bs, up)))), + fin.finished_miss(fs, found, M2.vr.done(spec, S.parse.finish(fs), found, hd))) + hb = fin.miss_req(pa, qa, nn, sh, lo, kind, hp, dflt, cs, fb, fin.miss_mid(r0, args, fb, rq, mn)) + eb = Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(S.own(bm), nn))), + Bool.not(List.is_empty(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, fb), nn))), True{}, + Equal.cong(List<&2, S.Bind>, Bool, xs => Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))), S.own(bm), + List.reverse(&2, S.Bind, fb), Grow.gw.own_build(f0, rq, ms)), + Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, fb), nn))), + Bool.not(List.is_empty(&2, String, List.reverse(&2, String, S.get_all.bind(fb, nn)))), True{}, + Equal.cong(List<&2, String>, Bool, xs => Bool.not(List.is_empty(&2, String, xs)), + S.get_all.bind(List.reverse(&2, S.Bind, fb), nn), List.reverse(&2, String, S.get_all.bind(fb, nn)), + M2.fin.g_rev(fb, nn)), + Equal.trans(Bool, Bool.not(List.is_empty(&2, String, List.reverse(&2, String, S.get_all.bind(fb, nn)))), + Bool.not(List.is_empty(&2, String, S.get_all.bind(fb, nn))), True{}, + Equal.cong(Bool, Bool, xx => Bool.not(xx), List.is_empty(&2, String, List.reverse(&2, String, + S.get_all.bind(fb, nn))), + List.is_empty(&2, String, S.get_all.bind(fb, nn)), fin.empty_rev0(S.get_all.bind(fb, nn))), + Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(fb, nn))), S.bound(fb, nn), True{}, + Equal.sym(Bool, S.bound(fb, nn), Bool.not(List.is_empty(&2, String, S.get_all.bind(fb, nn))), + M2.fin.bound_g(fb, nn)), hb)))) + Equal.trans(Maybe<&2, Bool>, Laws.present_at(found, path, nn), Laws.present_at.go(Some{bm}, nn), Some{True{}}, + Equal.cong(Maybe<&2, S.Matched>, Maybe<&2, Bool>, mm => Laws.present_at.go(mm, nn), S.at(found, path), Some{bm}, + M2.fin.at(spec, ws, found, qs, args, bs, up, path, ms, hd, h_fr2, h_len)), + Equal.cong(Bool, Maybe<&2, Bool>, xx => Some{xx}, Bool.not(List.is_empty(&2, String, + S.get_all.bind(S.own(bm), nn))), + True{}, eb)) diff --git a/proofs/RestTakesRemainingWords.bend b/proofs/RestTakesRemainingWords.bend new file mode 100644 index 0000000..85e6711 --- /dev/null +++ b/proofs/RestTakesRemainingWords.bend @@ -0,0 +1,334 @@ +# proofs of laws/RestTakesRemainingWords.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../src/walk.bend as Walk +import ../src/grow.bend as Grow +import ../laws/KeptAll.bend as L1 +import ../laws/RestTakesRemainingWords.bend as Laws +import ../laws/defs/ValuesAtPath.bend as D1 +import ./lemmas/BindingsKept.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +# a rest positional, as the walker holds it +def rest.arg( + +nm: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String> +) -> S.Arg: + S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs} + +# before `--`, plain words bind the rest one by one +def rest.walk( + fs: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + +nm: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +h_plain: {Walk.all_plain(fs) == True{} : Bool}, + +h_sub: {Walk.wk.unfound(subs, fs) == True{} : Bool}, + +h_ok: {Laws.all_allowed(cs, fs) == True{} : Bool} +) -> {S.parse.walk(fs, S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, + seen}) + == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], + False{}, Walk.wk.seen(fs, seen)} : S.St}: + match fs: + case Nil{}: + {==} + case Con{+ww, +tl}: + +pl = Walk.wk.and_l(Walk.plain_word(ww), Walk.all_plain(tl), h_plain) + su = Walk.wk.and_l(Maybe.is_none(&2, S.Sub, S.find_sub(subs, ww)), Walk.wk.unfound(subs, tl), h_sub) + ok = Walk.wk.and_l(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok) + ee = Equal.sym(S.St, + S.parse.step(ww, S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, + seen}), + S.St{S.Free{}, args, up, subs, path, S.Bind{nm, ww} <> bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, + True{}}, + M2.pos.step(ww, args, up, subs, path, bs, seen, nm, sh, lo, S.Rest{}, hp, req, dflt, cs, [], + Walk.wk.plain_dd(ww, pl), Walk.wk.plain_help(ww, pl), Walk.wk.plain_dash(ww, pl), + Walk.wk.none_of(S.find_sub(subs, ww), su), ok)) + %ee : {S.parse.walk(tl, _) + == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(tl, nm, S.Bind{nm, ww} <> bs), + [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, Walk.wk.seen(tl, True{})} : S.St} + rest.walk(tl, args, up, subs, path, S.Bind{nm, ww} <> bs, True{}, nm, sh, lo, hp, req, dflt, cs, + Walk.wk.and_r(Walk.plain_word(ww), Walk.all_plain(tl), h_plain), + Walk.wk.and_r(Maybe.is_none(&2, S.Sub, S.find_sub(subs, ww)), Walk.wk.unfound(subs, tl), h_sub), + Walk.wk.and_r(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok)) + +# after `--`, every word binds the rest one by one +def rest.raw( + fs: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + +nm: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +h_ok: {Laws.all_allowed(cs, fs) == True{} : Bool} +) -> {S.parse.walk(fs, S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, + seen}) + == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], + True{}, Walk.wk.seen(fs, seen)} : S.St}: + match fs: + case Nil{}: + {==} + case Con{+ww, +tl}: + ee = Equal.sym(S.St, + S.parse.take_pos(ww, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}), + S.St{S.Free{}, args, up, subs, path, S.Bind{nm, ww} <> bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, + True{}}, + M2.pos.take(ww, args, up, subs, path, bs, True{}, nm, sh, lo, S.Rest{}, hp, req, dflt, cs, [], + Walk.wk.and_l(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok))) + %ee : {S.parse.walk(tl, _) + == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(tl, nm, S.Bind{nm, ww} <> bs), + [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, Walk.wk.seen(tl, True{})} : S.St} + rest.raw(tl, args, up, subs, path, S.Bind{nm, ww} <> bs, True{}, nm, sh, lo, hp, req, dflt, cs, + Walk.wk.and_r(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok)) + +def Laws.rest_next( + spec, + pre, + fs, + args, + up, + subs, + path, + bs, + seen, + nm, + sh, + lo, + hp, + req, + dflt, + cs, + h_at, + h_plain, + h_sub, + h_ok +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, seen} : S.St} + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)), S.parse.walk(fs, st), + S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, + Walk.wk.seen(fs, seen)}, + M2.rest.after(spec, pre, fs, st, h_at), + rest.walk(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_plain, h_sub, h_ok)) + +def Laws.rest_raw_next(spec, pre, fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_at, h_ok): + +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, seen} : S.St} + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)), S.parse.walk(fs, st), + S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, + Walk.wk.seen(fs, seen)}, + M2.rest.after(spec, pre, fs, st, h_at), + rest.raw(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_ok)) + +# the bindings a walk pushes onto some it had are those it pushes onto none, +# then those +def rest.pushed( + fs: List<&2, String>, + +nm: String, + +acc: List<&2, S.Bind>, + +bs: List<&2, S.Bind> +) -> {Walk.wk.pushed(fs, nm, List.append(&2, S.Bind, acc, bs)) + == List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, acc), bs) : List<&2, S.Bind>}: + match fs: + case Nil{}: + {==} + case Con{+ww, tl}: + rest.pushed(tl, nm, S.Bind{nm, ww} <> acc, bs) + +# one binding of `nm` per word, in order +def rest.bs(fs: List<&2, String>, +nm: String) -> List<&2, S.Bind>: + match fs: + case Nil{}: + [] + case Con{+ww, tl}: + S.Bind{nm, ww} <> rest.bs(tl, nm) + +# the pushed bindings, reversed onto a list, are one per word in order +def rest.rev( + fs: List<&2, String>, + +nm: String, + +acc: List<&2, S.Bind>, + +out: List<&2, S.Bind> +) -> {List.reverse.go(&2, S.Bind, Walk.wk.pushed(fs, nm, acc), out) + == List.reverse.go(&2, S.Bind, acc, List.append(&2, S.Bind, rest.bs(fs, nm), out)) : List<&2, S.Bind>}: + match fs: + case Nil{}: + {==} + case Con{+ww, tl}: + rest.rev(tl, nm, S.Bind{nm, ww} <> acc, out) + +# bindings with nothing after them +def rest.nil_b(bs: List<&2, S.Bind>) -> {List.append(&2, S.Bind, bs, []) == bs : List<&2, S.Bind>}: + match bs: + case Nil{}: + {==} + case Con{+hh, tt}: + Equal.cong(List<&2, S.Bind>, List<&2, S.Bind>, xs => hh <> xs, List.append(&2, S.Bind, tt, + []), tt, rest.nil_b(tt)) + +# one binding of `nm` per word reads back the words +def rest.vals(fs: List<&2, String>, +nm: String) -> {S.get_all.bind(rest.bs(fs, nm), nm) == fs : List<&2, String>}: + match fs: + case Nil{}: + {==} + case Con{+ww, +tl}: + ee = Equal.sym(Bool, String.eq(nm, nm), True{}, Eq.string_eq_self(nm)) + %ee : {S.get_all.bind.hit(_, ww, S.get_all.bind(rest.bs(tl, nm), nm)) == ww <> tl : List<&2, String>} + Equal.cong(List<&2, String>, List<&2, String>, xs => ww <> xs, S.get_all.bind(rest.bs(tl, nm), nm), tl, + rest.vals(tl, nm)) + +# the rest's values the words pushed, read back in order, are the words +def rest.read( + +fs: List<&2, String>, + +nm: String +) -> {S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm) == fs : List<&2, String>}: + Equal.trans(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm), + S.get_all.bind(rest.bs(fs, nm), nm), fs, + Equal.cong(List<&2, S.Bind>, List<&2, String>, xs => S.get_all.bind(xs, nm), + List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), rest.bs(fs, nm), + Equal.trans(List<&2, S.Bind>, List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), + List.append(&2, S.Bind, rest.bs(fs, nm), []), rest.bs(fs, nm), rest.rev(fs, nm, [], []), + rest.nil_b(rest.bs(fs, nm)))), + rest.vals(fs, nm)) + +# what `rest_binds` claims +def rest.ty( + +found: Shake.Matched, + +path: List<&2, String>, + +nm: String, + +fs: List<&2, String>, + +bs: List<&2, S.Bind> +) -> Type: + &after: List<&2, String> -> {D1.all_at(found, path, nm) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nm), List.append(&2, String, + fs, after))} + : Maybe<&2, List<&2, String>>} + +# the pushed bindings read back as the words +def rest.back( + +found: Shake.Matched, + +path: List<&2, String>, + +nm: String, + +fs: List<&2, String>, + +bs: List<&2, S.Bind>, + ww: M1.kept_all.ty(found, path, nm, Walk.wk.pushed(fs, nm, []), bs) +) -> rest.ty(found, path, nm, fs, bs): + (ax, ee) = ww + +after = {ax : List<&2, String>} + +gb = S.get_all.bind(List.reverse(&2, S.Bind, bs), nm) + (after, Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, nm), + Some{List.append(&2, String, gb, + List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm), after))}, + Some{List.append(&2, String, gb, List.append(&2, String, fs, after))}, ee, + Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, + xs => Some{List.append(&2, String, gb, List.append(&2, String, xs, after))}, + S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm), fs, rest.read(fs, nm)))) + +# the view after the words, from the walker's state after them +def rest.view( + +fs: List<&2, String>, + +st: S.St, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +nm: String, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + ew: {S.parse.walk(fs, st) == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), pos, raw, seen} : S.St} +) -> {Grow.view(S.parse.walk(fs, st)) + == Grow.View{args, up, path, List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, []), bs)} : Grow.View}: + Equal.trans(Grow.View, Grow.view(S.parse.walk(fs, st)), Grow.View{args, up, path, Walk.wk.pushed(fs, nm, bs)}, + Grow.View{args, up, path, List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, []), bs)}, + Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.walk(fs, st), + S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), pos, raw, seen}, ew), + Equal.cong(List<&2, S.Bind>, Grow.View, xs => Grow.View{args, up, path, xs}, Walk.wk.pushed(fs, nm, bs), + List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, []), bs), rest.pushed(fs, nm, [], bs))) + +def Laws.rest_binds( + spec, + pre, + fs, + more, + args, + up, + subs, + path, + bs, + seen, + nm, + sh, + lo, + hp, + req, + dflt, + cs, + found, + h_at, + h_plain, + h_sub, + h_ok, + h_done +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, seen} : S.St} + rest.back(found, path, nm, fs, bs, + L1.kept_all(spec, pre, fs, more, st, args, up, path, Walk.wk.pushed(fs, nm, []), bs, nm, found, h_at, + rest.view(fs, st, args, up, subs, path, bs, nm, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, + Walk.wk.seen(fs, seen), + rest.walk(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_plain, h_sub, h_ok)), + h_done)) + +def Laws.rest_raw_binds( + spec, + pre, + fs, + more, + args, + up, + subs, + path, + bs, + seen, + nm, + sh, + lo, + hp, + req, + dflt, + cs, + found, + h_at, + h_ok, + h_done +): + +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, seen} : S.St} + rest.back(found, path, nm, fs, bs, + L1.kept_all(spec, pre, fs, more, st, args, up, path, Walk.wk.pushed(fs, nm, []), bs, nm, found, h_at, + rest.view(fs, st, args, up, subs, path, bs, nm, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, + Walk.wk.seen(fs, seen), rest.raw(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_ok)), + h_done)) diff --git a/proofs/ShortClusterReadsLetters.bend b/proofs/ShortClusterReadsLetters.bend new file mode 100644 index 0000000..44f4911 --- /dev/null +++ b/proofs/ShortClusterReadsLetters.bend @@ -0,0 +1,139 @@ +# proofs of laws/ShortClusterReadsLetters.bend +import Base +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../src/walk.bend as Walk +import ../laws/ShortClusterReadsLetters.bend as Laws +import ../laws/defs/ArgumentLists.bend as D1 +import ./lemmas/ClusterSteps.bend as M1 + +def Laws.short_step(cc, rest, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_help): + +tok = "-" ++ SCon{cc, rest} + +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e1 = Equal.sym(Bool, String.eq(tok, "--"), False{}, h_dd) + %e1 : {S.parse.step.end(_, tok, args, up, subs, path, bs, pos, seen) == S.cluster(SCon{cc, rest}, tok, st, + False{}) : S.St} + e4 = Equal.sym(Cmp, String.order.go(SCon{cc, rest}, "elp", LT{}), LT{}, Eq.string_order_lt(SCon{cc, rest}, "elp")) + %e4 : {S.parse.step.help(Bool.and(Cmp.is_eq(_), Bool.not(seen)), tok, args, up, subs, path, bs, pos, seen) + == S.cluster(SCon{cc, rest}, tok, st, False{}) : S.St} + e3 = Equal.sym(Bool, Char.is_eq(cc, '-'), False{}, h_dash) + %e3 : {S.parse.step.kind.go(String.starts_with.go.if("", rest, _), True{}, tok, args, up, subs, path, bs, pos, seen) + == S.cluster(SCon{cc, rest}, tok, st, False{}) : S.St} + e5 = Equal.sym(Bool, S.short_help(SCon{cc, rest}, args, seen), False{}, h_help) + %e5 : {S.parse.take_short.go(_, SCon{cc, rest}, tok, args, up, subs, path, bs, pos, False{}, seen) + == S.cluster(SCon{cc, rest}, tok, st, False{}) : S.St} + {==} + +def Laws.letter_flag( + cc, + rest, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + hp, + req, + dflt, + cs, + h_found, + h_once, + h_noeq +): + +aa = {S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs} : S.Arg} + +rhs = {S.cluster(rest, orig, S.St{S.Free{}, args, up, subs, path, S.Bind{nn, "true"} <> bs, pos, raw, seen}, False{}) + : S.St} + e0 = M1.cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {_ == rhs : S.St} + e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) + %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Flag{}, nn, S.cluster.val.flag(_, rest), orig, args, up, + subs, path, + bs, pos, raw, seen), _) == rhs : S.St} + e2 = Equal.sym(Bool, S.bound(bs, nn), False{}, h_once) + %e2 : {S.cluster(rest, orig, S.parse.once(_, S.parse.take_flag(None{}, nn, orig, args, up, subs, path, bs, + pos, raw, seen), + nn, path), False{}) == rhs : S.St} + {==} + +# what `ln` claims of an argument kind +def ln.ty( + kk: S.ArgKind, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String +) -> Type: + {S.cluster(SNil{}, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, SNil{}), orig, args, up, subs, + path, bs, pos, raw, seen), + S.cluster.ends.kind(kk, SNil{})) == S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} + +# a letter spelling an option that takes a value now, last in its word +def ln( + kk: S.ArgKind, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String, + h_kind: {D1.takes(kk, S.bound(bs, nn)) == True{} : Bool} +) -> ln.ty(kk, orig, args, up, subs, path, bs, pos, raw, seen, nn): + match kk: + case S.Opt{}: + e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) + %e3 : {S.parse.once(_, S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen}, nn, + path) == S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} + {==} + case S.Many{}: + {==} + case S.Flag{}: + Empty.absurd(ln.ty(S.Flag{}, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + case S.Pos{}: + Empty.absurd(ln.ty(S.Pos{}, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + case S.Rest{}: + Empty.absurd(ln.ty(S.Rest{}, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + +def Laws.letter_value_next( + cc, + orig, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + nn, + sh, + lo, + kind, + hp, + req, + dflt, + cs, + h_found, + h_kind +): + +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} + +T = {S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} + e0 = M1.cl.back(cc, SNil{}, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) + %e0 : {_ == T : S.St} + ln(kind, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind) diff --git a/proofs/SpecErrWhere.bend b/proofs/SpecErrWhere.bend new file mode 100644 index 0000000..544a91f --- /dev/null +++ b/proofs/SpecErrWhere.bend @@ -0,0 +1,23 @@ +# proofs of laws/SpecErrWhere.bend +import Base +import ../src/check.bend as K +import ../laws/SpecErrWhere.bend as Laws + +def Laws.spec_err_where(err): + match err: + case K.RestNotLast{+path, +name}: + ("rest positional '" ++ name ++ "' is not the last positional", {==}) + case K.SameName{+path, +name}: + ("two arguments are named '" ++ name ++ "'", {==}) + case K.SameShort{+path, +short}: + ("two arguments are spelled '-" ++ short ++ "'", {==}) + case K.SameLong{+path, +long}: + ("two arguments are spelled '--" ++ long ++ "'", {==}) + case K.SameSub{+path, +name}: + ("two subcommands are named '" ++ name ++ "'", {==}) + case K.HelpSub{+path}: + ("a subcommand is named 'help', which `parse` keeps for help", {==}) + case K.DefaultNotChoice{+path, +name, +value}: + ("the default '" ++ value ++ "' of '" ++ name ++ "' is not one of its choices", {==}) + case K.RequiredAfterOptional{+path, +name}: + ("required positional '" ++ name ++ "' follows an optional one", {==}) diff --git a/proofs/StepDies.bend b/proofs/StepDies.bend new file mode 100644 index 0000000..29596df --- /dev/null +++ b/proofs/StepDies.bend @@ -0,0 +1,6 @@ +# proofs of laws/StepDies.bend +import Base +import ../laws/StepDies.bend as Laws +import ./lemmas/FailedWalkThrough.bend as M1 + + diff --git a/proofs/SubcommandResultsNavigate.bend b/proofs/SubcommandResultsNavigate.bend new file mode 100644 index 0000000..9e790ce --- /dev/null +++ b/proofs/SubcommandResultsNavigate.bend @@ -0,0 +1,59 @@ +# proofs of laws/SubcommandResultsNavigate.bend +import Base +import ../src/cli.bend as S +import ../src/eq.bend as Eq +import ../laws/SubcommandResultsNavigate.bend as Laws + +def Laws.sub_name_leaf(_bs): + {==} + +def Laws.sub_name_node(_bs, _nn, _below): + {==} + +def Laws.sub_of_hit(_bs, nn, below): + ee = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) + %ee : {S.sub_of.pick(_, below) == Some{below} : Maybe<&2, S.Matched>} + {==} + +def Laws.sub_of_miss(_bs, nn, below, name, differ): + ee = Equal.sym(Bool, String.eq(nn, name), False{}, differ) + %ee : {S.sub_of.pick(_, below) == None{} : Maybe<&2, S.Matched>} + {==} + +def Laws.sub_of_leaf(_bs, _name): + {==} + +def Laws.path_of_leaf(_bs): + {==} + +def Laws.path_of_node(_bs, _nn, _below): + {==} + +def Laws.at_nil(found): + match found: + case S.Leaf{_b}: + {==} + case S.Node{_b, _n, _s}: + {==} + +# a walk down that found nothing finds nothing further down +def at_cons.none(rest: List<&2, String>) -> {S.at.go(rest, None{}) == None{} : Maybe<&2, S.Matched>}: + match rest: + case Nil{}: + {==} + case Con{_h, tt}: + at_cons.none(tt) + +# the rest of a walk down, from what the first name found +def at_cons.via( + mm: Maybe<&2, S.Matched>, + +rest: List<&2, String> +) -> {S.at.go(rest, mm) == Laws.at.via(mm, rest) : Maybe<&2, S.Matched>}: + match mm: + case None{}: + at_cons.none(rest) + case Some{_m}: + {==} + +def Laws.at_cons(found, name, rest): + at_cons.via(S.sub_of(found, name), rest) diff --git a/proofs/SubcommandSelects.bend b/proofs/SubcommandSelects.bend new file mode 100644 index 0000000..387e087 --- /dev/null +++ b/proofs/SubcommandSelects.bend @@ -0,0 +1,106 @@ +# proofs of laws/SubcommandSelects.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/SubcommandSelects.bend as Laws +import ../laws/defs/BlockingPositional.bend as D1 + +# a positional that does not block a subcommand is skipped: a rest, one not +# required, or one with a default +def blk.req( + rk: Bool, + req: Bool, + +ev: Maybe<&2, String>, + dflt: Maybe<&2, String>, + +nm: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +cs: List<&2, String>, + hh: {Bool.not(Bool.and(Bool.not(rk), Bool.and(req, Maybe.is_none(&2, String, dflt)))) == True{} : Bool} +) -> {S.parse.first_req.skip(rk, S.Arg{nm, sh, lo, kind, hp, req, S.Fallback{ev, dflt}, cs}) == None{} : Maybe<&2, + String>}: + match rk: + case True{}: + {==} + case False{}: + match req: + case False{}: + {==} + case True{}: + match dflt: + case Some{_d}: + {==} + case None{}: + Empty.absurd({S.parse.first_req.skip(False{}, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, + None{}}, cs}) == None{} + : Maybe<&2, String>}, Walk.wk.false_true(hh)) + +# the same, from the argument's kind +def blk.skip( + +kind: S.ArgKind, + +req: Bool, + +dflt: S.Fallback, + +nm: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +cs: List<&2, String>, + hh: {Bool.not(D1.blocks(S.Arg{nm, sh, lo, kind, hp, req, dflt, cs})) == True{} : Bool} +) -> {S.parse.first_req.skip(S.rest.is.kind(kind), S.Arg{nm, sh, lo, kind, hp, req, dflt, cs}) == None{} + : Maybe<&2, String>}: + match dflt: + case S.Fallback{+ev, dm}: + blk.req(S.rest.is.kind(kind), req, ev, dm, nm, sh, lo, kind, hp, cs, hh) + +# past a positional that does not block, the first blocking one is the rest's +def blk.past( + +kind: S.ArgKind, + +req: Bool, + +dflt: S.Fallback, + +nm: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +hp: String, + +cs: List<&2, String>, + +tt: List<&2, S.Arg>, + hh: {Bool.not(D1.blocks(S.Arg{nm, sh, lo, kind, hp, req, dflt, cs})) == True{} : Bool} +) -> {S.parse.first_req(S.Arg{nm, sh, lo, kind, hp, req, dflt, cs} <> tt) == S.parse.first_req(tt) : Maybe<&2, String>}: + ee = Equal.sym(Maybe<&2, String>, + S.parse.first_req.skip(S.rest.is.kind(kind), S.Arg{nm, sh, lo, kind, hp, req, dflt, cs}), None{}, + blk.skip(kind, req, dflt, nm, sh, lo, hp, cs, hh)) + %ee : {S.parse.first_req.at(_, S.parse.first_req(tt)) == S.parse.first_req(tt) : Maybe<&2, String>} + {==} + +def Laws.none_blocking(pos, h_none): + match pos: + case Nil{}: + {==} + case Con{hd, +tt}: + match hd: + case S.Arg{+nm, +sh, +lo, +kind, +hp, +req, +dflt, +cs}: + +ab = {S.Arg{nm, sh, lo, kind, hp, req, dflt, cs} : S.Arg} + Equal.trans(Maybe<&2, String>, S.parse.first_req(ab <> tt), S.parse.first_req(tt), None{}, + blk.past(kind, req, dflt, nm, sh, lo, hp, cs, tt, + Walk.wk.and_l(Bool.not(D1.blocks(ab)), Laws.none_block(tt), h_none)), + Laws.none_blocking(tt, Walk.wk.and_r(Bool.not(D1.blocks(ab)), Laws.none_block(tt), h_none))) + +def Laws.first_blocking(pa, nm, sh, lo, kind, hp, ev, cs, qa, h_pa, h_kind): + match pa: + case Nil{}: + ee = Equal.sym(Bool, S.rest.is.kind(kind), False{}, h_kind) + %ee : {S.parse.first_req.at(S.parse.first_req.skip(_, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, + None{}}, cs}), + S.parse.first_req(qa)) == Some{nm} : Maybe<&2, String>} + {==} + case Con{hd, +tt}: + match hd: + case S.Arg{+n2, +s2, +l2, +k2, +h2, +r2, +d2, +c2}: + +ab = {S.Arg{n2, s2, l2, k2, h2, r2, d2, c2} : S.Arg} + +rest = List.append(&2, S.Arg, tt, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, None{}}, cs} <> qa) + Equal.trans(Maybe<&2, String>, S.parse.first_req(ab <> rest), S.parse.first_req(rest), Some{nm}, + blk.past(k2, r2, d2, n2, s2, l2, h2, c2, rest, Walk.wk.and_l(Bool.not(D1.blocks(ab)), + Laws.none_block(tt), h_pa)), + Laws.first_blocking(tt, nm, sh, lo, kind, hp, ev, cs, qa, + Walk.wk.and_r(Bool.not(D1.blocks(ab)), Laws.none_block(tt), h_pa), h_kind)) diff --git a/proofs/SuggestionPicksClosestLong.bend b/proofs/SuggestionPicksClosestLong.bend new file mode 100644 index 0000000..e167b47 --- /dev/null +++ b/proofs/SuggestionPicksClosestLong.bend @@ -0,0 +1,121 @@ +# proofs of laws/SuggestionPicksClosestLong.bend +import Base +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/DropZero.bend as L1 +import ../laws/PrefixEmpty.bend as L2 +import ../laws/SuggestionPicksClosestLong.bend as Laws +import ../laws/defs/HelpPages.bend as D1 +import ./lemmas/WalkSteps.bend as M1 + +def Laws.sug_other(spec, err, h_err): + match err: + case S.UnknownFlag{at, word}: + Empty.absurd({S.suggestion(spec, S.UnknownFlag{at, word}) == None{} : Maybe<&2, String>}, + Walk.wk.true_false(h_err)) + case S.Missing{_at, _name}: + {==} + case S.NoValue{_at, _name}: + {==} + case S.BadValue{_at, _name, _value}: + {==} + case S.NeedHelp{_path}: + {==} + case S.Unexpected{_at, _arg}: + {==} + case S.Repeated{_at, _name}: + {==} + case S.NeedVersion{}: + {==} + +def Laws.sug_short(spec, at, word, h_word): + e1 = Equal.sym(Bool, String.starts_with(word, "--"), False{}, h_word) + %e1 : {S.sug.word.go(_, spec, at, word) == None{} : Maybe<&2, String>} + {==} + +# the arguments reached down a path are those of the page `help` reaches +def sa.go( + path: List<&2, String>, + how: S.How +) -> {S.sug.args.go(path, how) == Laws.how_args(D1.reach(path, how)) : List<&2, S.Arg>}: + match path: + case Nil{}: + match how: + case S.How{_b, _a, _v, _g, _s, _u}: + {==} + case Con{+hh, tt}: + sa.go(tt, S.help.step(how, hh)) + +# the arguments of the command at a path are those of its help page +def sa.reach( + spec: S.Cli, + path: List<&2, String> +) -> {S.sug.args(spec, path) == Laws.how_args(D1.reach(path, D1.root(spec))) : List<&2, S.Arg>}: + match spec: + case S.Cli{+n, a, v, args, subs}: + sa.go(path, S.How{n, a, v, args, subs, n}) + +def Laws.sug_long(spec, at, body, name, val, h_cut): + e1 = Equal.sym(Bool, String.starts_with(body, ""), True{}, L2.prefix_empty(body)) + %e1 : {S.sug.word.go(_, spec, at, "--" ++ body) + == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(D1.reach(at, D1.root(spec)))), name)) + : Maybe<&2, String>} + e2 = Equal.sym(String, String.drop(body, 0n), body, L1.drop_zero(body)) + %e2 : {S.sug.found(S.sug.best(S.sug.cands(S.sug.args(spec, at)), S.sug.typed(S.cut_eq(_)))) + == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(D1.reach(at, D1.root(spec)))), name)) + : Maybe<&2, String>} + e3 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) + %e3 : {S.sug.found(S.sug.best(S.sug.cands(S.sug.args(spec, at)), S.sug.typed(_))) + == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(D1.reach(at, D1.root(spec)))), name)) + : Maybe<&2, String>} + e4 = Equal.sym(List<&2, S.Arg>, S.sug.args(spec, at), Laws.how_args(D1.reach(at, D1.root(spec))), + sa.reach(spec, at)) + %e4 : {S.sug.found(S.sug.best(S.sug.cands(_), name)) + == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(D1.reach(at, D1.root(spec)))), name)) + : Maybe<&2, String>} + {==} + +def Laws.sug_found_some(_cc): + {==} + +def Laws.cands_some(_nn, _sh, _ll, _kind, _hp, _req, _fb, _cs, _tt): + {==} + +def Laws.cands_none(_nn, _sh, _kind, _hp, _req, _fb, _cs, _tt): + {==} + +def Laws.sug_close(_typed, _cc): + {==} + +def Laws.best_nil(_typed): + {==} + +def Laws.best_far(cc, tt, typed, h_far): + e1 = Equal.sym(Bool, S.sug.close(typed, cc), False{}, h_far) + %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == S.sug.best(tt, typed) : Maybe<&2, String>} + {==} + +def Laws.best_only(cc, tt, typed, h_close, h_rest): + e1 = Equal.sym(Bool, S.sug.close(typed, cc), True{}, h_close) + %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == Some{cc} : Maybe<&2, String>} + e2 = Equal.sym(Maybe<&2, String>, S.sug.best(tt, typed), None{}, h_rest) + %e2 : {S.sug.keep(True{}, typed, cc, _) == Some{cc} : Maybe<&2, String>} + {==} + +def Laws.best_here(cc, tt, typed, ww, h_close, h_rest, h_le): + e1 = Equal.sym(Bool, S.sug.close(typed, cc), True{}, h_close) + %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == Some{cc} : Maybe<&2, String>} + e2 = Equal.sym(Maybe<&2, String>, S.sug.best(tt, typed), Some{ww}, h_rest) + %e2 : {S.sug.keep(True{}, typed, cc, _) == Some{cc} : Maybe<&2, String>} + e3 = Equal.sym(Bool, Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)), True{}, h_le) + %e3 : {Bool.pick(Maybe<&2, String>, _, Some{cc}, Some{ww}) == Some{cc} : Maybe<&2, String>} + {==} + +def Laws.best_there(cc, tt, typed, ww, h_close, h_rest, h_le): + e1 = Equal.sym(Bool, S.sug.close(typed, cc), True{}, h_close) + %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == Some{ww} : Maybe<&2, String>} + e2 = Equal.sym(Maybe<&2, String>, S.sug.best(tt, typed), Some{ww}, h_rest) + %e2 : {S.sug.keep(True{}, typed, cc, _) == Some{ww} : Maybe<&2, String>} + e3 = Equal.sym(Bool, Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)), False{}, h_le) + %e3 : {Bool.pick(Maybe<&2, String>, _, Some{cc}, Some{ww}) == Some{ww} : Maybe<&2, String>} + {==} diff --git a/proofs/UnknownLong.bend b/proofs/UnknownLong.bend new file mode 100644 index 0000000..3110f54 --- /dev/null +++ b/proofs/UnknownLong.bend @@ -0,0 +1,39 @@ +# proofs of laws/UnknownLong.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../laws/UnknownLong.bend as Laws +import ../laws/UnknownLongDies.bend as L1 +import ./lemmas/FailedWalkThrough.bend as M1 +import ./lemmas/WalkSteps.bend as M2 + +def Laws.unknown_long( + spec, + pre, + more, + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + h_at, + h_dd, + h_cut, + h_name, + h_none, + h_lh, + h_ver +): + +ww = List.append(&2, String, pre, ["--" ++ body]) + +er = {S.UnknownFlag{path, "--" ++ body} : S.ParseErr} + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, List.append(&2, String, ww, more)), + Fail{S.version.err(spec, er)}, Fail{er}, + M1.fs.fail(spec, ww, more, er, + L1.unknown_long_dies(spec, pre, body, name, val, args, up, subs, path, bs, pos, seen, + h_at, h_dd, h_cut, h_name, h_none, h_lh)), + M1.fs.keep(spec, path, "--" ++ body, h_ver)) diff --git a/proofs/UnknownLongDies.bend b/proofs/UnknownLongDies.bend new file mode 100644 index 0000000..7f13811 --- /dev/null +++ b/proofs/UnknownLongDies.bend @@ -0,0 +1,6 @@ +# proofs of laws/UnknownLongDies.bend +import Base +import ../laws/UnknownLongDies.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + + diff --git a/proofs/UnknownLongStep.bend b/proofs/UnknownLongStep.bend new file mode 100644 index 0000000..f165fe4 --- /dev/null +++ b/proofs/UnknownLongStep.bend @@ -0,0 +1,6 @@ +# proofs of laws/UnknownLongStep.bend +import Base +import ../laws/UnknownLongStep.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + + diff --git a/proofs/UnknownShort.bend b/proofs/UnknownShort.bend new file mode 100644 index 0000000..1464366 --- /dev/null +++ b/proofs/UnknownShort.bend @@ -0,0 +1,64 @@ +# proofs of laws/UnknownShort.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../laws/Refused.bend as L1 +import ../laws/UnknownShort.bend as Laws +import ../laws/UnknownShortStep.bend as L2 +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/ClusterEnd.bend as M1 +import ./lemmas/FailedWalkThrough.bend as M2 + +# the first letter of a short cluster that no argument has fails the step +def unknown_short.step( + +cc: Char, + +rest: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool}, + h_dash: {Char.is_eq(cc, '-') == False{} : Bool}, + h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>}, + h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} +) -> {D1.dead_of(S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, + seen})) == Some{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}} + : Maybe<&2, S.ParseErr>}: + ee = Equal.sym(S.St, S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, + False{}, seen}), S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, + rest}}}, args, up, subs, path, bs, pos, False{}, seen}, + L2.unknown_short_step(cc, rest, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_none, h_help)) + %ee : {D1.dead_of(_) == Some{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.unknown_short( + spec, + pre, + more, + cc, + rest, + args, + up, + subs, + path, + bs, + pos, + seen, + h_at, + h_dd, + h_dash, + h_none, + h_help, + h_ver +): + +er = {S.UnknownFlag{path, "-" ++ SCon{cc, rest}} : S.ParseErr} + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, + Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["-" ++ SCon{cc, rest}]), more)), + Fail{S.version.err(spec, er)}, Fail{er}, + L1.refused(spec, pre, more, "-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, + seen}, er, h_at, + unknown_short.step(cc, rest, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_none, h_help)), + M2.fs.keep(spec, path, "-" ++ SCon{cc, rest}, h_ver)) diff --git a/proofs/UnknownShortStep.bend b/proofs/UnknownShortStep.bend new file mode 100644 index 0000000..c738a43 --- /dev/null +++ b/proofs/UnknownShortStep.bend @@ -0,0 +1,6 @@ +# proofs of laws/UnknownShortStep.bend +import Base +import ../laws/UnknownShortStep.bend as Laws +import ./lemmas/ClusterEnd.bend as M1 + + diff --git a/proofs/ValueAbsent.bend b/proofs/ValueAbsent.bend new file mode 100644 index 0000000..50883fb --- /dev/null +++ b/proofs/ValueAbsent.bend @@ -0,0 +1,23 @@ +# proofs of laws/ValueAbsent.bend +import Base +import ../src/cli.bend as S +import ../laws/ValueAbsent.bend as Laws + +def Laws.value_absent( + spec, + ws, + name, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + h_at +): + ee = Equal.sym(S.St, S.parse.walk(ws, S.parse.start(spec)), + S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, h_at) + %ee : {S.version.res(spec, S.parse.finish(_)) == Fail{S.NoValue{path, name}} : Result<&2, &2, S.ParseErr, S.Matched>} + {==} diff --git a/proofs/ValueBinds.bend b/proofs/ValueBinds.bend new file mode 100644 index 0000000..f2e1477 --- /dev/null +++ b/proofs/ValueBinds.bend @@ -0,0 +1,59 @@ +# proofs of laws/ValueBinds.bend +import Base +import ../src/cli.bend as S +import ../src/grow.bend as Grow +import ../laws/Kept.bend as L1 +import ../laws/ValueBinds.bend as Laws +import ./lemmas/BindingsKept.bend as M1 + +# an option's value that is not flag-shaped and is allowed binds +def value.step( + +tok: String, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + h_flag: {S.looks_flag(tok) == False{} : Bool}, + h_ok: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == True{} : Bool} +) -> {S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen} : S.St}: + e1 = Equal.sym(Bool, S.looks_flag(tok), False{}, h_flag) + %e1 : {S.parse.step.need(_, name, tok, args, up, subs, path, bs, pos, raw, seen) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen} : S.St} + e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, name)), tok), True{}, h_ok) + %e2 : {S.parse.take_choice(_, name, tok, args, up, subs, path, bs, pos, raw, seen) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen} : S.St} + {==} + +def Laws.value_binds( + spec, + pre, + more, + tok, + name, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + found, + h_at, + h_flag, + h_ok, + h_done +): + L1.kept(spec, pre, more, tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, args, up, path, + name, tok, bs, found, h_at, + Equal.cong(S.St, Grow.View, xx => Grow.view(xx), + S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen}, + value.step(tok, name, args, up, subs, path, bs, pos, raw, seen, h_flag, h_ok)), + h_done) diff --git a/proofs/ValueFlagShaped.bend b/proofs/ValueFlagShaped.bend new file mode 100644 index 0000000..5bfff0b --- /dev/null +++ b/proofs/ValueFlagShaped.bend @@ -0,0 +1,47 @@ +# proofs of laws/ValueFlagShaped.bend +import Base +import ../src/cli.bend as S +import ../laws/Refused.bend as L1 +import ../laws/ValueFlagShaped.bend as Laws +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 + +# a flag-shaped word where a value is wanted fails the step +def value_flag_shaped.step( + +tok: String, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + h_flag: {S.looks_flag(tok) == True{} : Bool} +) -> {D1.dead_of(S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen})) + == Some{S.NoValue{path, name}} : Maybe<&2, S.ParseErr>}: + ee = Equal.sym(Bool, S.looks_flag(tok), True{}, h_flag) + %ee : {D1.dead_of(S.parse.step.need(_, name, tok, args, up, subs, path, bs, pos, raw, seen)) + == Some{S.NoValue{path, name}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.value_flag_shaped( + spec, + pre, + more, + tok, + name, + args, + up, + subs, + path, + bs, + pos, + raw, + seen, + h_at, + h_flag +): + L1.refused(spec, pre, more, tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, + S.NoValue{path, name}, h_at, value_flag_shaped.step(tok, name, args, up, subs, path, bs, pos, raw, seen, h_flag)) diff --git a/proofs/ValueRefused.bend b/proofs/ValueRefused.bend new file mode 100644 index 0000000..36f6810 --- /dev/null +++ b/proofs/ValueRefused.bend @@ -0,0 +1,36 @@ +# proofs of laws/ValueRefused.bend +import Base +import ../src/cli.bend as S +import ../laws/Refused.bend as L1 +import ../laws/ValueRefused.bend as Laws +import ../laws/defs/WalkerFailure.bend as D1 +import ./lemmas/FailedWalkThrough.bend as M1 + +# an option's value that is not flag-shaped and is not allowed fails the step +def value_refused.step( + +tok: String, + +name: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + h_flag: {S.looks_flag(tok) == False{} : Bool}, + h_bad: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == False{} : Bool} +) -> {D1.dead_of(S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen})) + == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>}: + e1 = Equal.sym(Bool, S.looks_flag(tok), False{}, h_flag) + %e1 : {D1.dead_of(S.parse.step.need(_, name, tok, args, up, subs, path, bs, pos, raw, seen)) + == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, name)), tok), False{}, h_bad) + %e2 : {D1.dead_of(S.parse.take_choice(_, name, tok, args, up, subs, path, bs, pos, raw, seen)) + == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} + {==} + +def Laws.value_refused(spec, pre, more, tok, name, args, up, subs, path, bs, pos, raw, seen, h_at, h_flag, h_bad): + L1.refused(spec, pre, more, tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, + S.BadValue{path, name, tok}, h_at, + value_refused.step(tok, name, args, up, subs, path, bs, pos, raw, seen, h_flag, h_bad)) diff --git a/proofs/VersionOfNamesRelease.bend b/proofs/VersionOfNamesRelease.bend new file mode 100644 index 0000000..9887b4d --- /dev/null +++ b/proofs/VersionOfNamesRelease.bend @@ -0,0 +1,47 @@ +# proofs of laws/VersionOfNamesRelease.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/VersionOfNamesRelease.bend as Laws + +def Laws.version_of_some(_name, _about, _ver, _args, _subs): + {==} + +def Laws.version_of_none(_name, _about, _args, _subs, err): + match err: + case S.NeedVersion{}: + {==} + case S.UnknownFlag{_at, _word}: + {==} + case S.Missing{_at, _name}: + {==} + case S.NoValue{_at, _name}: + {==} + case S.BadValue{_at, _name, _value}: + {==} + case S.NeedHelp{_path}: + {==} + case S.Unexpected{_at, _arg}: + {==} + case S.Repeated{_at, _name}: + {==} + +def Laws.version_of_other(_spec, err, h_err): + match err: + case S.NeedVersion{}: + Empty.absurd({Shake.version_of(_spec, S.NeedVersion{}) == None{} : Maybe<&2, String>}, Walk.wk.true_false(h_err)) + case S.UnknownFlag{_at, _word}: + {==} + case S.Missing{_at, _name}: + {==} + case S.NoValue{_at, _name}: + {==} + case S.BadValue{_at, _name, _value}: + {==} + case S.NeedHelp{_path}: + {==} + case S.Unexpected{_at, _arg}: + {==} + case S.Repeated{_at, _name}: + {==} diff --git a/proofs/VersionWordAsksForVersion.bend b/proofs/VersionWordAsksForVersion.bend new file mode 100644 index 0000000..557e0b4 --- /dev/null +++ b/proofs/VersionWordAsksForVersion.bend @@ -0,0 +1,128 @@ +# proofs of laws/VersionWordAsksForVersion.bend +import Base +import ../main.bend as Shake +import ../src/cli.bend as S +import ../src/walk.bend as Walk +import ../laws/VersionWordAsksForVersion.bend as Laws + +# the code's version check is the law's +def va.asks( + spec: Shake.Cli, + +at: List<&2, String>, + +word: String +) -> {S.version.asks(spec, at, word) == Laws.asks_version(spec, at, word) : Bool}: + match spec: + case S.Cli{_n, _a, _v, _g, _s}: + {==} + +# the parse is the walk's answer, read for the version +def va.res( + +spec: Shake.Cli, + +ws: List<&2, String>, + +rr: Result<&2, &2, S.ParseErr, S.Matched>, + h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == rr : Result<&2, &2, S.ParseErr, S.Matched>} +) -> {Shake.parse(spec, ws) == S.version.res(spec, rr) : Result<&2, &2, S.ParseErr, S.Matched>}: + Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, + xx => S.version.res(spec, xx), S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), rr, h_walk) + +# a refused word read for the version, its check answered +def va.pick( + +spec: Shake.Cli, + +at: List<&2, String>, + +word: String, + +bb: Bool, + h_asks: {Laws.asks_version(spec, at, word) == bb : Bool} +) -> {S.version.res(spec, Fail{S.UnknownFlag{at, word}}) + == Fail{Bool.pick(S.ParseErr, bb, S.NeedVersion{}, S.UnknownFlag{at, word})} + : Result<&2, &2, S.ParseErr, S.Matched>}: + ee = Equal.trans(Bool, S.version.asks(spec, at, word), Laws.asks_version(spec, at, word), bb, va.asks(spec, at, word), + h_asks) + Equal.cong(Bool, Result<&2, &2, S.ParseErr, S.Matched>, + xx => Fail{Bool.pick(S.ParseErr, xx, S.NeedVersion{}, S.UnknownFlag{at, word})}, S.version.asks(spec, at, word), bb, + ee) + +def Laws.version_asked(spec, ws, at, word, h_walk, h_asks): + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, ws), + S.version.res(spec, Fail{S.UnknownFlag{at, word}}), Fail{S.NeedVersion{}}, + va.res(spec, ws, Fail{S.UnknownFlag{at, word}}, h_walk), va.pick(spec, at, word, True{}, h_asks)) + +def Laws.version_unasked(spec, ws, at, word, h_walk, h_asks): + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, ws), + S.version.res(spec, Fail{S.UnknownFlag{at, word}}), Fail{S.UnknownFlag{at, word}}, + va.res(spec, ws, Fail{S.UnknownFlag{at, word}}, h_walk), va.pick(spec, at, word, False{}, h_asks)) + +# an answer that refuses no unknown option is left as it is +def va.other( + +spec: Shake.Cli, + rr: Result<&2, &2, S.ParseErr, S.Matched>, + h_rr: {Laws.unknown.res(rr) == False{} : Bool} +) -> {S.version.res(spec, rr) == rr : Result<&2, &2, S.ParseErr, S.Matched>}: + match rr: + case Done{_m}: + {==} + case Fail{ee}: + match ee: + case S.UnknownFlag{at, word}: + Empty.absurd({S.version.res(spec, Fail{S.UnknownFlag{at, word}}) == Fail{S.UnknownFlag{at, word}} + : Result<&2, &2, S.ParseErr, S.Matched>}, Walk.wk.true_false(h_rr)) + case S.Missing{_at, _name}: + {==} + case S.NoValue{_at, _name}: + {==} + case S.BadValue{_at, _name, _value}: + {==} + case S.NeedHelp{_path}: + {==} + case S.Unexpected{_at, _arg}: + {==} + case S.Repeated{_at, _name}: + {==} + case S.NeedVersion{}: + {==} + +def Laws.version_other(spec, ws, rr, h_walk, h_rr): + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, ws), S.version.res(spec, rr), rr, + va.res(spec, ws, rr, h_walk), va.other(spec, rr, h_rr)) + +# a lookup's answer, read as absent or not, under one answer of the check +def rl.at( + bb: Bool, + +x1: S.Arg, + +x2: S.Arg, + +r1: Maybe<&2, S.Arg>, + +r2: Maybe<&2, S.Arg>, + e: {Maybe.is_none(&2, S.Arg, r1) == Maybe.is_none(&2, S.Arg, r2) : Bool} +) -> {Maybe.is_none(&2, S.Arg, S.by_long.at(bb, x1, r1)) == Maybe.is_none(&2, S.Arg, S.by_long.at(bb, x2, r2)) + : Bool}: + match bb: + case True{}: + {==} + case False{}: + e + +# resolved arguments spell long what they spelled +def rl.none( + args: List<&2, S.Arg>, + +vars: List<&2, Shake.Var>, + +name: String +) -> {Maybe.is_none(&2, S.Arg, S.by_long(S.resolve.args(args, vars), name)) + == Maybe.is_none(&2, S.Arg, S.by_long(args, name)) : Bool}: + match args: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Arg{+n, +s, +l, +k, +h, +r, fb, +cs}: + match fb: + case S.Fallback{+ev, +dd}: + +orig = {S.Arg{n, s, l, k, h, r, S.Fallback{ev, dd}, cs} : S.Arg} + rl.at(S.by_long.hit(orig, name), S.resolve.arg(orig, vars), orig, + S.by_long(S.resolve.args(tt, vars), name), S.by_long(tt, name), rl.none(tt, vars, name)) + +def Laws.resolve_asks(spec, vars, at, word): + match spec: + case S.Cli{+nn, +ab, +vv, +gg, +ss}: + Equal.cong(Bool, Bool, xx => Bool.and(Bool.or(String.eq(word, "--version"), Bool.and(String.eq(word, "-V"), xx)), + Bool.and(List.is_empty(&2, String, at), Maybe.is_some(&2, String, vv))), + Maybe.is_none(&2, S.Arg, S.by_long(S.resolve.args(gg, vars), "version")), + Maybe.is_none(&2, S.Arg, S.by_long(gg, "version")), rl.none(gg, vars, "version")) diff --git a/proofs/WalkAppend.bend b/proofs/WalkAppend.bend new file mode 100644 index 0000000..3c5706f --- /dev/null +++ b/proofs/WalkAppend.bend @@ -0,0 +1,6 @@ +# proofs of laws/WalkAppend.bend +import Base +import ../laws/WalkAppend.bend as Laws +import ./lemmas/WalkSteps.bend as M1 + + diff --git a/proofs/lemmas/AppendedBindings.bend b/proofs/lemmas/AppendedBindings.bend new file mode 100644 index 0000000..ae18bc4 --- /dev/null +++ b/proofs/lemmas/AppendedBindings.bend @@ -0,0 +1,30 @@ +# get_all and hits over appended bindings +import Base +import ../../src/cli.bend as S +import ../../laws/BindValuesAppend.bend as L1 +import ../../laws/HitAppend.bend as L2 + +def L2.hit_append(bb, _vv, _xs, _ys): + match bb: + case True{}: + {==} + case False{}: + {==} + +def L1.bind_values_append(bs, cs, name): + match bs: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Bind{+nn, +vv}: + ih = Equal.sym(List<&2, String>, + S.get_all.bind(List.append(&2, S.Bind, tt, cs), name), + List.append(&2, String, S.get_all.bind(tt, name), S.get_all.bind(cs, name)), + L1.bind_values_append(tt, cs, name)) + %ih : {S.get_all.bind.hit(String.eq(nn, name), vv, _) + == List.append(&2, String, + S.get_all.bind.hit(String.eq(nn, name), vv, S.get_all.bind(tt, name)), + S.get_all.bind(cs, name)) : List<&2, String>} + L2.hit_append(String.eq(nn, name), vv, S.get_all.bind(tt, name), + S.get_all.bind(cs, name)) diff --git a/proofs/lemmas/BindingsKept.bend b/proofs/lemmas/BindingsKept.bend new file mode 100644 index 0000000..5607ce8 --- /dev/null +++ b/proofs/lemmas/BindingsKept.bend @@ -0,0 +1,364 @@ +# the bindings a walk makes are kept and read back by get_all +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S +import ../../src/eq.bend as Eq +import ../../src/grow.bend as Grow +import ../../laws/BindValuesAppend.bend as L1 +import ../../laws/Kept.bend as L2 +import ../../laws/KeptAll.bend as L3 +import ../../laws/WalkAppend.bend as L4 +import ../../laws/defs/ValuesAtPath.bend as D1 +import ./AppendedBindings.bend as M1 +import ./FinishedWalker.bend as M2 +import ./WalkSteps.bend as M3 + +# a binding's own name reads its value first +def kept.hit( + +name: String, + +vv: String, + +rest: List<&2, String> +) -> {S.get_all.bind.hit(String.eq(name, name), vv, rest) == vv <> rest : List<&2, String>}: + ee = Equal.sym(Bool, String.eq(name, name), True{}, Eq.string_eq_self(name)) + %ee : {S.get_all.bind.hit(_, vv, rest) == vv <> rest : List<&2, String>} + {==} + +# what `kept_all` claims +def kept_all.ty( + +found: Shake.Matched, + +path: List<&2, String>, + +name: String, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind> +) -> Type: + &after: List<&2, String> -> {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), + List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after))} + : Maybe<&2, List<&2, String>>} + +# the bindings of the level `mid` stayed in when the words end: those the words +# after it made, those `mid` made, and those it had +def kept_all.ys(+xx: List<&2, S.Bind>, +xs: List<&2, S.Bind>, +bs: List<&2, S.Bind>) -> List<&2, S.Bind>: + List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs)) + +# the level's values, defaults in front, read back +def kept_all.vals(+args: List<&2, S.Arg>, +ys: List<&2, S.Bind>, +name: String) -> List<&2, String>: + S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, ys)), name) + +# what `kept_all.fill` claims +def kept_all.fill.ty( + +args: List<&2, S.Arg>, + +xx: List<&2, S.Bind>, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + +name: String +) -> Type: + &after: List<&2, String> -> {kept_all.vals(args, kept_all.ys(xx, xs, bs), name) + == List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), + List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after)) : List<&2, String>} + +# what `kept_all.read` claims +def kept_all.read.ty( + +found: Shake.Matched, + +path: List<&2, String>, + +args: List<&2, S.Arg>, + +ys: List<&2, S.Bind>, + +name: String +) -> Type: + {D1.all_at(found, path, name) == Some{kept_all.vals(args, ys, name)} : Maybe<&2, List<&2, String>>} + +# a successful parse's Matched, read at the path where the words `mid` stayed: +# the level's bindings, all of them, with defaults in front, reversed +def kept_all.read( + +found: Shake.Matched, + +fs: S.St, + +st1: S.St, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +yy: List<&2, S.Bind>, + +xx: List<&2, S.Bind>, + +qs: List<&2, S.Level>, + +ms: List<&2, String>, + +name: String, + h_mid: {Grow.view(st1) == Grow.View{args, up, path, yy} : Grow.View}, + hd: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, + dep: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}, + e2: {Grow.gw.frame(fs) == Grow.gw.grown(xx, qs, ms, Grow.view(st1)) : Grow.Frame} +) -> kept_all.read.ty(found, path, args, List.append(&2, S.Bind, xx, yy), name): + +ys = List.append(&2, S.Bind, xx, yy) + +f0 = {S.Level{args, S.fill.args(args, ys)} : S.Level} + +fq = S.fill.levels(qs) + +fu = S.fill.levels(up) + +r0 = List.reverse(&2, S.Level, fu) + +rq = List.reverse(&2, S.Level, fq) + +pm = List.append(&2, String, path, ms) + +lvs = List.append(&2, S.Level, qs, S.Level{args, ys} <> up) + ef = Equal.trans(Grow.Frame, Grow.gw.frame(fs), Grow.gw.grown(xx, qs, ms, Grow.view(st1)), Grow.Frame{lvs, pm}, e2, + Equal.cong(Grow.View, Grow.Frame, vv => Grow.gw.grown(xx, qs, ms, vv), Grow.view(st1), Grow.View{args, up, + path, yy}, + h_mid)) + em = Equal.trans(S.Matched, found, Grow.gw.build_of(Grow.gw.frame(fs)), + S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), + Grow.gw.finished(fs, found, hd), + Equal.trans(S.Matched, Grow.gw.build_of(Grow.gw.frame(fs)), Grow.gw.build_of(Grow.Frame{lvs, pm}), + S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), + Equal.cong(Grow.Frame, S.Matched, fr => Grow.gw.build_of(fr), Grow.gw.frame(fs), Grow.Frame{lvs, pm}, ef), + Equal.cong(List<&2, S.Level>, S.Matched, ls => S.matched.build(ls, pm), + List.reverse(&2, S.Level, S.fill.levels(lvs)), List.append(&2, S.Level, r0, f0 <> rq), + Equal.trans(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(lvs)), + List.reverse(&2, S.Level, List.append(&2, S.Level, fq, f0 <> fu)), List.append(&2, S.Level, r0, f0 <> rq), + Equal.cong(List<&2, S.Level>, List<&2, S.Level>, ls => List.reverse(&2, S.Level, ls), S.fill.levels(lvs), + List.append(&2, S.Level, fq, f0 <> fu), Grow.gw.fill_mid(qs, args, ys, up)), + Grow.gw.lrev_mid(fq, f0, fu))))) + hl = Equal.trans(Nat, List.length(&2, S.Level, r0), List.length(&2, S.Level, fu), List.length(&2, String, path), + Grow.gw.len_rev(fu), + Equal.trans(Nat, List.length(&2, S.Level, fu), List.length(&2, S.Level, up), List.length(&2, String, path), + Grow.gw.len_fill(up), dep)) + ea = Equal.trans(Maybe<&2, S.Matched>, S.at(found, path), + S.at(S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), path), Some{S.matched.build(f0 <> rq, ms)}, + Equal.cong(S.Matched, Maybe<&2, S.Matched>, mm => S.at(mm, path), found, + S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), em), + Grow.gw.at_build(r0, path, f0, rq, ms, hl)) + Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, name), + Some{S.get_all.bind(S.own(S.matched.build(f0 <> rq, ms)), name)}, + Some{S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, ys)), name)}, + Equal.cong(Maybe<&2, S.Matched>, Maybe<&2, List<&2, String>>, mm => D1.all_at.go(mm, name), S.at(found, path), + Some{S.matched.build(f0 <> rq, ms)}, ea), + Equal.cong(List<&2, S.Bind>, Maybe<&2, List<&2, String>>, bs2 => Some{S.get_all.bind(bs2, name)}, + S.own(S.matched.build(f0 <> rq, ms)), List.reverse(&2, S.Bind, S.fill.args(args, ys)), + Grow.gw.own_build(f0, rq, ms))) + +# the level's bindings, defaults in front, read back: those made before +# `mid`, those `mid` made, then the rest +def kept_all.fill( + +args: List<&2, S.Arg>, + +xx: List<&2, S.Bind>, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + +name: String, + wf: Grow.gw.Ext(S.fill.args(args, kept_all.ys(xx, xs, bs)), kept_all.ys(xx, xs, bs)) +) -> kept_all.fill.ty(args, xx, xs, bs, name): + (dx, ee) = wf + +defs = {dx : List<&2, S.Bind>} + +ys = List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs)) + +r1 = List.reverse.go(&2, S.Bind, xx, List.reverse(&2, S.Bind, defs)) + +gb = S.get_all.bind(List.reverse(&2, S.Bind, bs), name) + +gx = S.get_all.bind(List.reverse(&2, S.Bind, xs), name) + (S.get_all.bind(r1, name), + Equal.trans(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, ys)), name), + S.get_all.bind(List.reverse(&2, S.Bind, List.append(&2, S.Bind, defs, ys)), name), + List.append(&2, String, gb, List.append(&2, String, gx, S.get_all.bind(r1, name))), + Equal.cong(List<&2, S.Bind>, List<&2, String>, zs => S.get_all.bind(List.reverse(&2, S.Bind, zs), name), + S.fill.args(args, ys), List.append(&2, S.Bind, defs, ys), ee), + Equal.trans(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, List.append(&2, S.Bind, defs, ys)), name), + S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, bs), + List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1)), name), + List.append(&2, String, gb, List.append(&2, String, gx, S.get_all.bind(r1, name))), + Equal.cong(List<&2, S.Bind>, List<&2, String>, zs => S.get_all.bind(zs, name), + List.reverse(&2, S.Bind, List.append(&2, S.Bind, defs, ys)), + List.append(&2, S.Bind, List.reverse(&2, S.Bind, bs), List.append(&2, S.Bind, List.reverse(&2, S.Bind, + xs), r1)), + Grow.gw.rev_split(defs, xx, xs, bs)), + Equal.trans(List<&2, String>, + S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, bs), + List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1)), name), + List.append(&2, String, gb, + S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1), name)), + List.append(&2, String, gb, List.append(&2, String, gx, S.get_all.bind(r1, name))), + L1.bind_values_append(List.reverse(&2, S.Bind, bs), List.append(&2, S.Bind, List.reverse(&2, S.Bind, + xs), r1), + name), + Equal.cong(List<&2, String>, List<&2, String>, zs => List.append(&2, String, gb, zs), + S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1), name), + List.append(&2, String, gx, S.get_all.bind(r1, name)), + L1.bind_values_append(List.reverse(&2, S.Bind, xs), r1, name)))))) + +# the read at the path, and the level's bindings read back +def kept_all.join( + +found: Shake.Matched, + +path: List<&2, String>, + +args: List<&2, S.Arg>, + +xx: List<&2, S.Bind>, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + +name: String, + er: kept_all.read.ty(found, path, args, kept_all.ys(xx, xs, bs), name), + wr: kept_all.fill.ty(args, xx, xs, bs, name) +) -> kept_all.ty(found, path, name, xs, bs): + (ax, ee) = wr + +after = {ax : List<&2, String>} + +zz = S.get_all.bind(List.reverse(&2, S.Bind, + S.fill.args(args, List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs)))), name) + +ww = List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), + List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after)) + (after, Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, name), Some{zz}, Some{ww}, er, + Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, zs => Some{zs}, zz, ww, ee))) + +# a walker grown from where `mid` stayed +def kept_all.fin( + +found: Shake.Matched, + +fs: S.St, + +st1: S.St, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + +name: String, + h_mid: {Grow.view(st1) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} : Grow.View}, + hd: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, + dep: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}, + gg: Grow.gw.Grown(fs, Grow.view(st1)) +) -> kept_all.ty(found, path, name, xs, bs): + (x2, r2) = gg + (q2, r3) = r2 + (m2, r4) = r3 + (_hl, e2) = r4 + +xx = {x2 : List<&2, S.Bind>} + kept_all.join(found, path, args, xx, xs, bs, name, + kept_all.read(found, fs, st1, args, up, path, List.append(&2, S.Bind, xs, bs), xx, q2, m2, name, h_mid, hd, dep, + e2), + kept_all.fill(args, xx, xs, bs, name, + Grow.gw.fill(args, List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs))))) + +# the walk after `mid` grew the walker, or left it stuck, which no success +# follows +def kept_all.after( + +found: Shake.Matched, + +fs: S.St, + +st1: S.St, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + +name: String, + h_mid: {Grow.view(st1) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} : Grow.View}, + hd: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, + dep: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}, + rr: Grow.gw.Reach(fs, Grow.view(st1)) +) -> kept_all.ty(found, path, name, xs, bs): + match rr: + case Grow.Grew{gg}: + kept_all.fin(found, fs, st1, args, up, path, xs, bs, name, h_mid, hd, dep, gg) + case Grow.Stopped{hs}: + Empty.absurd(kept_all.ty(found, path, name, xs, bs), Grow.gw.live_done(fs, found, hd, hs)) + +# the walker `mid` left has one command above it per name of its path +def kept_all.dep( + +w0: S.St, + +st1: S.St, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +yy: List<&2, S.Bind>, + e_st: {w0 == st1 : S.St}, + h_mid: {Grow.view(st1) == Grow.View{args, up, path, yy} : Grow.View}, + dd: Grow.gw.Deep(w0) +) -> {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}: + +lit = {Grow.Frame{S.Level{args, yy} <> up, path} : Grow.Frame} + +ef = {Equal.trans(Grow.Frame, Grow.gw.frame(w0), Grow.gw.frame(st1), lit, + Equal.cong(S.St, Grow.Frame, xx => Grow.gw.frame(xx), w0, st1, e_st), + Equal.trans(Grow.Frame, Grow.gw.frame(st1), Grow.gw.vframe(Grow.view(st1)), lit, Grow.gw.frame_view(st1), + Equal.cong(Grow.View, Grow.Frame, vv => Grow.gw.vframe(vv), Grow.view(st1), Grow.View{args, up, path, + yy}, h_mid))) + : {Grow.gw.frame(w0) == lit : Grow.Frame}} + Equal.cong(Nat, Nat, nn => Grow.gw.pred(nn), 1n+List.length(&2, S.Level, up), 1n+List.length(&2, String, path), + Equal.trans(Nat, List.length(&2, S.Level, S.Level{args, yy} <> up), + List.length(&2, S.Level, Grow.gw.flevels(Grow.gw.frame(w0))), 1n+List.length(&2, String, path), + Equal.cong(Grow.Frame, Nat, fr => List.length(&2, S.Level, Grow.gw.flevels(fr)), lit, Grow.gw.frame(w0), + Equal.sym(Grow.Frame, Grow.gw.frame(w0), lit, ef)), + Equal.trans(Nat, List.length(&2, S.Level, Grow.gw.flevels(Grow.gw.frame(w0))), + 1n+List.length(&2, String, Grow.gw.fpath(Grow.gw.frame(w0))), 1n+List.length(&2, String, path), dd, + Equal.cong(Grow.Frame, Nat, fr => 1n+List.length(&2, String, Grow.gw.fpath(fr)), Grow.gw.frame(w0), lit, ef)))) + +# the walk from the start to where `mid` stayed grew the walker, or left it +# stuck, which no success follows +def kept_all.start( + spec: Shake.Cli, + +w0: S.St, + +found: Shake.Matched, + +st1: S.St, + +more: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +xs: List<&2, S.Bind>, + +bs: List<&2, S.Bind>, + +name: String, + +e_st: {w0 == st1 : S.St}, + +h_mid: {Grow.view(st1) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} : Grow.View}, + +hd: {S.parse.finish(S.parse.walk(more, st1)) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, + r0: Grow.gw.Reach(w0, Grow.view(S.parse.start(spec))), + r1: Grow.gw.Reach(S.parse.walk(more, st1), Grow.view(st1)) +) -> kept_all.ty(found, path, name, xs, bs): + match spec: + case S.Cli{_n, _a, _v, +sargs, _s}: + match r0: + case Grow.Grew{g0}: + kept_all.after(found, S.parse.walk(more, st1), st1, args, up, path, xs, bs, name, h_mid, hd, + kept_all.dep(w0, st1, args, up, path, List.append(&2, S.Bind, xs, bs), e_st, h_mid, + Grow.gw.depth(w0, sargs, [], g0)), + r1) + case Grow.Stopped{hs}: + Empty.absurd(kept_all.ty(found, path, name, xs, bs), + Grow.gw.live_done(S.parse.walk(more, st1), found, hd, + Grow.gw.stuck_walk(more, st1, + Equal.trans(Bool, Grow.gw.live(st1), Grow.gw.live(w0), False{}, + Equal.cong(S.St, Bool, xx => Grow.gw.live(xx), st1, w0, Equal.sym(S.St, w0, st1, e_st)), hs)))) + +def L3.kept_all(spec, pre, mid, more, st, args, up, path, xs, bs, name, found, h_at, h_mid, h_done): + +st1 = S.parse.walk(mid, st) + +ww = List.append(&2, String, pre, mid) + +e_st = {Equal.trans(S.St, S.parse.walk(ww, S.parse.start(spec)), + S.parse.walk(mid, S.parse.walk(pre, S.parse.start(spec))), st1, L4.walk_append(pre, mid, S.parse.start(spec)), + Equal.cong(S.St, S.St, xx => S.parse.walk(mid, xx), S.parse.walk(pre, S.parse.start(spec)), st, h_at)) + : {S.parse.walk(ww, S.parse.start(spec)) == st1 : S.St}} + e1 = Equal.cong(S.St, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.parse.finish(xx), + S.parse.walk(List.append(&2, String, ww, more), S.parse.start(spec)), + S.parse.walk(more, S.parse.walk(ww, S.parse.start(spec))), L4.walk_append(ww, more, S.parse.start(spec))) + e2 = Equal.cong(S.St, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.parse.finish(S.parse.walk(more, xx)), + S.parse.walk(ww, S.parse.start(spec)), st1, e_st) + +raw = S.parse.finish(S.parse.walk(List.append(&2, String, ww, more), S.parse.start(spec))) + hd = Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, S.parse.finish(S.parse.walk(more, st1)), + raw, Done{found}, + Equal.sym(Result<&2, &2, S.ParseErr, S.Matched>, raw, + S.parse.finish(S.parse.walk(more, st1)), + Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, raw, + S.parse.finish(S.parse.walk(more, S.parse.walk(ww, S.parse.start(spec)))), + S.parse.finish(S.parse.walk(more, st1)), e1, e2)), + M2.vr.done(spec, raw, found, h_done)) + kept_all.start(spec, S.parse.walk(ww, S.parse.start(spec)), found, st1, more, args, up, path, + xs, bs, name, e_st, h_mid, hd, Grow.gw.reach(ww, S.parse.start(spec)), Grow.gw.reach(more, st1)) + +# what `kept` claims +def kept.ty(+found: Shake.Matched, +path: List<&2, String>, +name: String, +vv: String, +bs: List<&2, S.Bind>) -> Type: + &after: List<&2, String> -> {D1.all_at(found, path, name) + == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), vv <> after)} + : Maybe<&2, List<&2, String>>} + +# one binding of the name, read back as its value +def kept.one( + +found: Shake.Matched, + +path: List<&2, String>, + +name: String, + +vv: String, + +bs: List<&2, S.Bind>, + ww: kept_all.ty(found, path, name, [S.Bind{name, vv}], bs) +) -> kept.ty(found, path, name, vv, bs): + (ax, ee) = ww + +after = {ax : List<&2, String>} + +gb = S.get_all.bind(List.reverse(&2, S.Bind, bs), name) + (after, Equal.trans(Maybe<&2, List<&2, String>>, D1.all_at(found, path, name), + Some{List.append(&2, String, gb, List.append(&2, String, S.get_all.bind.hit(String.eq(name, name), vv, []), + after))}, + Some{List.append(&2, String, gb, vv <> after)}, ee, + Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, + ys => Some{List.append(&2, String, gb, List.append(&2, String, ys, after))}, + S.get_all.bind.hit(String.eq(name, name), vv, []), [vv], kept.hit(name, vv, [])))) + +def L2.kept(spec, pre, more, tok, st, args, up, path, name, vv, bs, found, h_at, h_step, h_done): + kept.one(found, path, name, vv, bs, + L3.kept_all(spec, pre, [tok], more, st, args, up, path, [S.Bind{name, vv}], bs, name, found, h_at, h_step, + h_done)) diff --git a/proofs/lemmas/ClusterEnd.bend b/proofs/lemmas/ClusterEnd.bend new file mode 100644 index 0000000..4c811e6 --- /dev/null +++ b/proofs/lemmas/ClusterEnd.bend @@ -0,0 +1,158 @@ +# a finished cluster, the filled levels of a finished walker, refused glued +# values +import Base +import ../../src/cli.bend as S +import ../../src/eq.bend as Eq +import ../../src/walk.bend as Walk +import ../../src/grow.bend as Grow +import ../../laws/ClusterDone.bend as L1 +import ../../laws/UnknownShortStep.bend as L2 +import ../../laws/defs/ArgumentLists.bend as D1 + +def L1.cluster_done(body, orig, st): + match body: + case SNil{}: + {==} + case SCon{_cc, rest}: + L1.cluster_done(rest, orig, st) + +def L2.unknown_short_step( + cc, + rest, + args, + up, + subs, + path, + bs, + pos, + seen, + h_dd, + h_dash, + h_none, + h_help +): + e1 = Equal.sym(Bool, String.eq("-" ++ SCon{cc, rest}, "--"), False{}, h_dd) + %e1 : {S.parse.step.end(_, "-" ++ SCon{cc, rest}, args, up, subs, path, bs, pos, + seen) == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, + rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e4 = Equal.sym(Cmp, String.order.go(SCon{cc, rest}, "elp", LT{}), LT{}, Eq.string_order_lt(SCon{cc, rest}, "elp")) + %e4 : {S.parse.step.help(Bool.and(Cmp.is_eq(_), Bool.not(seen)), "-" ++ SCon{cc, rest}, args, up, subs, path, bs, + pos, seen) == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, + rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e3 = Equal.sym(Bool, Char.is_eq(cc, '-'), False{}, h_dash) + %e3 : {S.parse.step.kind.go(String.starts_with.go.if("", rest, _), True{}, "-" ++ SCon{cc, rest}, args, up, + subs, path, bs, pos, + seen) == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, + rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e5 = Equal.sym(Bool, S.short_help(SCon{cc, rest}, args, seen), False{}, h_help) + %e5 : {S.parse.take_short.go(_, SCon{cc, rest}, "-" ++ SCon{cc, rest}, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, SCon{cc, SNil{}}), None{}, h_none) + %e2 : {S.cluster(rest, "-" ++ SCon{cc, rest}, + S.parse.take_arg(_, S.cluster.val(_, rest), "-" ++ SCon{cc, rest}, args, up, subs, path, bs, pos, + False{}, seen), + S.cluster.ends(_, rest)) + == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + L1.cluster_done(rest, "-" ++ SCon{cc, rest}, S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, + up, subs, path, bs, pos, False{}, seen}) + +# the filled levels of a finished walker whose frame is known, root first +def fin.levels( + +qs: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level> +) -> {List.reverse(&2, S.Level, S.fill.levels(List.append(&2, S.Level, qs, S.Level{args, bs} <> up))) + == List.append(&2, S.Level, List.reverse(&2, S.Level, S.fill.levels(up)), + S.Level{args, S.fill.args(args, bs)} <> List.reverse(&2, S.Level, S.fill.levels(qs))) : List<&2, S.Level>}: + +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} + Equal.trans(List<&2, S.Level>, + List.reverse(&2, S.Level, S.fill.levels(List.append(&2, S.Level, qs, S.Level{args, bs} <> up))), + List.reverse(&2, S.Level, List.append(&2, S.Level, S.fill.levels(qs), f0 <> S.fill.levels(up))), + List.append(&2, S.Level, List.reverse(&2, S.Level, S.fill.levels(up)), + f0 <> List.reverse(&2, S.Level, S.fill.levels(qs))), + Equal.cong(List<&2, S.Level>, List<&2, S.Level>, ls => List.reverse(&2, S.Level, ls), + S.fill.levels(List.append(&2, S.Level, qs, S.Level{args, bs} <> up)), + List.append(&2, S.Level, S.fill.levels(qs), f0 <> S.fill.levels(up)), Grow.gw.fill_mid(qs, args, bs, up)), + Grow.gw.lrev_mid(S.fill.levels(qs), f0, S.fill.levels(up))) + +# what `lb` claims of an argument kind +def lb.ty( + kk: S.ArgKind, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String +) -> Type: + {S.cluster(rest, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, rest), orig, args, up, subs, path, + bs, pos, raw, seen), + S.cluster.ends.kind(kk, rest)) == S.St{S.Dead{S.BadValue{path, nn, + rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} + +# the same, with a glued value its choices refuse +def lb( + kk: S.ArgKind, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +nn: String, + h_kind: {D1.takes(kk, S.bound(bs, nn)) == True{} : Bool}, + h_noeq: {String.starts_with(rest, "=") == False{} : Bool}, + h_some: {String.is_empty(rest) == False{} : Bool}, + h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == False{} : Bool} +) -> lb.ty(kk, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn): + match kk: + case S.Opt{}: + +T = {S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} + e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) + %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, S.short_of.val(_, rest), orig, args, up, + subs, path, bs, pos, raw, seen), True{}) + == T : S.St} + e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) + %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, + Bool.pick(Maybe<&2, String>, _, None{}, + Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} + e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) + %e3 : {S.cluster(rest, orig, S.parse.once(_, S.parse.take_opt(Some{rest}, nn, args, up, subs, path, bs, + pos, raw, seen), nn, path), True{}) == T + : S.St} + e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), False{}, h_ok) + %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), + True{}) == T : S.St} + L1.cluster_done(rest, orig, T) + case S.Many{}: + +T = {S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} + e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) + %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, S.short_of.val(_, rest), orig, args, up, + subs, path, bs, pos, raw, seen), True{}) + == T : S.St} + e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) + %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, + Bool.pick(Maybe<&2, String>, _, None{}, + Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} + e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), False{}, h_ok) + %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), + True{}) == T : S.St} + L1.cluster_done(rest, orig, T) + case S.Flag{}: + Empty.absurd(lb.ty(S.Flag{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), + Walk.wk.false_true(h_kind)) + case S.Pos{}: + Empty.absurd(lb.ty(S.Pos{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) + case S.Rest{}: + Empty.absurd(lb.ty(S.Rest{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), + Walk.wk.false_true(h_kind)) diff --git a/proofs/lemmas/ClusterSteps.bend b/proofs/lemmas/ClusterSteps.bend new file mode 100644 index 0000000..ca90c2f --- /dev/null +++ b/proofs/lemmas/ClusterSteps.bend @@ -0,0 +1,55 @@ +# one letter of a short cluster, read in a free walker +import Base +import ../../src/cli.bend as S + +# one letter of a cluster in a free walker, once the argument it spells is +# known: `take_arg` with the value the letter's kind gives, then the rest, +# ended or not as the kind says +def cl.step( + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +aa: S.Arg, + h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{aa} : Maybe<&2, S.Arg>} +) -> {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) + == S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, + path, bs, pos, + raw, seen), S.cluster.ends(Some{aa}, rest)) : S.St}: + ee = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, SCon{cc, SNil{}}), Some{aa}, h_found) + %ee : {S.cluster(rest, orig, S.parse.take_arg(_, S.cluster.val(_, rest), orig, args, up, subs, path, bs, pos, raw, + seen), S.cluster.ends(_, rest)) + == S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, path, bs, + pos, raw, seen), S.cluster.ends(Some{aa}, rest)) : S.St} + {==} + +# the same, the other way round, for rewriting a goal about the cluster +def cl.back( + +cc: Char, + +rest: String, + +orig: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +aa: S.Arg, + h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{aa} : Maybe<&2, S.Arg>} +) -> {S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, + path, bs, pos, + raw, seen), S.cluster.ends(Some{aa}, rest)) + == S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) : S.St}: + Equal.sym(S.St, S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}), + S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, path, bs, pos, + raw, seen), S.cluster.ends(Some{aa}, rest)), + cl.step(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found)) diff --git a/proofs/lemmas/EnvChecksNoArgs.bend b/proofs/lemmas/EnvChecksNoArgs.bend new file mode 100644 index 0000000..1350da8 --- /dev/null +++ b/proofs/lemmas/EnvChecksNoArgs.bend @@ -0,0 +1,19 @@ +# the env value check over no arguments finds nothing +import Base +import ../../src/cli.bend as S +import ../../laws/EnvValueOutsideChoicesFails.bend as L1 + +def L1.bad_args_nil(args, bs): + match args: + case Nil{}: + {==} + case Con{hh, tt}: + match hh: + case S.Arg{_n, _s, _l, _k, _h, _r, fb, _c}: + match fb: + case S.Fallback{ev, _d}: + match ev: + case None{}: + L1.bad_args_nil(tt, bs) + case Some{_var}: + L1.bad_args_nil(tt, bs) diff --git a/proofs/lemmas/FailedWalkThrough.bend b/proofs/lemmas/FailedWalkThrough.bend new file mode 100644 index 0000000..44709f1 --- /dev/null +++ b/proofs/lemmas/FailedWalkThrough.bend @@ -0,0 +1,208 @@ +# a failed walker stays failed through letters, clusters and later words +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S +import ../../laws/ClusterDead.bend as L1 +import ../../laws/DeadWalk.bend as L2 +import ../../laws/FailFrom.bend as L3 +import ../../laws/Refused.bend as L4 +import ../../laws/StepDies.bend as L5 +import ../../laws/WalkAppend.bend as L6 +import ../../laws/defs/WalkerFailure.bend as D1 +import ./WalkSteps.bend as M1 + +def L2.dead_walk(ys, err, args, up, subs, path, binds, pos, raw, seen): + match ys: + case Nil{}: + {==} + case Con{_hh, tt}: + L2.dead_walk(tt, err, args, up, subs, path, binds, pos, raw, seen) + +# the type that is empty exactly where a Maybe is Some +def some.emp(mm: Maybe<&2, S.ParseErr>) -> Type: + match mm: + case None{}: + Unit + case Some{_e}: + Empty + +# the clash itself: None is not Some +def none.clash(+err: S.ParseErr, eq: {None{} == Some{err} : Maybe<&2, S.ParseErr>}) -> Empty: + %eq : some.emp(_) + Unit{} + +# nothing is in Empty, so it is any type +def gone(a, -A: Kind(a), never: Empty) -> A: + match never: + +# None is never Some, so such an equation proves anything +def none.absurd( + a, + -A: Kind(a), + +err: S.ParseErr, + eq: {None{} == Some{err} : Maybe<&2, S.ParseErr>} +) -> A: + gone(a, A, none.clash(err, eq)) + +# a Some read back as a failed parse +def some.fail(mm: Maybe<&2, S.ParseErr>) -> Result<&2, &2, S.ParseErr, S.Matched>: + match mm: + case None{}: + Fail{S.Missing{[], ""}} + case Some{ee}: + Fail{ee} + +def L3.fail_from(st, more, err, died): + match st: + case S.St{S.Dead{+ee}, +aa, +gg, +ss, +pp, +bb, +oo, +rr, +nn}: + e1 = Equal.sym(S.St, + S.parse.walk(more, S.St{S.Dead{ee}, aa, gg, ss, pp, bb, oo, rr, nn}), + S.St{S.Dead{ee}, aa, gg, ss, pp, bb, oo, rr, nn}, + L2.dead_walk(more, ee, aa, gg, ss, pp, bb, oo, rr, nn)) + %e1 : {S.parse.finish(_) == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>} + Equal.cong(Maybe<&2, S.ParseErr>, Result<&2, &2, S.ParseErr, S.Matched>, + mm => some.fail(mm), Some{ee}, Some{err}, died) + case S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n}: + none.absurd(&1, {S.parse.finish(S.parse.walk(more, S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n})) + == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>}, err, died) + case S.St{S.Need{_name}, _a, _g, _s, _p, _b, _o, _r, _n}: + none.absurd(&1, {S.parse.finish(S.parse.walk(more, S.St{S.Need{_name}, _a, _g, _s, _p, _b, _o, _r, _n})) + == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>}, err, died) + case S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}: + none.absurd(&1, {S.parse.finish(S.parse.walk(more, S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n})) + == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>}, err, died) + +# a failed walk finishes as its failure, whatever words follow +def fs.raw( + +spec: Shake.Cli, + +ws: List<&2, String>, + +more: List<&2, String>, + +err: Shake.ParseErr, + died: {D1.dead_of(S.parse.walk(ws, S.parse.start(spec))) == Some{err} : Maybe<&2, S.ParseErr>} +) -> {S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec))) == Fail{err} + : Result<&2, &2, S.ParseErr, S.Matched>}: + e1 = Equal.sym(S.St, + S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec)), + S.parse.walk(more, S.parse.walk(ws, S.parse.start(spec))), + L6.walk_append(ws, more, S.parse.start(spec))) + %e1 : {S.parse.finish(_) == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>} + L3.fail_from(S.parse.walk(ws, S.parse.start(spec)), more, err, died) + +# a failed walk's parse is its failure, read as a request for the version +# when it is one, whatever words follow +def fs.fail( + +spec: Shake.Cli, + +ws: List<&2, String>, + +more: List<&2, String>, + +err: Shake.ParseErr, + died: {D1.dead_of(S.parse.walk(ws, S.parse.start(spec))) == Some{err} : Maybe<&2, S.ParseErr>} +) -> {Shake.parse(spec, List.append(&2, String, ws, more)) == Fail{S.version.err(spec, err)} + : Result<&2, &2, S.ParseErr, S.Matched>}: + Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, + xx => S.version.res(spec, xx), S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec))), + Fail{err}, fs.raw(spec, ws, more, err, died)) + +# an unknown option that is no request for the version stays as it is +def fs.keep( + +spec: Shake.Cli, + +at: List<&2, String>, + +word: String, + h_ver: {S.version.asks(spec, at, word) == False{} : Bool} +) -> {Fail{S.version.err(spec, S.UnknownFlag{at, word})} == Fail{S.UnknownFlag{at, word}} + : Result<&2, &2, S.ParseErr, S.Matched>}: + ee = Equal.sym(Bool, S.version.asks(spec, at, word), False{}, h_ver) + %ee : {Fail{Bool.pick(S.ParseErr, _, S.NeedVersion{}, S.UnknownFlag{at, word})} == Fail{S.UnknownFlag{at, word}} + : Result<&2, &2, S.ParseErr, S.Matched>} + {==} + +def L5.step_dies( + spec, + pre, + tok, + st, + err, + h_at, + h_step +): + e1 = Equal.sym(S.St, + S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), + S.parse.walk([tok], S.parse.walk(pre, S.parse.start(spec))), + L6.walk_append(pre, [tok], S.parse.start(spec))) + %e1 : {D1.dead_of(_) == Some{err} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(S.St, S.parse.walk(pre, S.parse.start(spec)), st, h_at) + %e2 : {D1.dead_of(S.parse.walk([tok], _)) == Some{err} : Maybe<&2, S.ParseErr>} + h_step + +def L4.refused( + spec, + pre, + more, + tok, + st, + err, + h_at, + h_step +): + fs.fail(spec, List.append(&2, String, pre, [tok]), more, err, + L5.step_dies(spec, pre, tok, st, err, h_at, h_step)) + +# one letter against a failed walker keeps it failed, read or skipped +def cd.letter( + done: Bool, + +cc: Char, + +rest: String, + +orig: String, + +err: S.ParseErr, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> {S.cluster.letter(done, cc, rest, orig, S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen}) + == S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen} : S.St}: + match done: + case True{}: + {==} + case False{}: + {==} + +def L1.cluster_dead(body, orig, err, args, up, subs, path, bs, pos, raw, seen, done): + match body: + case SNil{}: + {==} + case SCon{+cc, +rest}: + +dd = {S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen} : S.St} + ee = Equal.sym(S.St, S.cluster.letter(done, cc, rest, orig, dd), dd, + cd.letter(done, cc, rest, orig, err, args, up, subs, path, bs, pos, raw, seen)) + %ee : {S.cluster(rest, orig, _, S.cluster.done(done, cc, rest, dd)) == dd : S.St} + L1.cluster_dead(rest, orig, err, args, up, subs, path, bs, pos, raw, seen, S.cluster.done(done, cc, rest, dd)) + +# a cluster whose walker has failed stays failed, whatever letters are left +def cl.dead( + +rest: String, + +orig: String, + xx: S.St, + +done: Bool, + +err: S.ParseErr, + hh: {D1.dead_of(xx) == Some{err} : Maybe<&2, S.ParseErr>} +) -> {D1.dead_of(S.cluster(rest, orig, xx, done)) == Some{err} : Maybe<&2, S.ParseErr>}: + match xx: + case S.St{S.Dead{+ee}, +a, +g, +s, +p, +b, +o, +r, +n}: + ec = Equal.sym(S.St, S.cluster(rest, orig, S.St{S.Dead{ee}, a, g, s, p, b, o, r, n}, done), + S.St{S.Dead{ee}, a, g, s, p, b, o, r, n}, L1.cluster_dead(rest, orig, ee, a, g, s, p, b, o, r, n, done)) + %ec : {D1.dead_of(_) == Some{err} : Maybe<&2, S.ParseErr>} + hh + case S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n}: + none.absurd(&1, {D1.dead_of(S.cluster(rest, orig, S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n}, + done)) == Some{err} + : Maybe<&2, S.ParseErr>}, err, hh) + case S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}: + none.absurd(&1, {D1.dead_of(S.cluster(rest, orig, S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}, done)) + == Some{err} : Maybe<&2, S.ParseErr>}, err, hh) + case S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}: + none.absurd(&1, {D1.dead_of(S.cluster(rest, orig, S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}, + done)) == Some{err} + : Maybe<&2, S.ParseErr>}, err, hh) diff --git a/proofs/lemmas/FailedWalkerStays.bend b/proofs/lemmas/FailedWalkerStays.bend new file mode 100644 index 0000000..f5c400b --- /dev/null +++ b/proofs/lemmas/FailedWalkerStays.bend @@ -0,0 +1,9 @@ +# a failed walker stays failed +import Base +import ../../src/cli.bend as S +import ../../laws/OnceDead.bend as L1 + +def L1.once_dead(st, _nn, _path): + match st: + case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: + {==} diff --git a/proofs/lemmas/FinishedWalker.bend b/proofs/lemmas/FinishedWalker.bend new file mode 100644 index 0000000..fe30352 --- /dev/null +++ b/proofs/lemmas/FinishedWalker.bend @@ -0,0 +1,175 @@ +# the frame and Matched of a finished walker, defaults filled +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S +import ../../src/grow.bend as Grow + +# Done{.} and Fail{.} told apart +def vr.done_ty(rr: Result<&2, &2, S.ParseErr, S.Matched>) -> Type: + match rr: + case Done{_m}: + Unit + case Fail{_e}: + Empty + +# a failure read for the version is still no success +def vr.clash( + +spec: Shake.Cli, + ee: S.ParseErr, + found: S.Matched, + h: {Fail{S.version.err(spec, ee)} == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} +) -> Empty: + ef = Equal.sym(Result<&2, &2, S.ParseErr, S.Matched>, Fail{S.version.err(spec, ee)}, Done{found}, h) + %ef : vr.done_ty(_) + Unit{} + +# a parse that succeeded did so before the version was read off it: the +# version only ever rewrites a failure +def vr.done( + +spec: Shake.Cli, + rr: Result<&2, &2, S.ParseErr, S.Matched>, + +found: S.Matched, + h: {S.version.res(spec, rr) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} +) -> {rr == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}: + match rr: + case Done{mm}: + h + case Fail{ee}: + Empty.absurd({Fail{ee} == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, vr.clash(spec, ee, found, h)) + +# the two orders a binding's value can be reversed onto a list in agree +def fin.hit_rev( + hit: Bool, + +vv: String, + +xs: List<&2, String>, + +acc: List<&2, String> +) -> {List.reverse.go(&2, String, xs, S.get_all.bind.hit(hit, vv, acc)) + == List.reverse.go(&2, String, S.get_all.bind.hit(hit, vv, xs), acc) : List<&2, String>}: + match hit: + case True{}: + {==} + case False{}: + {==} + +# `get_all` of bindings reversed onto a list is its values reversed onto that +# list's values +def fin.g_rev_go( + xs: List<&2, S.Bind>, + +acc: List<&2, S.Bind>, + +nn: String +) -> {S.get_all.bind(List.reverse.go(&2, S.Bind, xs, acc), nn) + == List.reverse.go(&2, String, S.get_all.bind(xs, nn), S.get_all.bind(acc, nn)) : List<&2, String>}: + match xs: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Bind{+mm, +vv}: + Equal.trans(List<&2, String>, + S.get_all.bind(List.reverse.go(&2, S.Bind, tt, S.Bind{mm, vv} <> acc), nn), + List.reverse.go(&2, String, S.get_all.bind(tt, nn), + S.get_all.bind.hit(String.eq(mm, nn), vv, S.get_all.bind(acc, nn))), + List.reverse.go(&2, String, S.get_all.bind.hit(String.eq(mm, nn), vv, S.get_all.bind(tt, nn)), + S.get_all.bind(acc, nn)), + fin.g_rev_go(tt, S.Bind{mm, vv} <> acc, nn), + fin.hit_rev(String.eq(mm, nn), vv, S.get_all.bind(tt, nn), S.get_all.bind(acc, nn))) + +# `get_all` of reversed bindings is their values reversed +def fin.g_rev( + +xs: List<&2, S.Bind>, + +nn: String +) -> {S.get_all.bind(List.reverse(&2, S.Bind, xs), nn) == List.reverse(&2, String, S.get_all.bind(xs, nn)) + : List<&2, String>}: + fin.g_rev_go(xs, [], nn) + +# a bound name has values, and an unbound one none +def fin.bound_g.hit( + hit: Bool, + +vv: String, + +rest: Bool, + +vals: List<&2, String>, + ih: {rest == Bool.not(List.is_empty(&2, String, vals)) : Bool} +) -> {S.bound.at(hit, rest) == Bool.not(List.is_empty(&2, String, S.get_all.bind.hit(hit, vv, vals))) : Bool}: + match hit: + case True{}: + {==} + case False{}: + ih + +# whether a name is bound is whether it has values +def fin.bound_g( + xs: List<&2, S.Bind>, + +nn: String +) -> {S.bound(xs, nn) == Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))) : Bool}: + match xs: + case Nil{}: + {==} + case Con{hh, +tt}: + match hh: + case S.Bind{+mm, +vv}: + fin.bound_g.hit(String.eq(mm, nn), vv, S.bound(tt, nn), S.get_all.bind(tt, nn), fin.bound_g(tt, nn)) + +# the frame of the walker the words leave: levels `qs` in front of the level +# `args`, `bs`, above the levels `up`, and names `ms` after the path +def fin.Framed( + +spec: Shake.Cli, + +ws: List<&2, String>, + +qs: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +ms: List<&2, String> +) -> Type: + {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) + == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} + : Grow.Frame} + +# the Matched of the command at `path`: its level, defaults filled, with the +# levels below it +def fin.at( + +spec: Shake.Cli, + +ws: List<&2, String>, + +found: Shake.Matched, + +qs: List<&2, S.Level>, + +args: List<&2, S.Arg>, + +bs: List<&2, S.Bind>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +ms: List<&2, String>, + h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}, + h_fr: fin.Framed(spec, ws, qs, args, bs, up, path, ms), + h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} +) -> {S.at(found, path) + == Some{S.matched.build(S.Level{args, S.fill.args(args, bs)} <> List.reverse(&2, S.Level, S.fill.levels(qs)), ms)} + : Maybe<&2, S.Matched>}: + +fs = S.parse.walk(ws, S.parse.start(spec)) + +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} + +fq = S.fill.levels(qs) + +fu = S.fill.levels(up) + +r0 = List.reverse(&2, S.Level, fu) + +rq = List.reverse(&2, S.Level, fq) + +pm = List.append(&2, String, path, ms) + +lvs = List.append(&2, S.Level, qs, S.Level{args, bs} <> up) + em = Equal.trans(S.Matched, found, Grow.gw.build_of(Grow.gw.frame(fs)), + S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), + Grow.gw.finished(fs, found, vr.done(spec, S.parse.finish(fs), found, h_done)), + Equal.trans(S.Matched, Grow.gw.build_of(Grow.gw.frame(fs)), Grow.gw.build_of(Grow.Frame{lvs, pm}), + S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), + Equal.cong(Grow.Frame, S.Matched, fr => Grow.gw.build_of(fr), Grow.gw.frame(fs), Grow.Frame{lvs, pm}, h_fr), + Equal.cong(List<&2, S.Level>, S.Matched, ls => S.matched.build(ls, pm), + List.reverse(&2, S.Level, S.fill.levels(lvs)), List.append(&2, S.Level, r0, f0 <> rq), + Equal.trans(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(lvs)), + List.reverse(&2, S.Level, List.append(&2, S.Level, fq, f0 <> fu)), List.append(&2, S.Level, r0, f0 <> rq), + Equal.cong(List<&2, S.Level>, List<&2, S.Level>, ls => List.reverse(&2, S.Level, ls), S.fill.levels(lvs), + List.append(&2, S.Level, fq, f0 <> fu), Grow.gw.fill_mid(qs, args, bs, up)), + Grow.gw.lrev_mid(fq, f0, fu))))) + hl = Equal.trans(Nat, List.length(&2, S.Level, r0), List.length(&2, S.Level, fu), List.length(&2, String, path), + Grow.gw.len_rev(fu), + Equal.trans(Nat, List.length(&2, S.Level, fu), List.length(&2, S.Level, up), List.length(&2, String, path), + Grow.gw.len_fill(up), h_len)) + Equal.trans(Maybe<&2, S.Matched>, S.at(found, path), + S.at(S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), path), Some{S.matched.build(f0 <> rq, ms)}, + Equal.cong(S.Matched, Maybe<&2, S.Matched>, mm => S.at(mm, path), found, + S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), em), + Grow.gw.at_build(r0, path, f0, rq, ms, hl)) diff --git a/proofs/lemmas/HelpPathWalk.bend b/proofs/lemmas/HelpPathWalk.bend new file mode 100644 index 0000000..571d388 --- /dev/null +++ b/proofs/lemmas/HelpPathWalk.bend @@ -0,0 +1,161 @@ +# the help walk over the words after a request for help +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S +import ../../src/walk.bend as Walk +import ../../src/grow.bend as Grow +import ../../laws/HelpWalk.bend as L1 +import ../../laws/defs/HelpPathNames.bend as D1 +import ./WalkSteps.bend as M1 + +# the first word of a list, or none +def hp.head(ns: List<&2, String>) -> String: + match ns: + case Nil{}: + "" + case Con{hh, _t}: + hh + +# what `hp.walk` claims +def hp.ty( + +ns: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool +) -> Type: + &kids: List<&2, S.Sub> -> {S.parse.walk(ns, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}) + == S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen} : S.St} + +# the names under a found subcommand, carried to its own subcommands +def hp.names_tr( + +tt: List<&2, String>, + +aa: List<&2, S.Sub>, + +bb: List<&2, S.Sub>, + ee: {aa == bb : List<&2, S.Sub>}, + hh: {D1.help_names(tt, aa) == True{} : Bool} +) -> {D1.help_names(tt, bb) == True{} : Bool}: + Equal.trans(Bool, D1.help_names(tt, bb), D1.help_names(tt, aa), True{}, + Equal.cong(List<&2, S.Sub>, Bool, kk => D1.help_names(tt, kk), bb, aa, Equal.sym(List<&2, S.Sub>, aa, bb, + ee)), hh) + +# one name further down the help path, then the rest +def hp.cons( + +nn: String, + +tt: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +sn: String, + +sa: String, + +sg: List<&2, S.Arg>, + +kids: List<&2, S.Sub>, + e_fm: {S.find_sub(subs, nn) == Some{S.Sub{sn, sa, sg, kids}} : Maybe<&2, S.Sub>}, + ih: hp.ty(tt, args, up, kids, List.append(&2, String, path, [nn]), bs, pos, raw, seen) +) -> hp.ty(nn <> tt, args, up, subs, path, bs, pos, raw, seen): + (k2, e2) = ih + +k3 = {k2 : List<&2, S.Sub>} + +mid = {S.St{S.Help{}, args, up, kids, List.append(&2, String, path, [nn]), bs, pos, raw, seen} : S.St} + +fin_st = {S.St{S.Help{}, args, up, k3, List.append(&2, String, path, nn <> tt), bs, pos, raw, seen} : S.St} + es = Equal.cong(Maybe<&2, S.Sub>, S.St, mm => S.parse.walk(tt, S.parse.step.help_path(mm, nn, args, up, subs, + path, bs, + pos, raw, seen)), S.find_sub(subs, nn), Some{S.Sub{sn, sa, sg, kids}}, e_fm) + (k3, Equal.trans(S.St, S.parse.walk(nn <> tt, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}), + S.parse.walk(tt, mid), fin_st, es, + Equal.trans(S.St, S.parse.walk(tt, mid), + S.St{S.Help{}, args, up, k3, List.append(&2, String, List.append(&2, String, path, [nn]), tt), bs, pos, + raw, seen}, + fin_st, e2, + Equal.cong(List<&2, String>, S.St, pp => S.St{S.Help{}, args, up, k3, pp, bs, pos, raw, seen}, + List.append(&2, String, List.append(&2, String, path, [nn]), tt), List.append(&2, String, path, nn <> tt), + Grow.gw.sapp(path, [nn], tt))))) + +# words that each name a subcommand under the one before extend a help path +def hp.walk( + ns: List<&2, String>, + +subs: List<&2, S.Sub>, + fm: Maybe<&2, S.Sub>, + +e_fm: {S.find_sub(subs, hp.head(ns)) == fm : Maybe<&2, S.Sub>}, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + +h_ns: {D1.help_names(ns, subs) == True{} : Bool} +) -> hp.ty(ns, args, up, subs, path, bs, pos, raw, seen): + match ns: + case Nil{}: + (subs, Equal.cong(List<&2, String>, S.St, pp => S.St{S.Help{}, args, up, subs, pp, bs, pos, raw, seen}, path, + List.append(&2, String, path, []), Equal.sym(List<&2, String>, List.append(&2, String, path, []), path, + Grow.gw.nil_s(path)))) + case Con{+nn, +tt}: + match fm: + case None{}: + Empty.absurd(hp.ty(nn <> tt, args, up, subs, path, bs, pos, raw, seen), + Walk.wk.false_true(Equal.trans(Bool, False{}, Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), True{}, + Equal.cong(Maybe<&2, S.Sub>, Bool, mm => Maybe.is_some(&2, S.Sub, mm), None{}, S.find_sub(subs, nn), + Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, nn), None{}, e_fm)), + Walk.wk.and_l(Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), + D1.help_names(tt, D1.kids_of(S.find_sub(subs, nn))), h_ns)))) + case Some{ss}: + match ss: + case S.Sub{+sn, +sa, +sg, +kids}: + hp.cons(nn, tt, args, up, subs, path, bs, pos, raw, seen, sn, sa, sg, kids, e_fm, + hp.walk(tt, kids, S.find_sub(kids, hp.head(tt)), {==}, args, up, List.append(&2, String, path, + [nn]), bs, + pos, raw, seen, + hp.names_tr(tt, D1.kids_of(S.find_sub(subs, nn)), kids, + Equal.cong(Maybe<&2, S.Sub>, List<&2, S.Sub>, mm => D1.kids_of(mm), S.find_sub(subs, nn), + Some{S.Sub{sn, sa, sg, kids}}, e_fm), + Walk.wk.and_r(Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), + D1.help_names(tt, D1.kids_of(S.find_sub(subs, nn))), h_ns)))) + +# the walk after the words before, read from where they left the walker +def hp.from( + +spec: Shake.Cli, + +pre: List<&2, String>, + +ns: List<&2, String>, + +st: S.St, + +big: S.St, + h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St}, + ew: {S.parse.walk(ns, st) == big : S.St} +) -> {S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)) == big : S.St}: + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)), S.parse.walk(ns, st), big, + M1.rest.after(spec, pre, ns, st, h_at), ew) + +# the help walk, read from the start +def hp.at( + +spec: Shake.Cli, + +pre: List<&2, String>, + +ns: List<&2, String>, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +raw: Bool, + +seen: Bool, + h_at: {S.parse.walk(pre, S.parse.start(spec)) == S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen} : S.St}, + ww: hp.ty(ns, args, up, subs, path, bs, pos, raw, seen) +) -> &kids: List<&2, S.Sub> -> {S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)) + == S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen} : S.St}: + (k1, e1) = ww + +kids = {k1 : List<&2, S.Sub>} + (kids, hp.from(spec, pre, ns, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}, + S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen}, h_at, e1)) + +def L1.help_walk(spec, pre, ns, args, up, subs, path, bs, pos, raw, seen, h_at, h_ns): + hp.at(spec, pre, ns, args, up, subs, path, bs, pos, raw, seen, h_at, + hp.walk(ns, subs, S.find_sub(subs, hp.head(ns)), {==}, args, up, path, bs, pos, raw, seen, h_ns)) diff --git a/proofs/lemmas/LastOfSnoc.bend b/proofs/lemmas/LastOfSnoc.bend new file mode 100644 index 0000000..1539aa4 --- /dev/null +++ b/proofs/lemmas/LastOfSnoc.bend @@ -0,0 +1,10 @@ +# the last element of a list grown at its end is what was added +import Base +import ../../laws/LastSnoc.bend as L1 + +def L1.last_snoc(xs, vv, _dflt): + match xs: + case Nil{}: + {==} + case Con{hh, tt}: + L1.last_snoc(tt, vv, Some{hh}) diff --git a/proofs/lemmas/NameAppend.bend b/proofs/lemmas/NameAppend.bend new file mode 100644 index 0000000..a0ac83c --- /dev/null +++ b/proofs/lemmas/NameAppend.bend @@ -0,0 +1,17 @@ +# appending lists of names is associative +import Base + +# appending names is associative +def sc.app( + aa: List<&2, String>, + +bb: List<&2, String>, + +cc: List<&2, String> +) -> {List.append(&2, String, List.append(&2, String, aa, bb), cc) + == List.append(&2, String, aa, List.append(&2, String, bb, cc)) : List<&2, String>}: + match aa: + case Nil{}: + {==} + case Con{+hh, +tt}: + Equal.cong(List<&2, String>, List<&2, String>, xs => hh <> xs, + List.append(&2, String, List.append(&2, String, tt, bb), cc), + List.append(&2, String, tt, List.append(&2, String, bb, cc)), sc.app(tt, bb, cc)) diff --git a/proofs/lemmas/StringAppendEmpty.bend b/proofs/lemmas/StringAppendEmpty.bend new file mode 100644 index 0000000..ec254ce --- /dev/null +++ b/proofs/lemmas/StringAppendEmpty.bend @@ -0,0 +1,10 @@ +# a string with nothing appended is itself +import Base + +# a string with nothing after it is itself +def hs.nil(xs: String) -> {xs ++ "" == xs : String}: + match xs: + case SNil{}: + {==} + case SCon{+hh, +tt}: + Equal.cong(String, String, ys => SCon{hh, ys}, tt ++ "", tt, hs.nil(tt)) diff --git a/proofs/lemmas/WalkSteps.bend b/proofs/lemmas/WalkSteps.bend new file mode 100644 index 0000000..8275559 --- /dev/null +++ b/proofs/lemmas/WalkSteps.bend @@ -0,0 +1,291 @@ +# one walker step: entering a subcommand, a plain word, a positional, a long +# option +import Base +import ../../main.bend as Shake +import ../../src/cli.bend as S +import ../../src/walk.bend as Walk +import ../../laws/DropZero.bend as L1 +import ../../laws/LongArgStep.bend as L2 +import ../../laws/PrefixEmpty.bend as L3 +import ../../laws/UnknownLongDies.bend as L4 +import ../../laws/UnknownLongStep.bend as L5 +import ../../laws/WalkAppend.bend as L6 +import ../../laws/defs/WalkerFailure.bend as D1 + +def L6.walk_append(xs, ys, st): + match xs: + case Nil{}: + {==} + case Con{+hh, tt}: + L6.walk_append(tt, ys, S.parse.step(hh, st)) + +def L3.prefix_empty(ss): + match ss: + case SNil{}: + {==} + case SCon{_h, _t}: + {==} + +def L1.drop_zero(ss): + match ss: + case SNil{}: + {==} + case SCon{_h, _t}: + {==} + +def L5.unknown_long_step( + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + h_dd, + h_cut, + h_name, + h_none, + h_lh +): + e1 = Equal.sym(Bool, String.eq("--" ++ body, "--"), False{}, h_dd) + %e1 : {S.parse.step.end(_, "--" ++ body, args, up, subs, path, bs, pos, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e0 = Equal.sym(Bool, String.starts_with(body, ""), True{}, L3.prefix_empty(body)) + %e0 : {S.parse.step.kind.go(_, True{}, "--" ++ body, args, up, subs, path, bs, pos, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + ed = Equal.sym(String, String.drop(body, 0n), body, L1.drop_zero(body)) + %ed : {S.parse.take_long(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e2 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) + %e2 : {S.parse.take_long.cut(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e3 = Equal.sym(Bool, String.is_empty(name), False{}, h_name) + %e3 : {S.parse.take_long.named(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e4 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), None{}, h_none) + %e4 : {S.parse.take_long.found(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + e5 = Equal.sym(Bool, S.long_help(name, val, seen), False{}, h_lh) + %e5 : {S.parse.take_long.help(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} + {==} + +def L4.unknown_long_dies( + spec, + pre, + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + h_at, + h_dd, + h_cut, + h_name, + h_none, + h_lh +): + e1 = Equal.sym(S.St, + S.parse.walk(List.append(&2, String, pre, ["--" ++ body]), S.parse.start(spec)), + S.parse.walk(["--" ++ body], S.parse.walk(pre, S.parse.start(spec))), + L6.walk_append(pre, ["--" ++ body], S.parse.start(spec))) + %e1 : {D1.dead_of(_) == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + e2 = Equal.sym(S.St, + S.parse.walk(pre, S.parse.start(spec)), + S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, + h_at) + %e2 : {D1.dead_of(S.parse.walk(["--" ++ body], _)) == Some{S.UnknownFlag{path, "--" ++ body}} + : Maybe<&2, S.ParseErr>} + e3 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), + S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen}, + L5.unknown_long_step(body, name, val, args, up, subs, path, bs, pos, seen, + h_dd, h_cut, h_name, h_none, h_lh)) + %e3 : {D1.dead_of(_) == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} + {==} + +def L2.long_arg_step( + body, + name, + val, + args, + up, + subs, + path, + bs, + pos, + seen, + h_dd, + h_cut, + h_name +): + e1 = Equal.sym(Bool, String.eq("--" ++ body, "--"), False{}, h_dd) + %e1 : {S.parse.step.end(_, "--" ++ body, args, up, subs, path, bs, pos, seen) + == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen) : S.St} + e0 = Equal.sym(Bool, String.starts_with(body, ""), True{}, L3.prefix_empty(body)) + %e0 : {S.parse.step.kind.go(_, True{}, "--" ++ body, args, up, subs, path, bs, pos, seen) + == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen) : S.St} + ed = Equal.sym(String, String.drop(body, 0n), body, L1.drop_zero(body)) + %ed : {S.parse.take_long(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen) : S.St} + e2 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) + %e2 : {S.parse.take_long.cut(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen) : S.St} + e3 = Equal.sym(Bool, String.is_empty(name), False{}, h_name) + %e3 : {S.parse.take_long.named(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) + == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, + seen) : S.St} + {==} + +# the walk through `tok` is one step from where the words before it stood +def kept.stepped( + +spec: Shake.Cli, + +pre: List<&2, String>, + +tok: String, + +st: S.St, + h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} +) -> {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) == S.parse.step(tok, st) : S.St}: + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), + S.parse.walk([tok], S.parse.walk(pre, S.parse.start(spec))), S.parse.step(tok, st), + L6.walk_append(pre, [tok], S.parse.start(spec)), + Equal.cong(S.St, S.St, xx => S.parse.walk([tok], xx), S.parse.walk(pre, S.parse.start(spec)), st, h_at)) + +# an allowed value taken by the next positional, through `take_choice` +def pos.take( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +raw: Bool, + +name: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +rest: List<&2, S.Arg>, + h_ok: {S.allowed(cs, tok) == True{} : Bool} +) -> {S.parse.take_pos(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, raw) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), raw, True{}} : S.St}: + e2 = Equal.sym(Bool, S.allowed(cs, tok), True{}, h_ok) + %e2 : {S.parse.take_choice(_, name, tok, args, up, subs, path, bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), raw, True{}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), raw, True{}} : S.St} + {==} + +# a plain word taken by the next positional +def pos.step( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +seen: Bool, + +name: String, + +sh: Maybe<&2, String>, + +lo: Maybe<&2, String>, + +kind: S.ArgKind, + +hp: String, + +req: Bool, + +dflt: S.Fallback, + +cs: List<&2, String>, + +rest: List<&2, S.Arg>, + h_dd: {String.eq(tok, "--") == False{} : Bool}, + h_help: {String.eq(tok, "help") == False{} : Bool}, + +h_dash: {String.starts_with(tok, "-") == False{} : Bool}, + h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>}, + h_ok: {S.allowed(cs, tok) == True{} : Bool} +) -> {S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, + S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, seen}) + == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St}: + Equal.trans(S.St, + S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, + False{}, seen}), + S.parse.take_pos(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}), + S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, + S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), + S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, + Walk.wk.word_step(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, seen, + h_dd, h_help, h_dash, h_sub), + pos.take(tok, args, up, subs, path, bs, False{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)) + +# the words before `fs`, then `fs`, from where the words before left the walker +def rest.after( + +spec: Shake.Cli, + +pre: List<&2, String>, + +fs: List<&2, String>, + +st: S.St, + h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} +) -> {S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)) == S.parse.walk(fs, st) : S.St}: + Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)), + S.parse.walk(fs, S.parse.walk(pre, S.parse.start(spec))), S.parse.walk(fs, st), + L6.walk_append(pre, fs, S.parse.start(spec)), + Equal.cong(S.St, S.St, xx => S.parse.walk(fs, xx), S.parse.walk(pre, S.parse.start(spec)), st, h_at)) + +# a plain word before `--` is read as a word: a subcommand or a positional +def enter.word( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + h_dd: {String.eq(tok, "--") == False{} : Bool}, + h_help: {String.eq(tok, "help") == False{} : Bool}, + +h_dash: {String.starts_with(tok, "-") == False{} : Bool} +) -> {S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) + == S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) : S.St}: + +rhs = S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) + e1 = Equal.sym(Bool, String.eq(tok, "--"), False{}, h_dd) + %e1 : {S.parse.step.end(_, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + e2 = Equal.sym(Bool, String.eq(tok, "help"), False{}, h_help) + %e2 : {S.parse.step.help(Bool.and(_, Bool.not(seen)), tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + e3 = Equal.sym(Bool, String.starts_with(tok, "--"), False{}, Walk.wk.dash(tok, h_dash)) + %e3 : {S.parse.step.kind.go(_, String.starts_with(tok, "-"), tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + e4 = Equal.sym(Bool, String.starts_with(tok, "-"), False{}, h_dash) + %e4 : {S.parse.step.kind.go(False{}, _, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} + {==} + +# a found subcommand, entered or refused, as the first blocking positional says +def enter.go( + +tok: String, + +args: List<&2, S.Arg>, + +up: List<&2, S.Level>, + +subs: List<&2, S.Sub>, + +path: List<&2, String>, + +bs: List<&2, S.Bind>, + +pos: List<&2, S.Arg>, + +seen: Bool, + +sub: S.Sub, + h_sub: {S.find_sub(subs, tok) == Some{sub} : Maybe<&2, S.Sub>} +) -> {S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) + == S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen) : S.St}: + ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, tok), Some{sub}, h_sub) + %ee : {S.parse.word.go(_, tok, args, up, subs, path, bs, pos, False{}, seen) + == S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen) : S.St} + {==} diff --git a/src/.split_dropped.txt b/src/.split_dropped.txt new file mode 100644 index 0000000..59d7f75 --- /dev/null +++ b/src/.split_dropped.txt @@ -0,0 +1,2 @@ +# law defs +# proof helpers diff --git a/src/LAWS.bend b/src/LAWS.bend deleted file mode 100644 index a12ae3d..0000000 --- a/src/LAWS.bend +++ /dev/null @@ -1,4025 +0,0 @@ -# shake: the laws. The human states them; PROOF.bend must prove them. A law -# tagged with a SPEC.md ID proves that requirement, and its statement changes -# only when the row does. The untagged ones are lemmas for the proofs to come. -import Base -import ../main.bend as Shake -import ../src/cli.bend as S -import ../src/check.bend as K -import ../src/args.bend as Args -import ../src/grow.bend as Grow -import ../src/walk.bend as Walk - -# a `&2` word list read back as the `&1` list IO.args answers -def down(ys: List<&2, String>) -> List<&1, String>: - match ys: - case Nil{}: - Nil{} - case Con{+hh, tt}: - hh <> down(tt) - -# the words of an IO.args line after its first, the program name -def after_head(xs: List<&1, String>) -> List<&1, String>: - match xs: - case Nil{}: - Nil{} - case Con{_hh, tt}: - tt - -# the error a walker has failed with, or None while it is still going -def dead_of(st: S.St) -> Maybe<&2, S.ParseErr>: - match st: - case S.St{S.Dead{err}, _a, _g, _s, _p, _b, _o, _r, _n}: - Some{err} - case S.St{_mode, _a, _g, _s, _p, _b, _o, _r, _n}: - None{} - -# LAW: a word that starts `--` is flag-shaped -law looks_flag_long: - for ss: String - {S.looks_flag("--" ++ ss) == True{} : Bool} - -# LAW: empty choices accept every value -law allowed_any: - for vv: String - {S.allowed([], vv) == True{} : Bool} - -# LAW: `get` on a Matched with no bindings is None -law get_nil: - for name: String - {S.get(S.Leaf{[]}, name) == None{} : Maybe<&2, String>} - -# LAW: no flag is on in a Matched with no bindings -law on_nil: - for name: String - {S.on(S.Leaf{[]}, name) == False{} : Bool} - -# LAW: a request for help has no error text, for every spec and path -# SHAKE-ERR-1 -law err_text_help: - for spec: Shake.Cli - for path: List<&2, String> - {Shake.err_text(spec, S.NeedHelp{path}) == "" : String} - -# LAW: a request for the version has no error text, for every spec -# SHAKE-ERR-1 -law err_text_version: - for spec: Shake.Cli - {Shake.err_text(spec, S.NeedVersion{}) == "" : String} - -# whether an error is a request for the version -def version.is(err: Shake.ParseErr) -> Bool: - match err: - case S.NeedVersion{}: - True{} - case _other: - False{} - -# LAW: the error text is empty exactly when the parse asked for help or for -# the version -# SHAKE-ERR-1 -law err_text_iff: - for +spec: Shake.Cli - for +err: Shake.ParseErr - {String.is_empty(Shake.err_text(spec, err)) - == Bool.or(Maybe.is_some(&2, List<&2, String>, Shake.help_path(err)), version.is(err)) : Bool} - -# LAW: a flag binding's hit keeps whatever list follows it (a lemma) -law hit_append: - for bb: Bool - for +vv: String - for xs: List<&2, String> - for ys: List<&2, String> - {S.get_all.bind.hit(bb, vv, List.append(&2, String, xs, ys)) - == List.append(&2, String, S.get_all.bind.hit(bb, vv, xs), ys) : List<&2, String>} - -# LAW: the values bound to a name in two lists of bindings are the first's -# then the second's (a lemma over the bindings alone) -law bind_values_append: - for bs: List<&2, S.Bind> - for +cs: List<&2, S.Bind> - for +name: String - {S.get_all.bind(List.append(&2, S.Bind, bs, cs), name) - == List.append(&2, String, S.get_all.bind(bs, name), S.get_all.bind(cs, name)) - : List<&2, String>} - -# LAW: the last of a list that ends in `vv` is `vv`, whatever the default -law last_snoc: - for xs: List<&2, String> - for +vv: String - for dflt: Maybe<&2, String> - {S.get.last(List.append(&2, String, xs, [vv]), dflt) == Some{vv} : Maybe<&2, String>} - -# LAW: `get_all` of two lists of bindings is the first's values, then the -# second's -# SHAKE-GET-1 -law get_all_append: - for +bs: List<&2, S.Bind> - for +cs: List<&2, S.Bind> - for +name: String - {Shake.get_all(S.Leaf{List.append(&2, S.Bind, bs, cs)}, name) - == List.append(&2, String, Shake.get_all(S.Leaf{bs}, name), - Shake.get_all(S.Leaf{cs}, name)) : List<&2, String>} - -# LAW: one binding of a name gives `get_all` its value -# SHAKE-GET-1 -law get_all_hit: - for +name: String - for +vv: String - {Shake.get_all(S.Leaf{[S.Bind{name, vv}]}, name) == [vv] : List<&2, String>} - -# LAW: one binding of another name gives `get_all` nothing -# SHAKE-GET-1 -law get_all_miss: - for +other: String - for +name: String - for +vv: String - for differ: {String.eq(other, name) == False{} : Bool} - {Shake.get_all(S.Leaf{[S.Bind{other, vv}]}, name) == [] : List<&2, String>} - -# LAW: `get` is the value of the last binding of the name -# SHAKE-GET-1 -law get_last: - for +bs: List<&2, S.Bind> - for +cs: List<&2, S.Bind> - for +name: String - for +vv: String - for after: {Shake.get_all(S.Leaf{cs}, name) == [] : List<&2, String>} - {Shake.get(S.Leaf{List.append(&2, S.Bind, bs, S.Bind{name, vv} <> cs)}, name) - == Some{vv} : Maybe<&2, String>} - -# LAW: `get` is None when nothing binds the name -# SHAKE-GET-1 -law get_none: - for +bs: List<&2, S.Bind> - for +name: String - for none: {Shake.get_all(S.Leaf{bs}, name) == [] : List<&2, String>} - {Shake.get(S.Leaf{bs}, name) == None{} : Maybe<&2, String>} - -# LAW: a flag is on exactly when `get` reads Some "true" -# SHAKE-GET-1 -law on_some: - for +found: Shake.Matched - for +name: String - for +vv: String - for h_get: {Shake.get(found, name) == Some{vv} : Maybe<&2, String>} - {Shake.on(found, name) == String.eq(vv, "true") : Bool} - -# LAW: nothing is on that nothing bound -# SHAKE-GET-1 -law on_none: - for +found: Shake.Matched - for +name: String - for h_get: {Shake.get(found, name) == None{} : Maybe<&2, String>} - {Shake.on(found, name) == False{} : Bool} - -# LAW: a command's `get_all` reads only its own bindings: the subcommand -# selected under it changes nothing -# SHAKE-GET-1 -law get_all_node: - for +bs: List<&2, S.Bind> - for +nn: String - for +below: Shake.Matched - for +name: String - {Shake.get_all(S.Node{bs, nn, below}, name) == Shake.get_all(S.Leaf{bs}, name) : List<&2, String>} - -# LAW: a command's `get` reads only its own bindings -# SHAKE-GET-1 -law get_node: - for +bs: List<&2, S.Bind> - for +nn: String - for +below: Shake.Matched - for +name: String - {Shake.get(S.Node{bs, nn, below}, name) == Shake.get(S.Leaf{bs}, name) : Maybe<&2, String>} - -# LAW: no subcommand was selected under a command with no Matched below it -# SHAKE-GET-2 -law sub_name_leaf: - for +bs: List<&2, S.Bind> - {Shake.sub_name(S.Leaf{bs}) == None{} : Maybe<&2, String>} - -# LAW: `sub_name` is the name of the subcommand selected -# SHAKE-GET-2 -law sub_name_node: - for +bs: List<&2, S.Bind> - for +nn: String - for +below: Shake.Matched - {Shake.sub_name(S.Node{bs, nn, below}) == Some{nn} : Maybe<&2, String>} - -# LAW: `sub_of` the subcommand selected is its Matched -# SHAKE-GET-2 -law sub_of_hit: - for +bs: List<&2, S.Bind> - for +nn: String - for +below: Shake.Matched - {Shake.sub_of(S.Node{bs, nn, below}, nn) == Some{below} : Maybe<&2, Shake.Matched>} - -# LAW: `sub_of` any other name is None -# SHAKE-GET-2 -law sub_of_miss: - for +bs: List<&2, S.Bind> - for +nn: String - for +below: Shake.Matched - for +name: String - for differ: {String.eq(nn, name) == False{} : Bool} - {Shake.sub_of(S.Node{bs, nn, below}, name) == None{} : Maybe<&2, Shake.Matched>} - -# LAW: `sub_of` is None when no subcommand was selected -# SHAKE-GET-2 -law sub_of_leaf: - for +bs: List<&2, S.Bind> - for +name: String - {Shake.sub_of(S.Leaf{bs}, name) == None{} : Maybe<&2, Shake.Matched>} - -# LAW: no names are selected below a command with no subcommand selected -# SHAKE-GET-2 -law path_of_leaf: - for +bs: List<&2, S.Bind> - {Shake.path_of(S.Leaf{bs}) == [] : List<&2, String>} - -# LAW: the names selected below a command are its subcommand's name, then the -# names selected below that -# SHAKE-GET-2 -law path_of_node: - for +bs: List<&2, S.Bind> - for +nn: String - for +below: Shake.Matched - {Shake.path_of(S.Node{bs, nn, below}) == nn <> Shake.path_of(below) : List<&2, String>} - -# `at` of what `sub_of` found, or None when it found nothing -def at.via(mm: Maybe<&2, Shake.Matched>, rest: List<&2, String>) -> Maybe<&2, Shake.Matched>: - match mm: - case None{}: - None{} - case Some{below}: - Shake.at(below, rest) - -# LAW: `at` an empty path is the Matched itself -# SHAKE-GET-2 -law at_nil: - for +found: Shake.Matched - {Shake.at(found, []) == Some{found} : Maybe<&2, Shake.Matched>} - -# LAW: `at` a path follows its first name with `sub_of`, then the rest -# SHAKE-GET-2 -law at_cons: - for +found: Shake.Matched - for +name: String - for +rest: List<&2, String> - {Shake.at(found, name <> rest) == at.via(Shake.sub_of(found, name), rest) : Maybe<&2, Shake.Matched>} - -# LAW: `copy` keeps every word IO.args gives, all of them, in order and -# unchanged: read back, the copy is the list it was made from -# SHAKE-ARGS-1 -law copy_keeps: - for xs: List<&1, String> - {down(Args.copy(xs)) == xs : List<&1, String>} - -# LAW: `words` drops the program name IO.args starts with and keeps every -# word after it, in order and unchanged; a line with no program name gives -# no arguments -# SHAKE-ARGS-1 -law words_keeps: - for xs: List<&1, String> - {down(Args.words(xs)) == after_head(xs) : List<&1, String>} - -# LAW: walking two word lists is walking the first, then the second (a lemma) -law walk_append: - for xs: List<&2, String> - for ys: List<&2, String> - for st: S.St - {S.parse.walk(List.append(&2, String, xs, ys), st) - == S.parse.walk(ys, S.parse.walk(xs, st)) : S.St} - -# LAW: a failed walker stays as it is, whatever words follow (a lemma) -law dead_walk: - for ys: List<&2, String> - for +err: S.ParseErr - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +binds: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - {S.parse.walk(ys, S.St{S.Dead{err}, args, up, subs, path, binds, pos, raw, seen}) - == S.St{S.Dead{err}, args, up, subs, path, binds, pos, raw, seen} : S.St} - -# LAW: a walker that has failed finishes as that failure, whatever words -# follow (a lemma) -law fail_from: - for st: S.St - for +more: List<&2, String> - for +err: S.ParseErr - for died: {dead_of(st) == Some{err} : Maybe<&2, S.ParseErr>} - {S.parse.finish(S.parse.walk(more, st)) == Fail{err} - : Result<&2, &2, S.ParseErr, S.Matched>} - -# LAW: once a word makes the parse fail, the words after it do not change -# what the parse answers -# SHAKE-PARSE-9 -law fail_stays: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +more: List<&2, String> - for +err: Shake.ParseErr - for +died: {dead_of(S.parse.walk(ws, S.parse.start(spec))) == Some{err} : Maybe<&2, S.ParseErr>} - {Shake.parse(spec, List.append(&2, String, ws, more)) == Shake.parse(spec, ws) - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: every string starts with the empty string (a lemma) -law prefix_empty: - for ss: String - {String.starts_with(ss, "") == True{} : Bool} - -# LAW: dropping nothing from a string leaves it (a lemma) -law drop_zero: - for ss: String - {String.drop(ss, 0n) == ss : String} - -# LAW: in a free walker, a long option whose spelling no argument of the -# current command has kills the walk with UnknownFlag of the whole word (a -# step lemma) -law unknown_long_step: - for +body: String - for +name: String - for val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_none: {S.by_long(args, name) == None{} : Maybe<&2, S.Arg>} - for h_lh: {S.long_help(name, val, seen) == False{} : Bool} - {S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - -# LAW: a walk that reaches an unknown long option dies there (a lemma) -law unknown_long_dies: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +body: String - for +name: String - for val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_none: {S.by_long(args, name) == None{} : Maybe<&2, S.Arg>} - for h_lh: {S.long_help(name, val, seen) == False{} : Bool} - {dead_of(S.parse.walk(List.append(&2, String, pre, ["--" ++ body]), S.parse.start(spec))) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - -# LAW: wherever the words before it leave the parse, in any command and with -# any bindings, a long option that no argument of that command spells, other -# than a bare `--help` that asks for help, fails the parse with UnknownFlag of -# the word, whatever follows -# SHAKE-TOK-7 -# SHAKE-ERR-2 -law unknown_long: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_none: {S.by_long(args, name) == None{} : Maybe<&2, S.Arg>} - for h_lh: {S.long_help(name, val, seen) == False{} : Bool} - for h_ver: {S.version.asks(spec, path, "--" ++ body) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Fail{S.UnknownFlag{path, "--" ++ body}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: when the words before `tok` reach a state where `tok` fails the walk, -# the walk through `tok` has failed with that error (a lemma) -law step_dies: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +tok: String - for +st: S.St - for +err: S.ParseErr - for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} - for h_step: {dead_of(S.parse.step(tok, st)) == Some{err} : Maybe<&2, S.ParseErr>} - {dead_of(S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec))) - == Some{err} : Maybe<&2, S.ParseErr>} - -# LAW: when the words before `tok` reach a state where `tok` fails the walk, -# the parse fails with that error, read as a request for the version when it -# is one, whatever follows (a lemma: every refusal row is its step fact -# through this) -law refused: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +st: S.St - for +err: S.ParseErr - for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} - for h_step: {dead_of(S.parse.step(tok, st)) == Some{err} : Maybe<&2, S.ParseErr>} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.version.err(spec, err)} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: once a short cluster has ended, the letters left change nothing (a -# lemma) -law cluster_done: - for body: String - for +orig: String - for +st: S.St - {S.cluster(body, orig, st, True{}) == st : S.St} - -# LAW: in a free walker, a short option word whose first letter no argument -# of the current command has kills the walk with UnknownFlag of the whole -# word (a step lemma) -law unknown_short_step: - for +cc: Char - for +rest: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool} - for h_dash: {Char.is_eq(cc, '-') == False{} : Bool} - for h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>} - for h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} - {S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - -# LAW: wherever the words before it leave the walker free, a short option -# word whose first letter no argument of that command has fails the parse -# with UnknownFlag of the word, whatever follows -# SHAKE-TOK-7 -# SHAKE-ERR-2 -law unknown_short: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +cc: Char - for +rest: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool} - for h_dash: {Char.is_eq(cc, '-') == False{} : Bool} - for h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>} - for h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} - for h_ver: {S.version.asks(spec, path, "-" ++ SCon{cc, rest}) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["-" ++ SCon{cc, rest}]), more)) - == Fail{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - - -# LAW: wherever the words before it leave the walker free with no positional -# left, a plain word that names no subcommand fails the parse with Unexpected -# of the word, whatever follows -# SHAKE-PARSE-2 -# SHAKE-ERR-2 -law no_pos_left: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, [], False{}, seen} : S.St} - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for h_help: {String.eq(tok, "help") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.Unexpected{path, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free with a positional -# next whose choices do not allow it, a plain word that names no subcommand -# fails the parse with BadValue of that positional and the word, whatever -# follows -# SHAKE-PARSE-5 -# SHAKE-ERR-2 -law choice_refused: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - False{}, seen} : S.St} - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for h_help: {String.eq(tok, "help") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} - for h_bad: {S.allowed(cs, tok) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.BadValue{path, name, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: a free walker hands a long option word to `take_long.found`, with the -# argument its name spells and the value after `=` (a step lemma) -law long_arg_step: - for +body: String - for +name: String - for +val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - {S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, - False{}, seen) : S.St} - -# LAW: wherever the words before it leave the walker free, a long option -# that spells a flag not yet given and gives it a value fails the parse with -# Unexpected of the word, whatever follows -# SHAKE-TOK-5 -# SHAKE-ERR-2 -law flag_long_valued: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for +vv: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_once: {S.bound(bs, nn) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Fail{S.Unexpected{path, "--" ++ body}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave an option waiting for its value, a -# word that starts with `-` and is not `-` fails the parse with Missing of the -# option, whatever follows -# SHAKE-TOK-6 -# SHAKE-ERR-2 -law value_flag_shaped: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +name: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} - for h_flag: {S.looks_flag(tok) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.NoValue{path, name}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: when the words end with an option still waiting for its value, the -# parse fails with Missing of the option -# SHAKE-TOK-6 -# SHAKE-ERR-2 -law value_absent: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +name: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for h_at: {S.parse.walk(ws, S.parse.start(spec)) - == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} - {Shake.parse(spec, ws) == Fail{S.NoValue{path, name}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - - -# LAW: wherever the words before it leave the walker reading a help path, a -# word that names no subcommand where the path stands fails the parse with -# Unexpected of the word, whatever follows -# SHAKE-PARSE-8 -# SHAKE-ERR-2 -law help_unknown: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen} : S.St} - for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.Unexpected{path, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free, a word `--=...`, -# with nothing between `--` and `=`, fails the parse with UnknownFlag of the -# word, whatever follows -# SHAKE-TOK-1 -# SHAKE-ERR-2 -law long_no_name: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for +val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for +h_empty: {String.is_empty(name) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Fail{S.UnknownFlag{path, "--" ++ body}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: the once-check, told the name was given before, fails with Repeated -# whatever the step it guards would have done (a lemma) -law once_dead: - for st: S.St - for +nn: String - for +path: List<&2, String> - {dead_of(S.parse.once(True{}, st, nn, path)) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - -# LAW: wherever the words before it leave the walker free, a long option that -# spells a flag already given fails the parse with Repeated of its name, -# whatever follows -# SHAKE-PARSE-10 -# SHAKE-ERR-2 -law repeated_long_flag: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for +val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_again: {S.bound(bs, nn) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Fail{S.Repeated{path, nn}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free, a long option that -# spells an option built with `opt` already given fails the parse with Repeated of its name, -# whatever follows -# SHAKE-PARSE-10 -# SHAKE-ERR-2 -law repeated_long_opt: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for +val: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_again: {S.bound(bs, nn) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Fail{S.Repeated{path, nn}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# `get_all` of the command a path reaches in a Matched, when it reaches one -def all_at.go(mm: Maybe<&2, Shake.Matched>, +name: String) -> Maybe<&2, List<&2, String>>: - match mm: - case None{}: - None{} - case Some{found}: - Some{Shake.get_all(found, name)} - -# `get_all` of the command at `path` in a Matched -def all_at(found: Shake.Matched, path: List<&2, String>, +name: String) -> Maybe<&2, List<&2, String>>: - all_at.go(Shake.at(found, path), name) - -# LAW: when the words `mid`, read where the words before them leave the -# walker, stay in the command at `path` and put bindings `xs` in front of the -# bindings `bs` it made, and the parse succeeds, `get_all` of a name in that -# command's Matched reads its values in `bs`, then its values in `xs`, both in -# the order given, then any it bound after, whatever follows (a lemma) -law kept_all: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +mid: List<&2, String> - for +more: List<&2, String> - for +st: S.St - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +xs: List<&2, S.Bind> - for +bs: List<&2, S.Bind> - for +name: String - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} - for h_mid: {Grow.view(S.parse.walk(mid, st)) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} - : Grow.View} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, mid), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), - List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after))} - : Maybe<&2, List<&2, String>>} - -# LAW: when the words before `tok` reach a state where `tok` stays in the -# command at `path` and puts `Bind{name, vv}` in front of the bindings `bs` it -# made, and the parse succeeds, `get_all` of the name in that command's Matched -# reads the values bound to it before `tok`, then `vv`, then any bound after, -# whatever follows (a lemma: every binding row is its step fact through this, -# as every refusal row is through `refused`) -law kept: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +st: S.St - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +name: String - for +vv: String - for +bs: List<&2, S.Bind> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} - for h_step: {Grow.view(S.parse.step(tok, st)) == Grow.View{args, up, path, S.Bind{name, vv} <> bs} : Grow.View} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), vv <> after)} - : Maybe<&2, List<&2, String>>} - -# LAW: wherever the words before it leave an option waiting for its value, a -# word that is not flag-shaped and that the option's choices allow is its -# value: when the parse succeeds, `get_all` of the option reads the values -# given it before, then the word, whatever follows -# SHAKE-TOK-6 -# SHAKE-PARSE-5 -# SHAKE-TOK-3 -law value_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +name: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} - for h_flag: {S.looks_flag(tok) == False{} : Bool} - for h_ok: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), tok <> after)} - : Maybe<&2, List<&2, String>>} - -# LAW: wherever the words before it leave the walker free with a positional -# next, a plain word that names no subcommand and that the positional's -# choices allow binds to it, and the walker goes on free with the positionals -# after it pending (a rest positional stays pending) -# SHAKE-PARSE-2 -# SHAKE-PARSE-5 -law pos_next: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - False{}, seen} : S.St} - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for h_help: {String.eq(tok, "help") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} - for h_ok: {S.allowed(cs, tok) == True{} : Bool} - {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St} - -# LAW: wherever the words before it leave the walker free with a positional -# next, a plain word that names no subcommand and that the positional's -# choices allow is its value: when the parse succeeds, `get_all` of the -# positional reads the values bound to it before, then the word, whatever -# follows -# SHAKE-PARSE-2 -# SHAKE-PARSE-5 -law pos_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - False{}, seen} : S.St} - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for h_help: {String.eq(tok, "help") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} - for h_ok: {S.allowed(cs, tok) == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), tok <> after)} - : Maybe<&2, List<&2, String>>} - -# LAW: wherever the words before it leave the walker free and before `--`, a -# word `--` binds nothing and leaves it free after `--` -# SHAKE-TOK-4 -law dd_step: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - {S.parse.walk(List.append(&2, String, pre, ["--"]), S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, True{}, seen} : S.St} - -# LAW: wherever the words before it leave the walker free after `--`, any -# word, whatever its shape, is read as a plain word bound to the next -# positional: one its choices allow binds, and the walker goes on free after -# `--` with the positionals after it pending -# SHAKE-TOK-4 -# SHAKE-TOK-3 -law raw_next: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - True{}, seen} : S.St} - for h_ok: {S.allowed(cs, tok) == True{} : Bool} - {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{}} : S.St} - -# LAW: wherever the words before it leave the walker free after `--` with a -# positional next, any word its choices allow, whatever its shape, is its -# value: when the parse succeeds, `get_all` of the positional reads the values -# bound to it before, then the word, whatever follows -# SHAKE-TOK-4 -# SHAKE-PARSE-5 -law raw_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - True{}, seen} : S.St} - for h_ok: {S.allowed(cs, tok) == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), tok <> after)} - : Maybe<&2, List<&2, String>>} - -# LAW: wherever the words before it leave the walker free after `--` with no -# positional left, any word fails the parse with Unexpected of the word, -# whatever follows -# SHAKE-TOK-4 -# SHAKE-ERR-2 -law raw_no_pos: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, [], True{}, seen} : S.St} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.Unexpected{path, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free after `--` with a -# positional next whose choices do not allow it, any word fails the parse -# with BadValue, whatever follows -# SHAKE-TOK-4 -# SHAKE-PARSE-5 -# SHAKE-ERR-2 -law raw_refused: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - True{}, seen} : S.St} - for h_bad: {S.allowed(cs, tok) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.BadValue{path, name, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# whether an option of this kind binds a value given it now: one built with -# `opt` when it was not given before, one built with `many` always -def takes(kk: S.ArgKind, given: Bool) -> Bool: - match kk: - case S.Opt{}: - Bool.not(given) - case S.Many{}: - True{} - case _other: - False{} - -# LAW: wherever the words before it leave the walker free, a long option -# `--name=vv` that spells an option built with `opt` not yet given, or one -# built with `many`, and whose choices allow `vv`, binds `vv`: when the parse -# succeeds, `get_all` of the option reads the values given it before, then -# `vv`, whatever follows -# SHAKE-TOK-1 -# SHAKE-PARSE-5 -# SHAKE-PARSE-10 -law long_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for +vv: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>} - for h_kind: {takes(kind, S.bound(bs, nn)) == True{} : Bool} - for h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, nn) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nn), vv <> after)} - : Maybe<&2, List<&2, String>>} - -# LAW: wherever the words before it leave an option waiting for its value, a -# word that is not flag-shaped and that the option's choices do not allow -# fails the parse with BadValue of the option and the word, whatever follows -# SHAKE-PARSE-5 -# SHAKE-TOK-3 -# SHAKE-ERR-2 -law value_refused: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +name: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen} : S.St} - for h_flag: {S.looks_flag(tok) == False{} : Bool} - for h_bad: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.BadValue{path, name, tok}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free, a long option -# `--name=vv` that spells an option built with `opt` not yet given, or one -# built with `many`, and whose choices do not allow `vv`, fails the parse with -# BadValue of the option and `vv`, whatever follows -# SHAKE-PARSE-5 -# SHAKE-ERR-2 -law long_refused: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +body: String - for +name: String - for +vv: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq("--" ++ body, "--") == False{} : Bool} - for h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>} - for h_name: {String.is_empty(name) == False{} : Bool} - for h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>} - for h_kind: {takes(kind, S.bound(bs, nn)) == True{} : Bool} - for h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == False{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)) - == Fail{S.BadValue{path, nn, vv}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# whether a string has no `=` -def no_eq(ss: String) -> Bool: - match ss: - case SNil{}: - True{} - case SCon{+cc, tt}: - Bool.and(Bool.not(Char.is_eq(cc, '=')), no_eq(tt)) - -# LAW: the `=` cut of a long option's body splits it at its first `=`: the -# chars before it are the spelling, and everything after it, verbatim and -# possibly empty, is the value -# SHAKE-TOK-1 -law cut_first_eq: - for +name: String - for +vv: String - for h_name: {no_eq(name) == True{} : Bool} - {S.cut_eq(name ++ ("=" ++ vv)) == (name, Some{vv}) : String & Maybe<&2, String>} - -# LAW: a long option's body with no `=` is all spelling, with no value -# SHAKE-TOK-1 -law cut_no_eq: - for +name: String - for h_name: {no_eq(name) == True{} : Bool} - {S.cut_eq(name) == (name, None{}) : String & Maybe<&2, String>} - -# whether a choices list allows every word -def all_allowed(+cs: List<&2, String>, ws: List<&2, String>) -> Bool: - match ws: - case Nil{}: - True{} - case Con{+ww, tt}: - Bool.and(S.allowed(cs, ww), all_allowed(cs, tt)) - -# LAW: wherever the words before them leave the walker free before `--`, with -# only a rest positional left, plain words that name no subcommand and that its -# choices allow each bind it, in order, and the walker stays free with the -# rest still pending -# SHAKE-PARSE-3 -law rest_next: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +fs: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +nm: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], False{}, seen} - : S.St} - for h_plain: {Walk.all_plain(fs) == True{} : Bool} - for h_sub: {Walk.wk.unfound(subs, fs) == True{} : Bool} - for h_ok: {all_allowed(cs, fs) == True{} : Bool} - {S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), - [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], False{}, Walk.wk.seen(fs, seen)} : S.St} - -# LAW: wherever the words before them leave the walker free before `--`, with -# only a rest positional left, plain words that name no subcommand and that its -# choices allow are its values: when the parse succeeds, `get_all` of the rest -# in its command's Matched reads the values bound to it before, then the words, -# in order, whatever follows -# SHAKE-PARSE-3 -law rest_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +fs: List<&2, String> - for +more: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +nm: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], False{}, seen} - : S.St} - for h_plain: {Walk.all_plain(fs) == True{} : Bool} - for h_sub: {Walk.wk.unfound(subs, fs) == True{} : Bool} - for h_ok: {all_allowed(cs, fs) == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, fs), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, nm) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nm), List.append(&2, String, - fs, after))} - : Maybe<&2, List<&2, String>>} - -# LAW: wherever the words before them leave the walker free after `--`, with -# only a rest positional left, words its choices allow, whatever their shape, -# each bind it, in order, and the walker stays free after `--` with the rest -# still pending -# SHAKE-PARSE-3 -law rest_raw_next: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +fs: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +nm: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], True{}, seen} - : S.St} - for h_ok: {all_allowed(cs, fs) == True{} : Bool} - {S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), - [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], True{}, Walk.wk.seen(fs, seen)} : S.St} - -# LAW: wherever the words before them leave the walker free after `--`, with -# only a rest positional left, words its choices allow, whatever their shape, -# are its values: when the parse succeeds, `get_all` of the rest in its -# command's Matched reads the values bound to it before, then the words, in -# order, whatever follows -# SHAKE-PARSE-3 -law rest_raw_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +fs: List<&2, String> - for +more: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +seen: Bool - for +nm: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, [S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs}], True{}, seen} - : S.St} - for h_ok: {all_allowed(cs, fs) == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, fs), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, nm) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nm), List.append(&2, String, - fs, after))} - : Maybe<&2, List<&2, String>>} - -# whether no argument of a list is named `name` -def none_named(args: List<&2, S.Arg>, +name: String) -> Bool: - match args: - case Nil{}: - True{} - case Con{hh, tt}: - S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c} = hh - Bool.and(Bool.not(String.eq(nn, name)), none_named(tt, name)) - -# whether a name has values in the command at `path` in a Matched, when the -# path reaches one -def present_at.go(mm: Maybe<&2, Shake.Matched>, +name: String) -> Maybe<&2, Bool>: - match mm: - case None{}: - None{} - case Some{found}: - Some{Bool.not(List.is_empty(&2, String, Shake.get_all(found, name)))} - -# whether a name has values in the command at `path` in a Matched -def present_at(found: Shake.Matched, path: List<&2, String>, +name: String) -> Maybe<&2, Bool>: - present_at.go(Shake.at(found, path), name) - -# whether a mode is free -def free.mode(mode: S.Mode) -> Bool: - match mode: - case S.Free{}: - True{} - case _other: - False{} - -# whether a walker is free -def free(st: S.St) -> Bool: - S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st - free.mode(mode) - -# LAW: when the parse succeeds, an argument with a default that its command -# left unbound reads its default, once, in that command's Matched -# SHAKE-PARSE-6 -law default_filled: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +pa: List<&2, S.Arg> - for +qa: List<&2, S.Arg> - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +ev: Maybe<&2, String> - for +dd: String - for +cs: List<&2, String> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, - S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, cs} <> qa), - bs} <> up), - List.append(&2, String, path, ms)} : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - for h_pa: {none_named(pa, nn) == True{} : Bool} - for h_qa: {none_named(qa, nn) == True{} : Bool} - for h_free: {S.bound(bs, nn) == False{} : Bool} - {all_at(found, path, nn) == Some{[dd]} : Maybe<&2, List<&2, String>>} - -# LAW: when the parse succeeds, an argument its command bound reads exactly -# the values it was given, in order, in that command's Matched: a default -# never replaces them nor adds to them -# SHAKE-PARSE-6 -law bound_kept: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +pa: List<&2, S.Arg> - for +qa: List<&2, S.Arg> - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, - S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} <> qa), bs} <> up), - List.append(&2, String, path, ms)} : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - for h_pa: {none_named(pa, nn) == True{} : Bool} - for h_qa: {none_named(qa, nn) == True{} : Bool} - for h_bound: {S.bound(bs, nn) == True{} : Bool} - {all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} : Maybe<&2, List<&2, String>>} - -# LAW: when the parse succeeds, an argument with no default reads only the -# values the words gave it in its command's Matched, none when they gave none -# SHAKE-PARSE-6 -law no_default: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +pa: List<&2, S.Arg> - for +qa: List<&2, S.Arg> - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +ev: Maybe<&2, String> - for +cs: List<&2, String> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, - S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs} <> qa), - bs} <> up), - List.append(&2, String, path, ms)} : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - for h_pa: {none_named(pa, nn) == True{} : Bool} - for h_qa: {none_named(qa, nn) == True{} : Bool} - {all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} : Maybe<&2, List<&2, String>>} - -# LAW: when the parse succeeds, a name no argument of a command has reads only -# what the words bound to it in that command's Matched -# SHAKE-PARSE-6 -law no_argument: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +args: List<&2, S.Arg> - for +nn: String - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} - : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - for h_none: {none_named(args, nn) == True{} : Bool} - {all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} : Maybe<&2, List<&2, String>>} - -# LAW: when the parse succeeds, every required argument of every command on -# the selected path has a value in that command's Matched -# SHAKE-PARSE-7 -law required_present: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +pa: List<&2, S.Arg> - for +qa: List<&2, S.Arg> - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +dflt: S.Fallback - for +cs: List<&2, String> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, - S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), bs} <> up), - List.append(&2, String, path, ms)} : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - {present_at(found, path, nn) == Some{True{}} : Maybe<&2, Bool>} - -# LAW: when the words end with the walker free and a required argument of a -# command on the selected path unbound and without a default, the parse fails -# with Missing at the selected path -# SHAKE-PARSE-7 -# SHAKE-ERR-2 -law required_missing: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +qs: List<&2, S.Level> - for +pa: List<&2, S.Arg> - for +qa: List<&2, S.Arg> - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +dflt: S.Fallback - for +cs: List<&2, String> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for h_free: {free(S.parse.walk(ws, S.parse.start(spec))) == True{} : Bool} - for h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, - S.Level{List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), bs} <> up), path} - : Grow.Frame} - for h_unbound: {S.bound(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), - bs), nn) == False{} : Bool} - exs name: String - {Shake.parse(spec, ws) == Fail{S.Missing{path, name}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# whether a pending positional blocks a subcommand: required, with no -# default, and not a rest -def blocks(aa: S.Arg) -> Bool: - S.Arg{_n, _s, _l, kind, _h, req, S.Fallback{_e, dflt}, _c} = aa - Bool.and(Bool.not(S.rest.is.kind(kind)), Bool.and(req, Maybe.is_none(&2, String, dflt))) - -# whether no pending positional blocks a subcommand -def none_block(pos: List<&2, S.Arg>) -> Bool: - match pos: - case Nil{}: - True{} - case Con{hh, tt}: - Bool.and(Bool.not(blocks(hh)), none_block(tt)) - -# LAW: with no pending positional that is required, has no default and is not -# a rest, nothing blocks a subcommand -# SHAKE-PARSE-4 -law none_blocking: - for pos: List<&2, S.Arg> - for +h_none: {none_block(pos) == True{} : Bool} - {S.parse.first_req(pos) == None{} : Maybe<&2, String>} - -# LAW: the positional that blocks a subcommand is the first pending one that is -# required, has no default and is not a rest -# SHAKE-PARSE-4 -law first_blocking: - for pa: List<&2, S.Arg> - for +nm: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +ev: Maybe<&2, String> - for +cs: List<&2, String> - for +qa: List<&2, S.Arg> - for +h_pa: {none_block(pa) == True{} : Bool} - for h_kind: {S.rest.is.kind(kind) == False{} : Bool} - {S.parse.first_req(List.append(&2, S.Arg, pa, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, None{}}, - cs} <> qa)) == Some{nm} - : Maybe<&2, String>} - -# LAW: wherever the words before it leave the walker free before `--`, a plain -# word that names a subcommand of the current command, with nothing blocking -# it, selects it: the path gains the subcommand's name, its arguments become -# current with its positionals pending and no bindings of its own, and the -# command left keeps its arguments and bindings above it -# SHAKE-PARSE-4 -# SHAKE-PARSE-2 -# SHAKE-TOK-7 -law enter_step: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +name: String - for +about: String - for +next: List<&2, S.Arg> - for +kids: List<&2, S.Sub> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for h_help: {String.eq(tok, "help") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_sub: {S.find_sub(subs, tok) == Some{S.Sub{name, about, next, kids}} : Maybe<&2, S.Sub>} - for h_req: {S.parse.first_req(pos) == None{} : Maybe<&2, String>} - {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) - == S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), - False{}, False{}} : S.St} - -# LAW: wherever the words before it leave the walker free before `--`, a plain -# word that names a subcommand of the current command while a positional -# blocks it fails the parse with Missing of that positional, whatever follows -# SHAKE-PARSE-4 -# SHAKE-ERR-2 -law enter_missing: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for +sub: S.Sub - for +nm: String - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for h_help: {String.eq(tok, "help") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_sub: {S.find_sub(subs, tok) == Some{sub} : Maybe<&2, S.Sub>} - for h_req: {S.parse.first_req(pos) == Some{nm} : Maybe<&2, String>} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, [tok]), more)) - == Fail{S.Missing{path, nm}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# the subcommands under a found one, or none -def kids_of(mm: Maybe<&2, S.Sub>) -> List<&2, S.Sub>: - match mm: - case None{}: - [] - case Some{ss}: - S.Sub{_n, _a, _g, kids} = ss - kids - -# whether each word names a subcommand under the one before, starting from -# `subs` -def help_names(ns: List<&2, String>, +subs: List<&2, S.Sub>) -> Bool: - match ns: - case Nil{}: - True{} - case Con{+nn, tt}: - Bool.and(Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), help_names(tt, kids_of(S.find_sub(subs, nn)))) - -# LAW: wherever the words before it leave the walker free, before `--` and -# before any positional of the current command is bound, a word `help` starts -# a help path at the selected path -# SHAKE-PARSE-8 -law help_step: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - {S.parse.walk(List.append(&2, String, pre, ["help"]), S.parse.start(spec)) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - -# LAW: wherever the words before them leave the walker reading a help path, -# words that each name a subcommand under the one before extend the path, in -# order -# SHAKE-PARSE-8 -# SHAKE-TOK-3 -law help_walk: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +ns: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen} : S.St} - for h_ns: {help_names(ns, subs) == True{} : Bool} - exs kids: List<&2, S.Sub> - {S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)) - == S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen} : S.St} - -# LAW: wherever the words before it leave the walker free, before `--` and -# before any positional of the current command is bound, a word `help` -# followed by words that each name a subcommand under the one before, until -# the words end, fails the parse with NeedHelp of the selected path followed -# by those words -# SHAKE-PARSE-8 -law help_path: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +ns: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - for h_ns: {help_names(ns, subs) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["help"]), ns)) - == Fail{S.NeedHelp{List.append(&2, String, path, ns)}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free, before `--` and -# before any positional of the current command is bound, a word `--help` -# starts a help path at the selected path, as `help` does, when no argument of -# the current command spells long `help` -# SHAKE-PARSE-8 -law dash_help_step: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - for h_none: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} - {S.parse.walk(List.append(&2, String, pre, ["--help"]), S.parse.start(spec)) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - -# LAW: wherever the words before it leave the walker free, before `--` and -# before any positional of the current command is bound, a word `--help`, -# when no argument of the current command spells long `help`, followed by -# words that each name a subcommand under the one before, until the words -# end, fails the parse with NeedHelp of the selected path followed by those -# words, as `help` does -# SHAKE-PARSE-8 -law dash_help_path: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +ns: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - for h_none: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} - for h_ns: {help_names(ns, subs) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--help"]), ns)) - == Fail{S.NeedHelp{List.append(&2, String, path, ns)}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: wherever the words before it leave the walker free, before `--` and -# before any positional of the current command is bound, a word `-h` starts a -# help path at the selected path, as `help` does, when no argument of the -# current command spells short `h` or long `help` -# SHAKE-PARSE-8 -law short_help_step: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - for h_short: {S.by_short(args, "h") == None{} : Maybe<&2, S.Arg>} - for h_long: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} - {S.parse.walk(List.append(&2, String, pre, ["-h"]), S.parse.start(spec)) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - -# LAW: wherever the words before it leave the walker free, before `--` and -# before any positional of the current command is bound, a word `-h`, when -# no argument of the current command spells short `h` or long `help`, -# followed by words that each name a subcommand under the one before, until -# the words end, fails the parse with NeedHelp of the selected path followed -# by those words, as `help` does -# SHAKE-PARSE-8 -law short_help_path: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +ns: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - for h_short: {S.by_short(args, "h") == None{} : Maybe<&2, S.Arg>} - for h_long: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} - for h_ns: {help_names(ns, subs) == True{} : Bool} - {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["-h"]), ns)) - == Fail{S.NeedHelp{List.append(&2, String, path, ns)}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: the walk starts free at the root, with no path, no bindings, the root's -# arguments current and its positionals pending -# SHAKE-PARSE-2 -law start_state: - for +name: String - for +about: String - for +version: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - {S.parse.walk([], S.parse.start(Shake.app(name, about, version, args, subs))) - == S.St{S.Free{}, args, [], subs, [], [], S.pos_of(args), False{}, False{}} : S.St} - -# LAW: the positionals pending for a list of arguments are those of its first -# part, then those of the rest, in spec order -# SHAKE-PARSE-2 -law pos_of_app: - for xs: List<&2, S.Arg> - for +ys: List<&2, S.Arg> - {S.pos_of(List.append(&2, S.Arg, xs, ys)) == List.append(&2, S.Arg, S.pos_of(xs), S.pos_of(ys)) : List<&2, S.Arg>} - -# LAW: a positional or rest positional is pending for itself -# SHAKE-PARSE-2 -law pos_of_pos: - for +aa: S.Arg - for h_pos: {S.pos.is(aa) == True{} : Bool} - {S.pos_of([aa]) == [aa] : List<&2, S.Arg>} - -# LAW: a flag or an option is not pending as a positional -# SHAKE-PARSE-2 -law pos_of_opt: - for +aa: S.Arg - for h_opt: {S.pos.is(aa) == False{} : Bool} - {S.pos_of([aa]) == [] : List<&2, S.Arg>} - -# LAW: once a positional of the current command is bound, a word `help` -# before `--` is a plain word like any other: with a positional pending whose -# choices allow it, and no subcommand named `help`, it binds that positional -# SHAKE-PARSE-2 -law help_word_next: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - False{}, True{}} : S.St} - for h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>} - for h_ok: {S.allowed(cs, "help") == True{} : Bool} - {S.parse.walk(List.append(&2, String, pre, ["help"]), S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St} - -# LAW: ... and when the parse succeeds, `get_all` of that positional in its -# command's Matched reads the values bound to it before, then `help` -# SHAKE-PARSE-2 -law help_word_binds: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +more: List<&2, String> - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +name: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for +rest: List<&2, S.Arg> - for +found: Shake.Matched - for h_at: {S.parse.walk(pre, S.parse.start(spec)) - == S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - False{}, True{}} : S.St} - for h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>} - for h_ok: {S.allowed(cs, "help") == True{} : Bool} - for h_done: {Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["help"]), more)) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - exs after: List<&2, String> - {all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), "help" <> after)} - : Maybe<&2, List<&2, String>>} - -# LAW: once a letter of a short cluster fails the walker, the letters after it -# change nothing: the word fails with the first letter's error -law cluster_dead: - for body: String - for +orig: String - for +err: S.ParseErr - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +done: Bool - {S.cluster(body, orig, S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen}, done) - == S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# LAW: in a free walker before `--`, a word that starts with `-`, is not `-` -# and does not start with `--` is read as a cluster of short options, letter -# by letter, from the walker as it stood -# SHAKE-TOK-2 -law short_step: - for +cc: Char - for +rest: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool} - for h_dash: {Char.is_eq(cc, '-') == False{} : Bool} - for h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} - {S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.cluster(SCon{cc, rest}, "-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - False{}) : S.St} - -# LAW: a letter that spells a flag not yet given, with no `=` after it, binds -# it, and the next letter goes on -# SHAKE-TOK-2 -law letter_flag: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_once: {S.bound(bs, nn) == False{} : Bool} - for h_noeq: {String.starts_with(rest, "=") == False{} : Bool} - {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.cluster(rest, orig, S.St{S.Free{}, args, up, subs, path, S.Bind{nn, "true"} <> bs, pos, raw, seen}, False{}) - : S.St} - -# LAW: a letter that spells a flag not yet given, with `=` after it, refuses -# the whole word as Unexpected -# SHAKE-TOK-5 -# SHAKE-ERR-2 -law letter_flag_eq: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_once: {S.bound(bs, nn) == False{} : Bool} - for h_eq: {String.starts_with(rest, "=") == True{} : Bool} - {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.St{S.Dead{S.Unexpected{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# LAW: a letter no argument of the current command spells refuses the whole -# word as UnknownFlag, whatever letters came before it -# SHAKE-TOK-2 -# SHAKE-TOK-7 -# SHAKE-ERR-2 -law letter_unknown: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>} - {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.St{S.Dead{S.UnknownFlag{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# LAW: a letter that spells a flag already given refuses the whole word as -# Repeated -# SHAKE-PARSE-10 -# SHAKE-ERR-2 -law letter_flag_again: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_again: {S.bound(bs, nn) == True{} : Bool} - {dead_of(S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{})) - == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - -# LAW: a letter that spells an option built with `opt` already given refuses -# the whole word as Repeated -# SHAKE-PARSE-10 -# SHAKE-ERR-2 -law letter_opt_again: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_again: {S.bound(bs, nn) == True{} : Bool} - {dead_of(S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{})) - == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - -# LAW: a letter that spells an option taking a value now, with more chars -# after it and no `=`, takes those chars as its value, verbatim, when its -# choices allow them, and the cluster ends -# SHAKE-TOK-2 -# SHAKE-PARSE-5 -# SHAKE-PARSE-10 -law letter_value: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_kind: {takes(kind, S.bound(bs, nn)) == True{} : Bool} - for h_noeq: {String.starts_with(rest, "=") == False{} : Bool} - for h_some: {String.is_empty(rest) == False{} : Bool} - for h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == True{} : Bool} - {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} - -# LAW: a letter that spells an option taking a value now, followed by `=`, -# takes everything after the `=` as its value, verbatim and possibly empty, -# when its choices allow it -# SHAKE-TOK-2 -# SHAKE-PARSE-5 -# SHAKE-PARSE-10 -law letter_value_eq: - for +cc: Char - for +vv: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_kind: {takes(kind, S.bound(bs, nn)) == True{} : Bool} - for h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} - {S.cluster(SCon{cc, "=" ++ vv}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} - -# LAW: a letter that spells an option taking a value now, last in its word, -# leaves the option waiting for the next word -# SHAKE-TOK-2 -law letter_value_next: - for +cc: Char - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_kind: {takes(kind, S.bound(bs, nn)) == True{} : Bool} - {S.cluster(SCon{cc, SNil{}}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# LAW: a letter that spells an option taking a value now, with a glued value -# its choices do not allow, refuses the whole word as BadValue -# SHAKE-PARSE-5 -# SHAKE-ERR-2 -law letter_value_bad: - for +cc: Char - for +rest: String - for +orig: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +raw: Bool - for +seen: Bool - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +dflt: S.Fallback - for +cs: List<&2, String> - for h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} - : Maybe<&2, S.Arg>} - for h_kind: {takes(kind, S.bound(bs, nn)) == True{} : Bool} - for h_noeq: {String.starts_with(rest, "=") == False{} : Bool} - for h_some: {String.is_empty(rest) == False{} : Bool} - for h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == False{} : Bool} - {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# LAW: in a free walker before `--`, a word that does not start with `-`, other -# than a `help` that asks for help, is read as a subcommand or a positional, -# never as an option -# SHAKE-TOK-3 -law plain_word_step: - for +tok: String - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - for h_dd: {String.eq(tok, "--") == False{} : Bool} - for +h_dash: {String.starts_with(tok, "-") == False{} : Bool} - for h_help: {Bool.and(String.eq(tok, "help"), Bool.not(seen)) == False{} : Bool} - {S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) : S.St} - -# LAW: in a free walker before `--`, the word `-` is read as a subcommand or a -# positional, never as an option -# SHAKE-TOK-3 -law dash_word_step: - for +args: List<&2, S.Arg> - for +up: List<&2, S.Level> - for +subs: List<&2, S.Sub> - for +path: List<&2, String> - for +bs: List<&2, S.Bind> - for +pos: List<&2, S.Arg> - for +seen: Bool - {S.parse.step("-", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.parse.word.go(S.find_sub(subs, "-"), "-", args, up, subs, path, bs, pos, False{}, seen) : S.St} - -# the page a help walk reaches from `how`: each name that is a subcommand -# where it stands moves to it, and any other name is skipped -def reach(path: List<&2, String>, how: S.How) -> S.How: - match path: - case Nil{}: - how - case Con{+hh, tt}: - reach(tt, S.help.step(how, hh)) - -# the root's page -def root(app: Shake.Cli) -> S.How: - S.Cli{+name, about, version, args, subs} = app - S.How{name, about, version, args, subs, name} - -# the usage line of a page -def usage_line(how: S.How) -> String: - S.How{_b, _a, version, +args, +subs, +usage} = how - S.help.usage(usage, S.help.with(args, version), subs) - -# a page as `help` renders it: its title, what it is about, its usage line -# (`usage_line`), its subcommands, its positionals and its options, the -# built-in flags after the command's own -def page(how: S.How) -> String: - S.How{+bin, +about, +version, +args, +subs, +usage} = how - S.help.page(S.title.go(bin, version), about, S.help.usage(usage, S.help.with(args, version), subs), - S.help.with(args, version), subs) - -# LAW: `help` renders the page of the command a path reaches from the root -# SHAKE-HELP-1 -# SHAKE-ERR-2 -law help_page: - for spec: Shake.Cli - for +path: List<&2, String> - {Shake.help(spec, path) == page(reach(path, root(spec))) : String} - -# LAW: a name that is a subcommand where the walk stands moves the page to -# that subcommand, its usage line gaining the name -# SHAKE-HELP-1 -law reach_child: - for +bin: String - for +about: String - for +version: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - for +usage: String - for +name: String - for +sn: String - for +sa: String - for +sargs: List<&2, S.Arg> - for +ssubs: List<&2, S.Sub> - for h_sub: {S.find_sub(subs, name) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} - {S.help.step(S.How{bin, about, version, args, subs, usage}, name) - == S.How{sn, sa, None{}, sargs, ssubs, usage ++ " " ++ name} : S.How} - -# LAW: a name that is no subcommand where the walk stands is skipped -# SHAKE-HELP-1 -law reach_skip: - for +bin: String - for +about: String - for +version: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - for +usage: String - for +name: String - for h_sub: {S.find_sub(subs, name) == None{} : Maybe<&2, S.Sub>} - {S.help.step(S.How{bin, about, version, args, subs, usage}, name) - == S.How{bin, about, version, args, subs, usage} : S.How} - -# LAW: every error but a request for help names the command path where the -# parse failed, and `err_path` reads it; a request for the version names the -# root -# SHAKE-ERR-2 -law err_path_at: - for +at: List<&2, String> - for +word: String - for +name: String - for +value: String - {[Shake.err_path(S.UnknownFlag{at, word}), Shake.err_path(S.Missing{at, name}), Shake.err_path(S.NoValue{at, name}), - Shake.err_path(S.BadValue{at, name, value}), Shake.err_path(S.Unexpected{at, word}), - Shake.err_path(S.Repeated{at, name}), Shake.err_path(S.NeedVersion{})] - == [at, at, at, at, at, at, []] : List<&2, List<&2, String>>} - -# LAW: the text of every error but a request for help or for the version is a -# message, then the usage line of the command at its path, as that command's -# help page shows it, then where to look for more -# SHAKE-ERR-2 -law err_text_usage: - for +spec: Shake.Cli - for +err: Shake.ParseErr - for h_err: {Shake.help_path(err) == None{} : Maybe<&2, List<&2, String>>} - for h_ver: {version.is(err) == False{} : Bool} - exs msg: String - {Shake.err_text(spec, err) - == msg ++ "\n\n" ++ usage_line(reach(Shake.err_path(err), root(spec))) ++ "\n\n" ++ S.err.hint(Shake.err_path(err)) - : String} - -# the pages a path walks through from `how`, `how` first, each name moving as -# `help` moves -def pages(path: List<&2, String>, +how: S.How) -> List<&2, S.How>: - match path: - case Nil{}: - [how] - case Con{+hh, tt}: - how <> pages(tt, S.help.step(how, hh)) - -# a later find when there is one, else an earlier -def keep_later(here: Maybe<&2, S.Arg>, later: Maybe<&2, S.Arg>) -> Maybe<&2, S.Arg>: - match later: - case None{}: - here - case Some{aa}: - Some{aa} - -# the argument named `name` of the last page that has one -def deepest(hs: List<&2, S.How>, +name: String) -> Maybe<&2, S.Arg>: - match hs: - case Nil{}: - None{} - case Con{hh, tt}: - keep_later(S.by_name(S.help.how_args(hh), name), deepest(tt, name)) - -# a flag's or an option's spelling: `--long`, else `-s`, then its value -# mark; its name when it has neither -def spelled(+short: String, +long: String, +name: String, +mark: String) -> String: - Bool.pick(String, String.is_empty(long), - Bool.pick(String, String.is_empty(short), name, "-" ++ short ++ mark), - "--" ++ long ++ mark) - -# a positional's form: `` when required, `[NAME]` otherwise -def pos_form(req: Bool, +nn: String) -> String: - match req: - case True{}: - "<" ++ nn ++ ">" - case False{}: - "[" ++ nn ++ "]" - -# an argument of this kind as an error names it -def form_of.kind(kk: S.ArgKind, +name: String, +sh: String, +lo: String, req: Bool) -> String: - match kk: - case S.Flag{}: - spelled(sh, lo, name, "") - case S.Opt{}: - spelled(sh, lo, name, " <" ++ String.to_upper(name) ++ ">") - case S.Many{}: - spelled(sh, lo, name, " <" ++ String.to_upper(name) ++ ">...") - case S.Pos{}: - pos_form(req, String.to_upper(name)) - case S.Rest{}: - pos_form(req, String.to_upper(name)) ++ "..." - -# an argument's display form -def form_of(aa: S.Arg) -> String: - S.Arg{+name, short, long, kind, _h, req, _d, _c} = aa - form_of.kind(kind, name, S.text.of(short, ""), S.text.of(long, ""), req) - -# a found argument's display form, else the name the error carries -def named(found: Maybe<&2, S.Arg>, +name: String) -> String: - match found: - case None{}: - name - case Some{aa}: - form_of(aa) - -# clap's line of the values a found argument allows, when it has choices -def values_line(found: Maybe<&2, S.Arg>) -> String: - match found: - case None{}: - "" - case Some{aa}: - S.Arg{_n, _s, _l, _k, _h, _r, _d, cs} = aa - match cs: - case Nil{}: - "" - case Con{cc, rest}: - "\n [possible values: " ++ String.join(cc <> rest, ", ") ++ "]" - -# LAW: the argument an error is about is the one named so on the deepest -# command of its path that has one, the root first -# SHAKE-ERR-5 -law err_find: - for +spec: Shake.Cli - for +at: List<&2, String> - for +name: String - {S.err.find(spec, at, name) == deepest(pages(at, root(spec)), name) : Maybe<&2, S.Arg>} - -# LAW: an argument's display form: a flag by `--long`, else `-s`, an option -# with its value mark after, a positional by `` or `[NAME]`, `...` -# after a rest -# SHAKE-ERR-5 -law err_form: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +fb: S.Fallback - for +cs: List<&2, String> - {S.err.form(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) == form_of(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) : String} - -# LAW: a missing argument is named by its display form -# SHAKE-ERR-5 -law err_missing_named: - for +spec: Shake.Cli - for +at: List<&2, String> - for +name: String - {Shake.err_text(spec, S.Missing{at, name}) - == S.err.wrap(spec, at, "error: the following required argument was not provided:\n " ++ - named(deepest(pages(at, root(spec)), name), name)) : String} - -# LAW: an option missing its value is named by its display form -# SHAKE-ERR-5 -law err_novalue_named: - for +spec: Shake.Cli - for +at: List<&2, String> - for +name: String - {Shake.err_text(spec, S.NoValue{at, name}) - == S.err.wrap(spec, at, "error: a value is required for '" ++ named(deepest(pages(at, root(spec)), name), name) ++ - "' but none was supplied") : String} - -# LAW: a value outside the choices names its argument by its display form, -# then lists the choices -# SHAKE-ERR-5 -law err_bad_named: - for +spec: Shake.Cli - for +at: List<&2, String> - for +name: String - for +value: String - {Shake.err_text(spec, S.BadValue{at, name, value}) - == S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ - named(deepest(pages(at, root(spec)), name), name) ++ "'" ++ values_line(deepest(pages(at, root(spec)), name))) - : String} - -# LAW: an argument given twice is named by its display form -# SHAKE-ERR-5 -law err_repeated_named: - for +spec: Shake.Cli - for +at: List<&2, String> - for +name: String - {Shake.err_text(spec, S.Repeated{at, name}) - == S.err.wrap(spec, at, "error: the argument '" ++ named(deepest(pages(at, root(spec)), name), name) ++ - "' cannot be used multiple times") : String} - -# a built-in flag a page lists, spelled `--name` and maybe a short letter -def builtin(+name: String, short: Maybe<&2, String>, +help: String) -> S.Arg: - S.Arg{name, short, Some{name}, S.Flag{}, help, False{}, S.Fallback{None{}, None{}}, []} - -# the help row, given what the command spells as long `help` and short `h` -def help_rows.of(long: Maybe<&2, S.Arg>, short: Maybe<&2, S.Arg>) -> List<&2, S.Arg>: - match long: - case None{}: - match short: - case None{}: - [builtin("help", Some{"h"}, "Print help")] - case Some{_a}: - [builtin("help", None{}, "Print help")] - case Some{_a}: - [] - -# the help row of a page: `-h, --help` when its command spells neither, -# `--help` when it spells `-h`, none when it spells `--help` -def help_rows(+args: List<&2, S.Arg>) -> List<&2, S.Arg>: - help_rows.of(S.by_long(args, "help"), S.by_short(args, "h")) - -# the version row, given what the command spells as long `version` and short -# `V` -def version_rows.of(long: Maybe<&2, S.Arg>, short: Maybe<&2, S.Arg>) -> List<&2, S.Arg>: - match long: - case None{}: - match short: - case None{}: - [builtin("version", Some{"V"}, "Print version")] - case Some{_a}: - [builtin("version", None{}, "Print version")] - case Some{_a}: - [] - -# the version row of a page: none without a version; else `-V, --version` -# when its command spells neither, `--version` when it spells `-V`, none -# when it spells `--version` -def version_rows(+args: List<&2, S.Arg>, version: Maybe<&2, String>) -> List<&2, S.Arg>: - match version: - case None{}: - [] - case Some{_v}: - version_rows.of(S.by_long(args, "version"), S.by_short(args, "V")) - -# LAW: the built-in flags a page lists after its command's own are its help -# row, then its version row -# SHAKE-HELP-2 -# SHAKE-HELP-4 -law builtins_listed: - for +args: List<&2, S.Arg> - for +version: Maybe<&2, String> - {S.help.builtins(args, version) == List.append(&2, S.Arg, help_rows(args), version_rows(args, version)) - : List<&2, S.Arg>} - -# the entry every Commands block ends with -def help_sub() -> S.Sub: - S.Sub{"help", "Print this message or the help of the given subcommand(s)", [], []} - -# one Commands line per subcommand, its name and what it is about, in order -def sub_lines(subs: List<&2, S.Sub>, +ww: Nat) -> String: - match subs: - case Nil{}: - "" - case Con{+hh, tt}: - S.help.cmd_line(S.help.sub_name(hh), S.help.sub_about(hh), ww) ++ sub_lines(tt, ww) - -# LAW: a page's Commands block has one line for each subcommand of its -# command, in spec order, then one for `help` -# SHAKE-HELP-2 -law cmds_listed: - for +subs: List<&2, S.Sub> - {S.help.cmds(subs) - == "Commands:\n" ++ sub_lines(List.append(&2, S.Sub, subs, [help_sub()]), S.help.cmd_width(subs, 0n)) ++ "\n" - : String} - -# whether an argument is a flag or an option -def is_opt(aa: S.Arg) -> Bool: - Bool.not(S.pos.is(aa)) - -# one Options line per argument, in order -def opt_lines(args: List<&2, S.Arg>, +ww: Nat) -> String: - match args: - case Nil{}: - "" - case Con{+hh, tt}: - S.help.opt_line(hh, ww) ++ "\n" ++ opt_lines(tt, ww) - -# LAW: a page's Options block has one line for each flag and option of its -# command, in spec order, and none for a positional -# SHAKE-HELP-2 -law opts_listed: - for +args: List<&2, S.Arg> - {S.help.opts(args) - == S.help.opts.go(opt_lines(List.filter(~S.Arg, ~is_opt, args), S.help.opt_width(args, 0n))) : String} - -# a positional's mark: `` when required, `[NAME]` otherwise -def mark.req(req: Bool, +nn: String) -> String: - match req: - case True{}: - " <" ++ nn ++ ">" - case False{}: - " [" ++ nn ++ "]" - -# `...` after a rest positional -def mark.rest(kk: S.ArgKind) -> String: - match kk: - case S.Rest{}: - "..." - case S.Pos{}: - "" - case S.Flag{}: - "" - case S.Opt{}: - "" - case S.Many{}: - "" - -# a positional as a usage line names it -def mark(aa: S.Arg) -> String: - S.Arg{+name, _s, _l, kind, _h, required, _d, _c} = aa - mark.req(required, String.to_upper(name)) ++ mark.rest(kind) - -# the marks of a list of positionals, in order -def marks(args: List<&2, S.Arg>) -> String: - match args: - case Nil{}: - "" - case Con{+hh, tt}: - mark(hh) ++ marks(tt) - -# LAW: a usage line names each positional of its command once, in spec -# order, as `` when required and `[NAME]` otherwise, with `...` after a -# rest positional -# SHAKE-HELP-2 -law usage_listed: - for +name: String - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - {S.help.usage(name, args, subs) - == "Usage: " ++ name ++ S.help.opts_hint(args) ++ marks(List.filter(~S.Arg, ~S.pos.is, args)) ++ S.help.cmd_hint(subs) - : String} - -# one Arguments line per argument, in order -def arg_lines(args: List<&2, S.Arg>, +ww: Nat) -> String: - match args: - case Nil{}: - "" - case Con{+hh, tt}: - S.help.arg_line(hh, ww) ++ "\n" ++ arg_lines(tt, ww) - -# LAW: a page's Arguments block has one line for each positional of its -# command, in spec order, and none for a flag or an option; with no -# positional it is empty -# SHAKE-HELP-2 -law args_listed: - for +args: List<&2, S.Arg> - {S.help.args(args) - == S.help.args.go(arg_lines(List.filter(~S.Arg, ~S.pos.is, args), S.help.arg_width(S.pos_of(args), 0n))) - : String} - -# LAW: an Arguments line is the positional's usage mark without the space -# before it, padded, then its help text and the extras an Options line has -# SHAKE-HELP-2 -# SHAKE-HELP-3 -law arg_line_mark: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +fb: S.Fallback - for +cs: List<&2, String> - for +ww: Nat - {S.help.arg_line(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}, ww) - == " " ++ S.help.pad(String.drop(mark(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}), 1n), ww) ++ " " ++ hp ++ - S.help.extra(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) - : String} - -# the names of some arguments -def arg_names(args: List<&2, S.Arg>) -> List<&2, String>: - match args: - case Nil{}: - [] - case Con{hh, tt}: - S.Arg{name, _s, _l, _k, _h, _r, _d, _c} = hh - name <> arg_names(tt) - -# the short spellings of some arguments, of those that have one -def arg_shorts(args: List<&2, S.Arg>) -> List<&2, String>: - match args: - case Nil{}: - [] - case Con{hh, tt}: - S.Arg{_n, short, _l, _k, _h, _r, _d, _c} = hh - K.spelled.add(short, arg_shorts(tt)) - -# the long spellings of some arguments, of those that have one -def arg_longs(args: List<&2, S.Arg>) -> List<&2, String>: - match args: - case Nil{}: - [] - case Con{hh, tt}: - S.Arg{_n, _s, long, _k, _h, _r, _d, _c} = hh - K.spelled.add(long, arg_longs(tt)) - -# a report of a default that is outside nonempty choices -def off_choice( - +path: List<&2, String>, - +name: String, - +cs: List<&2, String>, - dflt: Maybe<&2, String> -) -> List<&2, K.SpecErr>: - match dflt: - case None{}: - [] - case Some{+vv}: - K.one(Bool.and(Bool.not(List.is_empty(&2, String, cs)), Bool.not(List.contains(~String, ~String.eq, cs, vv))), - K.DefaultNotChoice{path, name, vv}) - -# the reports about one argument of a command, against the arguments before -# it, then `after`: its name, short and long spellings where an earlier one -# has them, and its default -def arg_reports( - +path: List<&2, String>, - +before: List<&2, S.Arg>, - aa: S.Arg, - after: List<&2, K.SpecErr> -) -> List<&2, K.SpecErr>: - S.Arg{+name, +short, +long, _k, _h, _r, S.Fallback{_e, dflt}, +cs} = aa - K.both(K.one(List.contains(~String, ~String.eq, arg_names(before), name), K.SameName{path, name}), - K.both(K.one(K.spelled.seen(short, arg_shorts(before)), K.SameShort{path, S.text.of(short, "")}), - K.both(K.one(K.spelled.seen(long, arg_longs(before)), K.SameLong{path, S.text.of(long, "")}), - K.both(off_choice(path, name, cs, dflt), after)))) - -# the reports about each argument, in spec order, given those before them -def args_reports( - args: List<&2, S.Arg>, - +path: List<&2, String>, - +before: List<&2, S.Arg> -) -> List<&2, K.SpecErr>: - match args: - case Nil{}: - [] - case Con{+hh, tt}: - arg_reports(path, before, hh, args_reports(tt, path, hh <> before)) - -# whether one of some arguments is optional -def any_optional(args: List<&2, S.Arg>) -> Bool: - match args: - case Nil{}: - False{} - case Con{hh, tt}: - S.Arg{_n, _s, _l, _k, _h, required, _d, _c} = hh - Bool.or(any_optional(tt), Bool.not(required)) - -# the reports about each positional, in spec order, given those before them: a -# rest positional with one after it, and a required one after an optional one -def pos_reports( - pos: List<&2, S.Arg>, - +path: List<&2, String>, - +before: List<&2, S.Arg> -) -> List<&2, K.SpecErr>: - match pos: - case Nil{}: - [] - case Con{+hh, +tt}: - S.Arg{+name, _s, _l, kind, _h, required, _d, _c} = hh - K.both(K.one(Bool.and(S.rest.is.kind(kind), Bool.not(List.is_empty(&2, S.Arg, tt))), K.RestNotLast{path, name}), - K.both(K.one(Bool.and(required, any_optional(before)), K.RequiredAfterOptional{path, name}), - pos_reports(tt, path, hh <> before))) - -# the names of some subcommands -def sub_names(subs: List<&2, S.Sub>) -> List<&2, String>: - match subs: - case Nil{}: - [] - case Con{hh, tt}: - S.Sub{name, _a, _g, _c} = hh - name <> sub_names(tt) - -# the reports about each subcommand, in spec order, given those before them: -# a name an earlier one has, and the name `help` -def sub_reports( - subs: List<&2, S.Sub>, - +path: List<&2, String>, - +before: List<&2, S.Sub> -) -> List<&2, K.SpecErr>: - match subs: - case Nil{}: - [] - case Con{+hh, tt}: - S.Sub{+name, _a, _g, _c} = hh - K.both(K.one(List.contains(~String, ~String.eq, sub_names(before), name), K.SameSub{path, name}), - K.both(K.one(String.eq(name, "help"), K.HelpSub{path}), sub_reports(tt, path, hh <> before))) - -# one command's reports, at its path: its arguments', its positionals', its -# subcommands' -def cmd_reports( - +path: List<&2, String>, - +args: List<&2, S.Arg>, - subs: List<&2, S.Sub> -) -> List<&2, K.SpecErr>: - K.both(args_reports(args, path, []), K.both(pos_reports(S.pos_of(args), path, []), sub_reports(subs, path, []))) - -# the reports of every command under some subcommands of the command at -# `path`, depth first, each at its own path -def below(subs: List<&2, S.Sub>, +path: List<&2, String>) -> List<&2, K.SpecErr>: - match subs: - case Nil{}: - [] - case Con{hh, tt}: - S.Sub{+name, _a, +args, +kids} = hh - +here = List.append(&2, String, path, [name]) - K.both(cmd_reports(here, args, kids), K.both(below(kids, here), below(tt, path))) - -# LAW: `check` reports, for the root and then for every subcommand depth -# first, each at its own path: each argument whose name, short spelling or -# long spelling an earlier argument of its command has, each default outside -# nonempty choices, each rest positional before the last positional, each -# required positional after an optional one, each subcommand with an earlier -# sibling's name, and each subcommand named `help`; one report each and -# nothing else -# SHAKE-SPEC-1 -law check_listed: - for +name: String - for +about: String - for +version: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - {Shake.check(S.Cli{name, about, version, args, subs}) == K.both(cmd_reports([], args, subs), below(subs, [])) - : List<&2, K.SpecErr>} - -# whether `vv` is what is left of the word `wd` once some chars, maybe none, are -# dropped from its front -def ends(wd: String, +vv: String) -> Bool: - match wd: - case SNil{}: - String.eq(vv, "") - case SCon{+hh, +tt}: - Bool.or(String.eq(vv, SCon{hh, tt}), ends(tt, vv)) - -# whether `vv` is a piece of one of the words: what is left of it once some -# chars are dropped from its front -def piece(ws: List<&2, String>, +vv: String) -> Bool: - match ws: - case Nil{}: - False{} - case Con{+ww, tt}: - Bool.or(ends(ww, vv), piece(tt, vv)) - -# whether a default is `vv` -def default_is(dd: Maybe<&2, String>, +vv: String) -> Bool: - match dd: - case None{}: - False{} - case Some{dv}: - String.eq(dv, vv) - -# whether `vv` is the default of one of `args` named `name` -def is_default(args: List<&2, S.Arg>, +name: String, +vv: String) -> Bool: - match args: - case Nil{}: - False{} - case Con{hh, tt}: - S.Arg{nn, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c} = hh - Bool.or(Bool.and(String.eq(nn, name), default_is(dd, vv)), is_default(tt, name, vv)) - -# whether a binding's value is `true`, which a flag binds, a piece of one of -# the words, or the default of one of `args` of its name -def given(+ws: List<&2, String>, +args: List<&2, S.Arg>, bb: S.Bind) -> Bool: - S.Bind{+name, +vv} = bb - Bool.or(String.eq(vv, "true"), Bool.or(piece(ws, vv), is_default(args, name, vv))) - -# whether every binding is given -def all_given(bs: List<&2, S.Bind>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Bool: - match bs: - case Nil{}: - True{} - case Con{hh, tt}: - Bool.and(given(ws, args, hh), all_given(tt, ws, args)) - -# whether every binding of a command's own Matched is given, when there is one -def given_at.go(mm: Maybe<&2, Shake.Matched>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Maybe<&2, Bool>: - match mm: - case None{}: - None{} - case Some{found}: - Some{all_given(S.own(found), ws, args)} - -# whether every binding of the command at `path` in a Matched is given -def given_at( - found: Shake.Matched, - path: List<&2, String>, - +ws: List<&2, String>, - +args: List<&2, S.Arg> -) -> Maybe<&2, Bool>: - given_at.go(Shake.at(found, path), ws, args) - -# LAW: when the parse succeeds, every binding in the Matched of a command on -# the selected path is `true`, which a flag binds, a piece of one of the words, -# verbatim, or the default of one of that command's arguments: no value is -# invented, cut short or changed -# SHAKE-PARSE-1 -law values_given: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +args: List<&2, S.Arg> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for +h_fr: {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} - : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - {given_at(found, path, ws, args) == Some{True{}} : Maybe<&2, Bool>} - -# whether one of `args` is named `name` -def has_arg(args: List<&2, S.Arg>, +name: String) -> Bool: - match args: - case Nil{}: - False{} - case Con{hh, tt}: - S.Arg{nn, _s, _l, _k, _h, _r, _d, _c} = hh - Bool.or(String.eq(nn, name), has_arg(tt, name)) - -# whether a binding is named after one of `args` and is given: `true`, a -# piece of one of the words, or the default of one of `args` of its name -def named_given(+ws: List<&2, String>, +args: List<&2, S.Arg>, bb: S.Bind) -> Bool: - S.Bind{+name, +vv} = bb - Bool.and(has_arg(args, name), given(ws, args, S.Bind{name, vv})) - -# whether every binding is named and given -def all_named(bs: List<&2, S.Bind>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Bool: - match bs: - case Nil{}: - True{} - case Con{hh, tt}: - Bool.and(named_given(ws, args, hh), all_named(tt, ws, args)) - -# whether every binding of a command's own Matched is named and given, when -# there is one -def named_at.go(mm: Maybe<&2, Shake.Matched>, +ws: List<&2, String>, +args: List<&2, S.Arg>) -> Maybe<&2, Bool>: - match mm: - case None{}: - None{} - case Some{found}: - Some{all_named(S.own(found), ws, args)} - -# whether every binding of the command at `path` in a Matched is named and -# given -def named_at( - found: Shake.Matched, - path: List<&2, String>, - +ws: List<&2, String>, - +args: List<&2, S.Arg> -) -> Maybe<&2, Bool>: - named_at.go(Shake.at(found, path), ws, args) - -# LAW: when the parse succeeds, every binding in the Matched of a command on -# the selected path is named after one of that command's arguments, and is -# `true`, which a flag binds, a piece of the words read while that command -# was current (`mid`: from the word that selected it, or the start, to the -# word that selects a subcommand under it, or the end), verbatim, or the -# default of one of its arguments -# SHAKE-PARSE-1 -law values_here: - for +spec: Shake.Cli - for +pre: List<&2, String> - for +mid: List<&2, String> - for +post: List<&2, String> - for +found: Shake.Matched - for +qs: List<&2, S.Level> - for +args: List<&2, S.Arg> - for +bs: List<&2, S.Bind> - for +up: List<&2, S.Level> - for +path: List<&2, String> - for +ms: List<&2, String> - for h_done: {Shake.parse(spec, List.append(&2, String, pre, List.append(&2, String, mid, post))) == Done{found} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_pre: {Grow.view(S.parse.walk(pre, S.parse.start(spec))) == Grow.View{args, up, path, []} : Grow.View} - for h_mid: {Grow.view(S.parse.walk(List.append(&2, String, pre, mid), S.parse.start(spec))) - == Grow.View{args, up, path, bs} : Grow.View} - for +h_fr: {Grow.gw.frame(S.parse.walk(List.append(&2, String, pre, List.append(&2, String, mid, post)), - S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} - : Grow.Frame} - for h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} - {named_at(found, path, mid, args) == Some{True{}} : Maybe<&2, Bool>} - -# a count, with one more when a value is not `true` -def valued.at(flag: Bool, nn: Nat) -> Nat: - match flag: - case True{}: - nn - case False{}: - 1n+nn - -# how many bindings have a value other than `true`, the value a flag binds -def valued(bs: List<&2, S.Bind>) -> Nat: - match bs: - case Nil{}: - 0n - case Con{hh, tt}: - S.Bind{_n, vv} = hh - valued.at(String.eq(vv, "true"), valued(tt)) - -# whether `aa` is at most `bb` -def nat_le(aa: Nat, bb: Nat) -> Bool: - match aa: - case 0n: - True{} - case 1n+pp: - match bb: - case 0n: - False{} - case 1n+qq: - nat_le(pp, qq) - -# a walker's bindings for its current command -def binds_of(st: S.St) -> List<&2, S.Bind>: - S.St{_m, _a, _g, _s, _p, binds, _o, _r, _n} = st - binds - -# LAW: one word adds at most one binding to the current command other than -# `true`, which a flag binds: a word never binds two values, whatever the -# walker it is read by -# SHAKE-PARSE-1 -law one_value_per_word: - for +tok: String - for +st: S.St - {nat_le(valued(binds_of(S.parse.step(tok, st))), 1n+valued(binds_of(st))) == True{} : Bool} - -# LAW: a word read by a walker that can still finish as a success changes it: -# it binds a value, sets a flag, enters a subcommand, waits for an option's -# value, switches to positionals, starts a help path, or fails. No word is -# ignored -# SHAKE-PARSE-1 -law word_used: - for +tok: String - for +st: S.St - for h_live: {Grow.gw.live(st) == True{} : Bool} - for h_same: {S.parse.step(tok, st) == st : S.St} - Empty - -# the command path a report of `check` is at -def spec_path(err: K.SpecErr) -> List<&2, String>: - match err: - case K.RestNotLast{path, _n}: - path - case K.SameName{path, _n}: - path - case K.SameShort{path, _s}: - path - case K.SameLong{path, _l}: - path - case K.SameSub{path, _n}: - path - case K.HelpSub{path}: - path - case K.DefaultNotChoice{path, _n, _v}: - path - case K.RequiredAfterOptional{path, _n}: - path - -# LAW: the text of every report of `check` says first where it is: `spec: ` -# at the root, else the command's path (a lemma for SHAKE-SPEC-1's reports) -law spec_err_where: - for +err: K.SpecErr - exs msg: String - {Shake.spec_err_text(err) == K.at(spec_path(err)) ++ msg : String} - -# LAW: `env` gives an argument its env var and keeps the rest of it -# SHAKE-PARSE-11 -law env_sets: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for ev: Maybe<&2, String> - for +dd: Maybe<&2, String> - for +cs: List<&2, String> - for +var: String - {Shake.env(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, var) - == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{Some{var}, dd}, cs} : S.Arg} - -# LAW: an argument with no env var has no env value -# SHAKE-PARSE-11 -law env_of_none: - for vars: List<&2, Shake.Var> - {S.env.of(None{}, vars) == None{} : Maybe<&2, String>} - -# LAW: an env var no pair names has no env value -# SHAKE-PARSE-11 -law env_of_nil: - for var: String - {S.env.of(Some{var}, []) == None{} : Maybe<&2, String>} - -# LAW: the first pair an env var names gives its env value, when its value -# is not empty -# SHAKE-PARSE-11 -law env_of_hit: - for +var: String - for +nn: String - for +vv: String - for rest: List<&2, Shake.Var> - for h_eq: {String.eq(nn, var) == True{} : Bool} - for h_full: {String.is_empty(vv) == False{} : Bool} - {S.env.of(Some{var}, (nn, vv) <> rest) == Some{vv} : Maybe<&2, String>} - -# LAW: an empty value in the first pair an env var names gives no env value, -# whatever later pairs hold -# SHAKE-PARSE-11 -law env_of_empty: - for +var: String - for +nn: String - for +vv: String - for rest: List<&2, Shake.Var> - for h_eq: {String.eq(nn, var) == True{} : Bool} - for h_empty: {String.is_empty(vv) == True{} : Bool} - {S.env.of(Some{var}, (nn, vv) <> rest) == None{} : Maybe<&2, String>} - -# LAW: a pair named otherwise is passed over -# SHAKE-PARSE-11 -law env_of_skip: - for +var: String - for +nn: String - for +vv: String - for +rest: List<&2, Shake.Var> - for h_eq: {String.eq(nn, var) == False{} : Bool} - {S.env.of(Some{var}, (nn, vv) <> rest) == S.env.of(Some{var}, rest) : Maybe<&2, String>} - -# the env values that turn a flag off, lowercased -def off_words() -> List<&2, String>: - ["0", "false", "no", "off", "n", "f"] - -# LAW: an argument other than a flag that has an env value falls back to it -# SHAKE-PARSE-11 -law fallback_env: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for +vv: String - for h_kind: {S.env.flag.is(kind) == False{} : Bool} - for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} - {S.resolve.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars) - == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{vv}}, cs} : S.Arg} - -# LAW: a flag with an env value that, lowercased, is not an off word falls -# back to `true` -# SHAKE-PARSE-11 -law fallback_flag_on: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for +vv: String - for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} - for h_on: {S.allowed.ok(off_words(), String.to_lower(vv)) == False{} : Bool} - {S.resolve.arg(S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, dd}, cs}, vars) - == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Some{"true"}}, cs} : S.Arg} - -# LAW: a flag with an env value that, lowercased, is an off word has no -# fallback -# SHAKE-PARSE-11 -law fallback_flag_off: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +hp: String - for +req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for +vv: String - for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} - for h_off: {S.allowed.ok(off_words(), String.to_lower(vv)) == True{} : Bool} - {S.resolve.arg(S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, dd}, cs}, vars) - == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, None{}}, cs} : S.Arg} - -# LAW: an argument with no env value falls back to its default -# SHAKE-PARSE-11 -law fallback_default: - for +nn: String - for +sh: Maybe<&2, String> - for +lo: Maybe<&2, String> - for +kind: S.ArgKind - for +hp: String - for +req: Bool - for +ev: Maybe<&2, String> - for +dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for h_env: {S.env.of(ev, vars) == None{} : Maybe<&2, String>} - {S.resolve.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars) - == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs} : S.Arg} - -# LAW: a spec's arguments are resolved in the root command and below it -# SHAKE-PARSE-11 -law resolve_spec: - for +nn: String - for +ab: String - for +ver: Maybe<&2, String> - for args: List<&2, S.Arg> - for subs: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - {S.resolve(S.Cli{nn, ab, ver, args, subs}, vars) - == S.Cli{nn, ab, ver, S.resolve.args(args, vars), S.resolve.subs(subs, vars)} : S.Cli} - -# LAW: a command's arguments are resolved one by one, in order -# SHAKE-PARSE-11 -law resolve_args_con: - for aa: S.Arg - for tt: List<&2, S.Arg> - for +vars: List<&2, Shake.Var> - {S.resolve.args(aa <> tt, vars) == S.resolve.arg(aa, vars) <> S.resolve.args(tt, vars) : List<&2, S.Arg>} - -# LAW: a subcommand's arguments are resolved, and every subcommand under it -# SHAKE-PARSE-11 -law resolve_subs_con: - for +nn: String - for +ab: String - for args: List<&2, S.Arg> - for kids: List<&2, S.Sub> - for tt: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - {S.resolve.subs(S.Sub{nn, ab, args, kids} <> tt, vars) - == S.Sub{nn, ab, S.resolve.args(args, vars), S.resolve.subs(kids, vars)} <> S.resolve.subs(tt, vars) - : List<&2, S.Sub>} - -# LAW: `parse_env` is `parse` against the spec with every default replaced by -# its fallback, then the check SHAKE-PARSE-12 describes -# SHAKE-PARSE-11 -law env_parse: - for +spec: Shake.Cli - for ws: List<&2, String> - for +vars: List<&2, Shake.Var> - {Shake.parse_env(spec, ws, vars) == S.env.check(spec, vars, Shake.parse(S.resolve(spec, vars), ws)) - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: with no pairs, `parse_env` is `parse` -# SHAKE-PARSE-11 -law env_none: - for +spec: Shake.Cli - for ws: List<&2, String> - {Shake.parse_env(spec, ws, []) == Shake.parse(spec, ws) : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: a failed parse fails `parse_env` with the same error -# SHAKE-PARSE-12 -law env_check_fail: - for spec: Shake.Cli - for vars: List<&2, Shake.Var> - for ee: Shake.ParseErr - {S.env.check(spec, vars, Fail{ee}) == Fail{ee} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: a successful parse that bound no env value outside its choices is -# what `parse_env` answers -# SHAKE-PARSE-12 -law env_check_good: - for nn: String - for ab: String - for ver: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - for +mm: Shake.Matched - for h_none: {S.env.bad.go(mm, args, subs, vars) == None{} : Maybe<&2, Shake.Var>} - {S.env.check(S.Cli{nn, ab, ver, args, subs}, vars, Done{mm}) == Done{mm} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: a successful parse that bound an env value outside its choices fails -# with BadValue at the selected path -# SHAKE-PARSE-12 -law env_check_bad: - for nn: String - for ab: String - for ver: Maybe<&2, String> - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - for +mm: Shake.Matched - for +name: String - for +vv: String - for h_bad: {S.env.bad.go(mm, args, subs, vars) == Some{(name, vv)} : Maybe<&2, Shake.Var>} - {S.env.check(S.Cli{nn, ab, ver, args, subs}, vars, Done{mm}) == Fail{S.BadValue{Shake.path_of(mm), name, vv}} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: the last command on the path is checked among its own arguments and -# bindings -# SHAKE-PARSE-12 -law bad_go_leaf: - for +bs: List<&2, S.Bind> - for +args: List<&2, S.Arg> - for subs: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - {S.env.bad.go(S.Leaf{bs}, args, subs, vars) == S.env.bad.args(args, vars, bs) : Maybe<&2, Shake.Var>} - -# LAW: a command's own arguments come before the subcommand selected under it -# SHAKE-PARSE-12 -law bad_go_root: - for +bs: List<&2, S.Bind> - for +name: String - for +next: Shake.Matched - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - for +pp: Shake.Var - for h_here: {S.env.bad.args(args, vars, bs) == Some{pp} : Maybe<&2, Shake.Var>} - {S.env.bad.go(S.Node{bs, name, next}, args, subs, vars) == Some{pp} : Maybe<&2, Shake.Var>} - -# LAW: with none among a command's own arguments, the subcommand selected -# under it is checked among its arguments and bindings -# SHAKE-PARSE-12 -law bad_go_down: - for +bs: List<&2, S.Bind> - for +name: String - for +next: Shake.Matched - for +args: List<&2, S.Arg> - for +subs: List<&2, S.Sub> - for +vars: List<&2, Shake.Var> - for h_none: {S.env.bad.args(args, vars, bs) == None{} : Maybe<&2, Shake.Var>} - {S.env.bad.go(S.Node{bs, name, next}, args, subs, vars) - == S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars) - : Maybe<&2, Shake.Var>} - -# LAW: within a command, the first argument in spec order is the one reported -# SHAKE-PARSE-12 -law bad_args_here: - for +aa: S.Arg - for +tt: List<&2, S.Arg> - for +vars: List<&2, Shake.Var> - for +bs: List<&2, S.Bind> - for +pp: Shake.Var - for h_here: {S.env.bad.arg(aa, vars, bs) == Some{pp} : Maybe<&2, Shake.Var>} - {S.env.bad.args(aa <> tt, vars, bs) == Some{pp} : Maybe<&2, Shake.Var>} - -# LAW: an argument that is fine passes the check on to the next -# SHAKE-PARSE-12 -law bad_args_next: - for +aa: S.Arg - for +tt: List<&2, S.Arg> - for +vars: List<&2, Shake.Var> - for +bs: List<&2, S.Bind> - for h_none: {S.env.bad.arg(aa, vars, bs) == None{} : Maybe<&2, Shake.Var>} - {S.env.bad.args(aa <> tt, vars, bs) == S.env.bad.args(tt, vars, bs) : Maybe<&2, Shake.Var>} - -# LAW: no command has a bad argument when there are no pairs -# SHAKE-PARSE-12 -law bad_args_nil: - for args: List<&2, S.Arg> - for +bs: List<&2, S.Bind> - {S.env.bad.args(args, [], bs) == None{} : Maybe<&2, Shake.Var>} - -# LAW: an argument other than a flag whose env value is outside its choices -# and bound in its command is reported with that value -# SHAKE-PARSE-12 -law bad_arg_some: - for +nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for +kind: S.ArgKind - for hp: String - for req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for +bs: List<&2, S.Bind> - for +vv: String - for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} - for h_kind: {S.env.flag.is(kind) == False{} : Bool} - for h_out: {S.allowed(cs, vv) == False{} : Bool} - for h_bound: {List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv) == True{} : Bool} - {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == Some{(nn, vv)} - : Maybe<&2, Shake.Var>} - -# LAW: an argument with no env value is never reported -# SHAKE-PARSE-12 -law bad_arg_unset: - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for kind: S.ArgKind - for hp: String - for req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for bs: List<&2, S.Bind> - for h_env: {S.env.of(ev, vars) == None{} : Maybe<&2, String>} - {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} - : Maybe<&2, Shake.Var>} - -# LAW: a flag is never reported -# SHAKE-PARSE-12 -law bad_arg_flag: - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for hp: String - for req: Bool - for ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for cs: List<&2, String> - for vars: List<&2, Shake.Var> - for bs: List<&2, S.Bind> - {S.env.bad.arg(S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} - : Maybe<&2, Shake.Var>} - -# LAW: an env value its choices allow is never reported -# SHAKE-PARSE-12 -law bad_arg_allowed: - for +nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for +kind: S.ArgKind - for hp: String - for req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for +bs: List<&2, S.Bind> - for +vv: String - for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} - for h_in: {S.allowed(cs, vv) == True{} : Bool} - {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} - : Maybe<&2, Shake.Var>} - -# LAW: an env value its command does not bind is never reported -# SHAKE-PARSE-12 -law bad_arg_unbound: - for +nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for +kind: S.ArgKind - for hp: String - for req: Bool - for +ev: Maybe<&2, String> - for dd: Maybe<&2, String> - for +cs: List<&2, String> - for +vars: List<&2, Shake.Var> - for +bs: List<&2, S.Bind> - for +vv: String - for h_env: {S.env.of(ev, vars) == Some{vv} : Maybe<&2, String>} - for h_free: {List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv) == False{} : Bool} - {S.env.bad.arg(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs}, vars, bs) == None{} - : Maybe<&2, Shake.Var>} - -# LAW: an option line shows the env var after the help text, before the -# default and the possible values -# SHAKE-HELP-3 -law help_env_shown: - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for kind: S.ArgKind - for hp: String - for req: Bool - for +var: String - for +dd: Maybe<&2, String> - for +cs: List<&2, String> - {S.help.extra(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{Some{var}, dd}, cs}) - == (" [env: " ++ var ++ "]") ++ S.help.extra.go(S.text.of(dd, ""), cs) : String} - -# LAW: an option line with no env var shows only the default and the possible -# values, as before -# SHAKE-HELP-3 -law help_env_none: - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for kind: S.ArgKind - for hp: String - for req: Bool - for +dd: Maybe<&2, String> - for +cs: List<&2, String> - {S.help.extra(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{None{}, dd}, cs}) - == S.help.extra.go(S.text.of(dd, ""), cs) : String} - -# LAW: `env_vars` asks for the root command's env vars, then its -# subcommands' -# SHAKE-ARGS-2 -law env_names_root: - for nn: String - for ab: String - for ver: Maybe<&2, String> - for args: List<&2, S.Arg> - for subs: List<&2, S.Sub> - {S.env.names(S.Cli{nn, ab, ver, args, subs}) - == List.append(&2, String, S.env.names.args(args), S.env.names.forest(subs)) : List<&2, String>} - -# LAW: a subcommand's env vars come before those of the subcommands under it, -# and those before its next sibling's -# SHAKE-ARGS-2 -law env_names_forest: - for nn: String - for ab: String - for args: List<&2, S.Arg> - for kids: List<&2, S.Sub> - for tt: List<&2, S.Sub> - {S.env.names.forest(S.Sub{nn, ab, args, kids} <> tt) - == List.append(&2, String, S.env.names.args(args), - List.append(&2, String, S.env.names.forest(kids), S.env.names.forest(tt))) : List<&2, String>} - -# LAW: an argument with an env var names it, in spec order -# SHAKE-ARGS-2 -law env_names_some: - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for kind: S.ArgKind - for hp: String - for req: Bool - for +var: String - for dd: Maybe<&2, String> - for cs: List<&2, String> - for tt: List<&2, S.Arg> - {S.env.names.args(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{Some{var}, dd}, cs} <> tt) - == var <> S.env.names.args(tt) : List<&2, String>} - -# LAW: an argument with no env var names none -# SHAKE-ARGS-2 -law env_names_none: - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for kind: S.ArgKind - for hp: String - for req: Bool - for dd: Maybe<&2, String> - for cs: List<&2, String> - for tt: List<&2, S.Arg> - {S.env.names.args(S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{None{}, dd}, cs} <> tt) - == S.env.names.args(tt) : List<&2, String>} - -# LAW: a name `IO.get_env` found a value for is kept with that value, in front -# of the rest (a lemma for SHAKE-TRUST-5's reading) -law kept_set: - for +nn: String - for vv: String - for rest: List<&2, Shake.Var> - {Args.kept(nn, Done{vv}, rest) == (nn, vv) <> rest : List<&2, Shake.Var>} - -# LAW: a name `IO.get_env` failed for gives no pair (a lemma for -# SHAKE-TRUST-5's reading) -law kept_unset: - for nn: String - for ee: U32 & String - for rest: List<&2, Shake.Var> - {Args.kept(nn, Fail{ee}, rest) == rest : List<&2, Shake.Var>} - -# whether an error is an unknown option -def unknown.is(err: Shake.ParseErr) -> Bool: - match err: - case S.UnknownFlag{_at, _word}: - True{} - case _other: - False{} - -# the arguments of a page's command -def how_args(how: S.How) -> List<&2, S.Arg>: - S.How{_b, _a, _v, args, _s, _u} = how - args - -# LAW: only an unknown option can get a suggestion -# SHAKE-ERR-3 -law sug_other: - for spec: Shake.Cli - for +err: Shake.ParseErr - for h_err: {unknown.is(err) == False{} : Bool} - {Shake.suggestion(spec, err) == None{} : Maybe<&2, String>} - -# LAW: a word that does not start with `--` gets no suggestion -# SHAKE-ERR-3 -law sug_short: - for +spec: Shake.Cli - for +at: List<&2, String> - for +word: String - for h_word: {String.starts_with(word, "--") == False{} : Bool} - {Shake.suggestion(spec, S.UnknownFlag{at, word}) == None{} : Maybe<&2, String>} - -# LAW: a long option word's suggestion is the closest close candidate to its -# spelling up to `=`, among the long spellings of the command at `at`, reached -# as `help` reaches it, as a long option word -# SHAKE-ERR-3 -law sug_long: - for +spec: Shake.Cli - for +at: List<&2, String> - for +body: String - for +name: String - for val: Maybe<&2, String> - for h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>} - {Shake.suggestion(spec, S.UnknownFlag{at, "--" ++ body}) - == S.sug.found(S.sug.best(S.sug.cands(how_args(reach(at, root(spec)))), name)) : Maybe<&2, String>} - -# LAW: a candidate is suggested as its long option word -# SHAKE-ERR-3 -law sug_found_some: - for cc: String - {S.sug.found(Some{cc}) == Some{"--" ++ cc} : Maybe<&2, String>} - -# LAW: an argument with a long spelling is a candidate, in spec order -# SHAKE-ERR-3 -law cands_some: - for nn: String - for sh: Maybe<&2, String> - for ll: String - for kind: S.ArgKind - for hp: String - for req: Bool - for fb: S.Fallback - for cs: List<&2, String> - for tt: List<&2, S.Arg> - {S.sug.cands(S.Arg{nn, sh, Some{ll}, kind, hp, req, fb, cs} <> tt) == ll <> S.sug.cands(tt) : List<&2, String>} - -# LAW: an argument with no long spelling is not a candidate -# SHAKE-ERR-3 -law cands_none: - for nn: String - for sh: Maybe<&2, String> - for kind: S.ArgKind - for hp: String - for req: Bool - for fb: S.Fallback - for cs: List<&2, String> - for tt: List<&2, S.Arg> - {S.sug.cands(S.Arg{nn, sh, None{}, kind, hp, req, fb, cs} <> tt) == S.sug.cands(tt) : List<&2, String>} - -# LAW: a candidate is close when it is some edits away, and three times as -# many is at most the longer length -# SHAKE-ERR-3 -law sug_close: - for +typed: String - for +cc: String - {S.sug.close(typed, cc) - == Bool.and(Nat.is_lt(0n, S.osa(typed, cc)), - Nat.is_le(Nat.mul(3n, S.osa(typed, cc)), Nat.max(String.length(typed), String.length(cc)))) : Bool} - -# LAW: no candidates, no closest one -# SHAKE-ERR-3 -law best_nil: - for typed: String - {S.sug.best([], typed) == None{} : Maybe<&2, String>} - -# LAW: a candidate that is not close is passed over -# SHAKE-ERR-3 -law best_far: - for +cc: String - for +tt: List<&2, String> - for +typed: String - for h_far: {S.sug.close(typed, cc) == False{} : Bool} - {S.sug.best(cc <> tt, typed) == S.sug.best(tt, typed) : Maybe<&2, String>} - -# LAW: a close candidate with none close after it is the closest -# SHAKE-ERR-3 -law best_only: - for +cc: String - for +tt: List<&2, String> - for +typed: String - for h_close: {S.sug.close(typed, cc) == True{} : Bool} - for h_rest: {S.sug.best(tt, typed) == None{} : Maybe<&2, String>} - {S.sug.best(cc <> tt, typed) == Some{cc} : Maybe<&2, String>} - -# LAW: a close candidate no farther than the closest after it is the closest: -# the first wins a tie -# SHAKE-ERR-3 -law best_here: - for +cc: String - for +tt: List<&2, String> - for +typed: String - for +ww: String - for h_close: {S.sug.close(typed, cc) == True{} : Bool} - for h_rest: {S.sug.best(tt, typed) == Some{ww} : Maybe<&2, String>} - for h_le: {Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)) == True{} : Bool} - {S.sug.best(cc <> tt, typed) == Some{cc} : Maybe<&2, String>} - -# LAW: a close candidate farther than the closest after it is passed over -# SHAKE-ERR-3 -law best_there: - for +cc: String - for +tt: List<&2, String> - for +typed: String - for +ww: String - for h_close: {S.sug.close(typed, cc) == True{} : Bool} - for h_rest: {S.sug.best(tt, typed) == Some{ww} : Maybe<&2, String>} - for h_le: {Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)) == False{} : Bool} - {S.sug.best(cc <> tt, typed) == Some{ww} : Maybe<&2, String>} - -# LAW: the error text shows the suggestion, when there is one -# SHAKE-ERR-4 -law err_text_tip: - for +spec: Shake.Cli - for +err: Shake.ParseErr - for +ss: String - for h_sug: {Shake.suggestion(spec, err) == Some{ss} : Maybe<&2, String>} - {String.contains(Shake.err_text(spec, err), ss) == True{} : Bool} - -# whether a word refused at `at` asks for the version: `--version`, or `-V` -# when the root does not spell `--version`, refused at the root of a spec -# with a version -def asks_version(spec: Shake.Cli, +at: List<&2, String>, +word: String) -> Bool: - S.Cli{_n, _a, version, args, _s} = spec - Bool.and(Bool.or(String.eq(word, "--version"), - Bool.and(String.eq(word, "-V"), Maybe.is_none(&2, S.Arg, S.by_long(args, "version")))), - Bool.and(List.is_empty(&2, String, at), Maybe.is_some(&2, String, version))) - -# whether a parse's answer refuses an unknown option -def unknown.res(rr: Result<&2, &2, Shake.ParseErr, Shake.Matched>) -> Bool: - match rr: - case Done{_m}: - False{} - case Fail{ee}: - unknown.is(ee) - -# LAW: where the walk would refuse a word that asks for the version, the -# parse fails with a request for the version -# SHAKE-VER-1 -law version_asked: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +at: List<&2, String> - for +word: String - for h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == Fail{S.UnknownFlag{at, word}} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_asks: {asks_version(spec, at, word) == True{} : Bool} - {Shake.parse(spec, ws) == Fail{S.NeedVersion{}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: where the walk would refuse a word that does not ask for the version, -# the parse refuses it -# SHAKE-VER-1 -law version_unasked: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +at: List<&2, String> - for +word: String - for h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == Fail{S.UnknownFlag{at, word}} - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_asks: {asks_version(spec, at, word) == False{} : Bool} - {Shake.parse(spec, ws) == Fail{S.UnknownFlag{at, word}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: every other answer of the walk is the parse's -# SHAKE-VER-1 -law version_other: - for +spec: Shake.Cli - for +ws: List<&2, String> - for +rr: Result<&2, &2, Shake.ParseErr, Shake.Matched> - for h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == rr - : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - for h_rr: {unknown.res(rr) == False{} : Bool} - {Shake.parse(spec, ws) == rr : Result<&2, &2, Shake.ParseErr, Shake.Matched>} - -# LAW: resolving the env vars keeps the version and every long spelling, so -# `parse_env`'s parse asks for the version where `parse`'s does -# SHAKE-VER-1 -law resolve_asks: - for +spec: Shake.Cli - for +vars: List<&2, Shake.Var> - for +at: List<&2, String> - for +word: String - {asks_version(S.resolve(spec, vars), at, word) == asks_version(spec, at, word) : Bool} - -# LAW: a request for the version reads as the root's name and the version -# SHAKE-VER-2 -law version_of_some: - for +name: String - for about: String - for +ver: String - for args: List<&2, S.Arg> - for subs: List<&2, S.Sub> - {Shake.version_of(S.Cli{name, about, Some{ver}, args, subs}, S.NeedVersion{}) == Some{name ++ " " ++ ver} - : Maybe<&2, String>} - -# LAW: a spec with no version reads no version, whatever the error -# SHAKE-VER-2 -law version_of_none: - for +name: String - for about: String - for args: List<&2, S.Arg> - for subs: List<&2, S.Sub> - for err: Shake.ParseErr - {Shake.version_of(S.Cli{name, about, None{}, args, subs}, err) == None{} : Maybe<&2, String>} - -# LAW: an error that is no request for the version reads no version -# SHAKE-VER-2 -law version_of_other: - for +spec: Shake.Cli - for err: Shake.ParseErr - for h_err: {version.is(err) == False{} : Bool} - {Shake.version_of(spec, err) == None{} : Maybe<&2, String>} - -# the root command as a subcommand, to walk a path from -def root_sub(app: Shake.Cli) -> S.Sub: - S.Cli{+name, +about, _v, args, subs} = app - S.Sub{name, about, args, subs} - -# the subcommand named `name` of a found command -def sub_named(here: Maybe<&2, S.Sub>, +name: String) -> Maybe<&2, S.Sub>: - match here: - case None{}: - None{} - case Some{ss}: - S.Sub{_n, _a, _g, subs} = ss - S.find_sub(subs, name) - -# the command a path names below a found one, each name a subcommand of the -# command before it, or None -def named_cmd(path: List<&2, String>, here: Maybe<&2, S.Sub>) -> Maybe<&2, S.Sub>: - match path: - case Nil{}: - here - case Con{+hh, tt}: - named_cmd(tt, sub_named(here, hh)) - -# whether an argument kind is a flag -def is_flag(kk: S.ArgKind) -> Bool: - match kk: - case S.Flag{}: - True{} - case _other: - False{} - -# LAW: no reads, no reports -# SHAKE-READ-1 -law reads_nil: - for +spec: Shake.Cli - {Shake.check_reads(spec, []) == [] : List<&2, K.ReadErr>} - -# LAW: the reports of each read, in the order of the reads -# SHAKE-READ-1 -law reads_con: - for +spec: Shake.Cli - for rr: K.Read - for rs: List<&2, K.Read> - {Shake.check_reads(spec, rr <> rs) == List.append(&2, K.ReadErr, K.read(spec, rr), Shake.check_reads(spec, rs)) - : List<&2, K.ReadErr>} - -# LAW: a read whose path names no command is reported as that -# SHAKE-READ-1 -law read_no_command: - for +spec: Shake.Cli - for +path: List<&2, String> - for +name: String - for want: K.Want - for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == None{} : Maybe<&2, S.Sub>} - {K.read(spec, K.Read{path, name, want}) == [K.NoCommand{path}] : List<&2, K.ReadErr>} - -# LAW: a read of a name no argument of its command has is reported as that -# SHAKE-READ-1 -law read_no_arg: - for +spec: Shake.Cli - for +path: List<&2, String> - for +name: String - for want: K.Want - for sn: String - for sa: String - for +sargs: List<&2, S.Arg> - for ssubs: List<&2, S.Sub> - for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} - for h_arg: {S.by_name(sargs, name) == None{} : Maybe<&2, S.Arg>} - {K.read(spec, K.Read{path, name, want}) == [K.NoArg{path, name}] : List<&2, K.ReadErr>} - -# LAW: a flag read is reported exactly when its argument is not a flag -# SHAKE-READ-1 -law read_flag: - for +spec: Shake.Cli - for +path: List<&2, String> - for +name: String - for sn: String - for sa: String - for +sargs: List<&2, S.Arg> - for ssubs: List<&2, S.Sub> - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for +kind: S.ArgKind - for hp: String - for rq: Bool - for fb: S.Fallback - for cs: List<&2, String> - for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} - for h_arg: {S.by_name(sargs, name) == Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}} : Maybe<&2, S.Arg>} - {K.read(spec, K.Read{path, name, K.WantFlag{}}) - == K.reads.one(Bool.not(is_flag(kind)), K.NotFlag{path, name}) : List<&2, K.ReadErr>} - -# LAW: a value read is reported exactly when its argument is a flag -# SHAKE-READ-1 -law read_value: - for +spec: Shake.Cli - for +path: List<&2, String> - for +name: String - for sn: String - for sa: String - for +sargs: List<&2, S.Arg> - for ssubs: List<&2, S.Sub> - for nn: String - for sh: Maybe<&2, String> - for lo: Maybe<&2, String> - for +kind: S.ArgKind - for hp: String - for rq: Bool - for fb: S.Fallback - for cs: List<&2, String> - for h_cmd: {named_cmd(path, Some{root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>} - for h_arg: {S.by_name(sargs, name) == Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}} : Maybe<&2, S.Arg>} - {K.read(spec, K.Read{path, name, K.WantValue{}}) == K.reads.one(is_flag(kind), K.IsFlag{path, name}) - : List<&2, K.ReadErr>} diff --git a/src/PROOF.bend b/src/PROOF.bend deleted file mode 100644 index 335bad4..0000000 --- a/src/PROOF.bend +++ /dev/null @@ -1,11527 +0,0 @@ -# shake: the proofs. `bend src/PROOF.bend` is the gate. -import Base -import ../main.bend as Shake -import ../src/cli.bend as S -import ../src/check.bend as K -import ../src/eq.bend as Eq -import ../src/args.bend as Args -import ../src/walk.bend as Walk -import ../src/grow.bend as Grow -import ./LAWS.bend as Laws - -def Laws.looks_flag_long(_ss): - {==} - -def Laws.allowed_any(_vv): - {==} - -def Laws.get_nil(_name): - {==} - -def Laws.on_nil(_name): - {==} - -def Laws.err_text_help(_spec, _path): - {==} - -def Laws.err_text_version(_spec): - {==} - -def Laws.err_text_iff(spec, err): - match spec err: - case S.Cli{_n, _a, _v, _g, _s} S.UnknownFlag{_at, _flag}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.Missing{_at, _name}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.NoValue{_at, _name}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.BadValue{_at, _name, _value}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.NeedHelp{_path}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.Unexpected{_at, _arg}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.Repeated{_at, _name}: - {==} - case S.Cli{_n, _a, _v, _g, _s} S.NeedVersion{}: - {==} - -def Laws.hit_append(bb, _vv, _xs, _ys): - match bb: - case True{}: - {==} - case False{}: - {==} - -def Laws.bind_values_append(bs, cs, name): - match bs: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Bind{+nn, +vv}: - ih = Equal.sym(List<&2, String>, - S.get_all.bind(List.append(&2, S.Bind, tt, cs), name), - List.append(&2, String, S.get_all.bind(tt, name), S.get_all.bind(cs, name)), - Laws.bind_values_append(tt, cs, name)) - %ih : {S.get_all.bind.hit(String.eq(nn, name), vv, _) - == List.append(&2, String, - S.get_all.bind.hit(String.eq(nn, name), vv, S.get_all.bind(tt, name)), - S.get_all.bind(cs, name)) : List<&2, String>} - Laws.hit_append(String.eq(nn, name), vv, S.get_all.bind(tt, name), - S.get_all.bind(cs, name)) - -def Laws.last_snoc(xs, vv, _dflt): - match xs: - case Nil{}: - {==} - case Con{hh, tt}: - Laws.last_snoc(tt, vv, Some{hh}) - -def Laws.get_all_append(bs, cs, name): - Laws.bind_values_append(bs, cs, name) - -def Laws.get_all_hit(name, vv): - ee = Equal.sym(Bool, String.eq(name, name), True{}, Eq.string_eq_self(name)) - %ee : {S.get_all.bind.hit(_, vv, []) == [vv] : List<&2, String>} - {==} - -def Laws.get_all_miss(other, name, vv, differ): - ee = Equal.sym(Bool, String.eq(other, name), False{}, differ) - %ee : {S.get_all.bind.hit(_, vv, []) == [] : List<&2, String>} - {==} - -def Laws.get_last(bs, cs, name, vv, after): - e1 = Equal.sym(List<&2, String>, - S.get_all.bind(List.append(&2, S.Bind, bs, S.Bind{name, vv} <> cs), name), - List.append(&2, String, S.get_all.bind(bs, name), S.get_all.bind(S.Bind{name, vv} <> cs, name)), - Laws.bind_values_append(bs, S.Bind{name, vv} <> cs, name)) - %e1 : {S.get.last(_, None{}) == Some{vv} : Maybe<&2, String>} - e2 = Equal.sym(Bool, String.eq(name, name), True{}, Eq.string_eq_self(name)) - %e2 : {S.get.last(List.append(&2, String, S.get_all.bind(bs, name), - S.get_all.bind.hit(_, vv, S.get_all.bind(cs, name))), None{}) == Some{vv} - : Maybe<&2, String>} - e3 = Equal.sym(List<&2, String>, S.get_all.bind(cs, name), [], after) - %e3 : {S.get.last(List.append(&2, String, S.get_all.bind(bs, name), vv <> _), None{}) - == Some{vv} : Maybe<&2, String>} - Laws.last_snoc(S.get_all.bind(bs, name), vv, None{}) - -def Laws.get_none(bs, name, none): - ee = Equal.sym(List<&2, String>, S.get_all.bind(bs, name), [], none) - %ee : {S.get.last(_, None{}) == None{} : Maybe<&2, String>} - {==} - -def Laws.on_some(found, name, vv, h_get): - ee = Equal.sym(Maybe<&2, String>, Shake.get(found, name), Some{vv}, h_get) - %ee : {S.on.of(_) == String.eq(vv, "true") : Bool} - {==} - -def Laws.on_none(found, name, h_get): - ee = Equal.sym(Maybe<&2, String>, Shake.get(found, name), None{}, h_get) - %ee : {S.on.of(_) == False{} : Bool} - {==} - -def Laws.get_all_node(_bs, _nn, _below, _name): - {==} - -def Laws.get_node(_bs, _nn, _below, _name): - {==} - -def Laws.sub_name_leaf(_bs): - {==} - -def Laws.sub_name_node(_bs, _nn, _below): - {==} - -def Laws.sub_of_hit(_bs, nn, below): - ee = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) - %ee : {S.sub_of.pick(_, below) == Some{below} : Maybe<&2, S.Matched>} - {==} - -def Laws.sub_of_miss(_bs, nn, below, name, differ): - ee = Equal.sym(Bool, String.eq(nn, name), False{}, differ) - %ee : {S.sub_of.pick(_, below) == None{} : Maybe<&2, S.Matched>} - {==} - -def Laws.sub_of_leaf(_bs, _name): - {==} - -def Laws.path_of_leaf(_bs): - {==} - -def Laws.path_of_node(_bs, _nn, _below): - {==} - -def Laws.at_nil(found): - match found: - case S.Leaf{_b}: - {==} - case S.Node{_b, _n, _s}: - {==} - -# a walk down that found nothing finds nothing further down -def at_cons.none(rest: List<&2, String>) -> {S.at.go(rest, None{}) == None{} : Maybe<&2, S.Matched>}: - match rest: - case Nil{}: - {==} - case Con{_h, tt}: - at_cons.none(tt) - -# the rest of a walk down, from what the first name found -def at_cons.via( - mm: Maybe<&2, S.Matched>, - +rest: List<&2, String> -) -> {S.at.go(rest, mm) == Laws.at.via(mm, rest) : Maybe<&2, S.Matched>}: - match mm: - case None{}: - at_cons.none(rest) - case Some{_m}: - {==} - -def Laws.at_cons(found, name, rest): - at_cons.via(S.sub_of(found, name), rest) - -def Laws.copy_keeps(xs): - match xs: - case Nil{}: - {==} - case Con{+hh, tt}: - ih = Equal.sym(List<&1, String>, Laws.down(Args.copy(tt)), tt, Laws.copy_keeps(tt)) - %ih : {hh <> _ == hh <> tt : List<&1, String>} - {==} - -def Laws.words_keeps(xs): - match xs: - case Nil{}: - {==} - case Con{_hh, tt}: - Laws.copy_keeps(tt) - -def Laws.walk_append(xs, ys, st): - match xs: - case Nil{}: - {==} - case Con{+hh, tt}: - Laws.walk_append(tt, ys, S.parse.step(hh, st)) - -def Laws.dead_walk(ys, err, args, up, subs, path, binds, pos, raw, seen): - match ys: - case Nil{}: - {==} - case Con{_hh, tt}: - Laws.dead_walk(tt, err, args, up, subs, path, binds, pos, raw, seen) - -# the type that is empty exactly where a Maybe is Some -def some.emp(mm: Maybe<&2, S.ParseErr>) -> Type: - match mm: - case None{}: - Unit - case Some{_e}: - Empty - -# the clash itself: None is not Some -def none.clash(+err: S.ParseErr, eq: {None{} == Some{err} : Maybe<&2, S.ParseErr>}) -> Empty: - %eq : some.emp(_) - Unit{} - -# nothing is in Empty, so it is any type -def gone(a, -A: Kind(a), never: Empty) -> A: - match never: - -# None is never Some, so such an equation proves anything -def none.absurd( - a, - -A: Kind(a), - +err: S.ParseErr, - eq: {None{} == Some{err} : Maybe<&2, S.ParseErr>} -) -> A: - gone(a, A, none.clash(err, eq)) - -# a Some read back as a failed parse -def some.fail(mm: Maybe<&2, S.ParseErr>) -> Result<&2, &2, S.ParseErr, S.Matched>: - match mm: - case None{}: - Fail{S.Missing{[], ""}} - case Some{ee}: - Fail{ee} - -def Laws.fail_from(st, more, err, died): - match st: - case S.St{S.Dead{+ee}, +aa, +gg, +ss, +pp, +bb, +oo, +rr, +nn}: - e1 = Equal.sym(S.St, - S.parse.walk(more, S.St{S.Dead{ee}, aa, gg, ss, pp, bb, oo, rr, nn}), - S.St{S.Dead{ee}, aa, gg, ss, pp, bb, oo, rr, nn}, - Laws.dead_walk(more, ee, aa, gg, ss, pp, bb, oo, rr, nn)) - %e1 : {S.parse.finish(_) == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>} - Equal.cong(Maybe<&2, S.ParseErr>, Result<&2, &2, S.ParseErr, S.Matched>, - mm => some.fail(mm), Some{ee}, Some{err}, died) - case S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n}: - none.absurd(&1, {S.parse.finish(S.parse.walk(more, S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n})) - == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>}, err, died) - case S.St{S.Need{_name}, _a, _g, _s, _p, _b, _o, _r, _n}: - none.absurd(&1, {S.parse.finish(S.parse.walk(more, S.St{S.Need{_name}, _a, _g, _s, _p, _b, _o, _r, _n})) - == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>}, err, died) - case S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}: - none.absurd(&1, {S.parse.finish(S.parse.walk(more, S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n})) - == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>}, err, died) - -# a failed walk finishes as its failure, whatever words follow -def fs.raw( - +spec: Shake.Cli, - +ws: List<&2, String>, - +more: List<&2, String>, - +err: Shake.ParseErr, - died: {Laws.dead_of(S.parse.walk(ws, S.parse.start(spec))) == Some{err} : Maybe<&2, S.ParseErr>} -) -> {S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec))) == Fail{err} - : Result<&2, &2, S.ParseErr, S.Matched>}: - e1 = Equal.sym(S.St, - S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec)), - S.parse.walk(more, S.parse.walk(ws, S.parse.start(spec))), - Laws.walk_append(ws, more, S.parse.start(spec))) - %e1 : {S.parse.finish(_) == Fail{err} : Result<&2, &2, S.ParseErr, S.Matched>} - Laws.fail_from(S.parse.walk(ws, S.parse.start(spec)), more, err, died) - -# a failed walk's parse is its failure, read as a request for the version -# when it is one, whatever words follow -def fs.fail( - +spec: Shake.Cli, - +ws: List<&2, String>, - +more: List<&2, String>, - +err: Shake.ParseErr, - died: {Laws.dead_of(S.parse.walk(ws, S.parse.start(spec))) == Some{err} : Maybe<&2, S.ParseErr>} -) -> {Shake.parse(spec, List.append(&2, String, ws, more)) == Fail{S.version.err(spec, err)} - : Result<&2, &2, S.ParseErr, S.Matched>}: - Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, - xx => S.version.res(spec, xx), S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec))), - Fail{err}, fs.raw(spec, ws, more, err, died)) - -# an unknown option that is no request for the version stays as it is -def fs.keep( - +spec: Shake.Cli, - +at: List<&2, String>, - +word: String, - h_ver: {S.version.asks(spec, at, word) == False{} : Bool} -) -> {Fail{S.version.err(spec, S.UnknownFlag{at, word})} == Fail{S.UnknownFlag{at, word}} - : Result<&2, &2, S.ParseErr, S.Matched>}: - ee = Equal.sym(Bool, S.version.asks(spec, at, word), False{}, h_ver) - %ee : {Fail{Bool.pick(S.ParseErr, _, S.NeedVersion{}, S.UnknownFlag{at, word})} == Fail{S.UnknownFlag{at, word}} - : Result<&2, &2, S.ParseErr, S.Matched>} - {==} - -# Done{.} and Fail{.} told apart -def vr.done_ty(rr: Result<&2, &2, S.ParseErr, S.Matched>) -> Type: - match rr: - case Done{_m}: - Unit - case Fail{_e}: - Empty - -# a failure read for the version is still no success -def vr.clash( - +spec: Shake.Cli, - ee: S.ParseErr, - found: S.Matched, - h: {Fail{S.version.err(spec, ee)} == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} -) -> Empty: - ef = Equal.sym(Result<&2, &2, S.ParseErr, S.Matched>, Fail{S.version.err(spec, ee)}, Done{found}, h) - %ef : vr.done_ty(_) - Unit{} - -# a parse that succeeded did so before the version was read off it: the -# version only ever rewrites a failure -def vr.done( - +spec: Shake.Cli, - rr: Result<&2, &2, S.ParseErr, S.Matched>, - +found: S.Matched, - h: {S.version.res(spec, rr) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} -) -> {rr == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}: - match rr: - case Done{mm}: - h - case Fail{ee}: - Empty.absurd({Fail{ee} == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, vr.clash(spec, ee, found, h)) - -# a walk that finishes missing a name, as a type -def vr.miss_ty(+spec: Shake.Cli, +ws: List<&2, String>, +path: List<&2, String>) -> Type: - &nm: String -> {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == Fail{S.Missing{path, nm}} - : Result<&2, &2, S.ParseErr, S.Matched>} - -# a walk that finishes missing a name parses missing it -def vr.miss( - +spec: Shake.Cli, - +ws: List<&2, String>, - +path: List<&2, String>, - ww: vr.miss_ty(spec, ws, path) -) -> &nm: String -> {Shake.parse(spec, ws) == Fail{S.Missing{path, nm}} : Result<&2, &2, S.ParseErr, S.Matched>}: - (n1, e1) = ww - +nm = {n1 : String} - (nm, Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, - xx => S.version.res(spec, xx), S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), - Fail{S.Missing{path, nm}}, e1)) - -def Laws.fail_stays(spec, ws, more, err, died): - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, - S.version.res(spec, S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec)))), - S.version.res(spec, Fail{err}), - S.version.res(spec, S.parse.finish(S.parse.walk(ws, S.parse.start(spec)))), - Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, - xx => S.version.res(spec, xx), - S.parse.finish(S.parse.walk(List.append(&2, String, ws, more), S.parse.start(spec))), - Fail{err}, fs.raw(spec, ws, more, err, died)), - Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, - xx => S.version.res(spec, xx), Fail{err}, S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), - Equal.sym(Result<&2, &2, S.ParseErr, S.Matched>, S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), Fail{err}, - Laws.fail_from(S.parse.walk(ws, S.parse.start(spec)), [], err, died)))) - -def Laws.prefix_empty(ss): - match ss: - case SNil{}: - {==} - case SCon{_h, _t}: - {==} - -def Laws.drop_zero(ss): - match ss: - case SNil{}: - {==} - case SCon{_h, _t}: - {==} - -def Laws.unknown_long_step( - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - h_dd, - h_cut, - h_name, - h_none, - h_lh -): - e1 = Equal.sym(Bool, String.eq("--" ++ body, "--"), False{}, h_dd) - %e1 : {S.parse.step.end(_, "--" ++ body, args, up, subs, path, bs, pos, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e0 = Equal.sym(Bool, String.starts_with(body, ""), True{}, Laws.prefix_empty(body)) - %e0 : {S.parse.step.kind.go(_, True{}, "--" ++ body, args, up, subs, path, bs, pos, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - ed = Equal.sym(String, String.drop(body, 0n), body, Laws.drop_zero(body)) - %ed : {S.parse.take_long(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e2 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) - %e2 : {S.parse.take_long.cut(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e3 = Equal.sym(Bool, String.is_empty(name), False{}, h_name) - %e3 : {S.parse.take_long.named(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e4 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), None{}, h_none) - %e4 : {S.parse.take_long.found(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e5 = Equal.sym(Bool, S.long_help(name, val, seen), False{}, h_lh) - %e5 : {S.parse.take_long.help(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - {==} - -def Laws.unknown_long_dies( - spec, - pre, - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - h_at, - h_dd, - h_cut, - h_name, - h_none, - h_lh -): - e1 = Equal.sym(S.St, - S.parse.walk(List.append(&2, String, pre, ["--" ++ body]), S.parse.start(spec)), - S.parse.walk(["--" ++ body], S.parse.walk(pre, S.parse.start(spec))), - Laws.walk_append(pre, ["--" ++ body], S.parse.start(spec))) - %e1 : {Laws.dead_of(_) == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(S.St, - S.parse.walk(pre, S.parse.start(spec)), - S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - h_at) - %e2 : {Laws.dead_of(S.parse.walk(["--" ++ body], _)) == Some{S.UnknownFlag{path, "--" ++ body}} - : Maybe<&2, S.ParseErr>} - e3 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), - S.St{S.Dead{S.UnknownFlag{path, "--" ++ body}}, args, up, subs, path, bs, pos, False{}, seen}, - Laws.unknown_long_step(body, name, val, args, up, subs, path, bs, pos, seen, - h_dd, h_cut, h_name, h_none, h_lh)) - %e3 : {Laws.dead_of(_) == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.unknown_long( - spec, - pre, - more, - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - h_at, - h_dd, - h_cut, - h_name, - h_none, - h_lh, - h_ver -): - +ww = List.append(&2, String, pre, ["--" ++ body]) - +er = {S.UnknownFlag{path, "--" ++ body} : S.ParseErr} - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, List.append(&2, String, ww, more)), - Fail{S.version.err(spec, er)}, Fail{er}, - fs.fail(spec, ww, more, er, - Laws.unknown_long_dies(spec, pre, body, name, val, args, up, subs, path, bs, pos, seen, - h_at, h_dd, h_cut, h_name, h_none, h_lh)), - fs.keep(spec, path, "--" ++ body, h_ver)) - -def Laws.step_dies( - spec, - pre, - tok, - st, - err, - h_at, - h_step -): - e1 = Equal.sym(S.St, - S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), - S.parse.walk([tok], S.parse.walk(pre, S.parse.start(spec))), - Laws.walk_append(pre, [tok], S.parse.start(spec))) - %e1 : {Laws.dead_of(_) == Some{err} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(S.St, S.parse.walk(pre, S.parse.start(spec)), st, h_at) - %e2 : {Laws.dead_of(S.parse.walk([tok], _)) == Some{err} : Maybe<&2, S.ParseErr>} - h_step - -def Laws.refused( - spec, - pre, - more, - tok, - st, - err, - h_at, - h_step -): - fs.fail(spec, List.append(&2, String, pre, [tok]), more, err, - Laws.step_dies(spec, pre, tok, st, err, h_at, h_step)) - -def Laws.cluster_done(body, orig, st): - match body: - case SNil{}: - {==} - case SCon{_cc, rest}: - Laws.cluster_done(rest, orig, st) - -def Laws.unknown_short_step( - cc, - rest, - args, - up, - subs, - path, - bs, - pos, - seen, - h_dd, - h_dash, - h_none, - h_help -): - e1 = Equal.sym(Bool, String.eq("-" ++ SCon{cc, rest}, "--"), False{}, h_dd) - %e1 : {S.parse.step.end(_, "-" ++ SCon{cc, rest}, args, up, subs, path, bs, pos, - seen) == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, - rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e4 = Equal.sym(Cmp, String.order.go(SCon{cc, rest}, "elp", LT{}), LT{}, Eq.string_order_lt(SCon{cc, rest}, "elp")) - %e4 : {S.parse.step.help(Bool.and(Cmp.is_eq(_), Bool.not(seen)), "-" ++ SCon{cc, rest}, args, up, subs, path, bs, - pos, seen) == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, - rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e3 = Equal.sym(Bool, Char.is_eq(cc, '-'), False{}, h_dash) - %e3 : {S.parse.step.kind.go(String.starts_with.go.if("", rest, _), True{}, "-" ++ SCon{cc, rest}, args, up, - subs, path, bs, pos, - seen) == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, - rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e5 = Equal.sym(Bool, S.short_help(SCon{cc, rest}, args, seen), False{}, h_help) - %e5 : {S.parse.take_short.go(_, SCon{cc, rest}, "-" ++ SCon{cc, rest}, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, SCon{cc, SNil{}}), None{}, h_none) - %e2 : {S.cluster(rest, "-" ++ SCon{cc, rest}, - S.parse.take_arg(_, S.cluster.val(_, rest), "-" ++ SCon{cc, rest}, args, up, subs, path, bs, pos, - False{}, seen), - S.cluster.ends(_, rest)) - == S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - Laws.cluster_done(rest, "-" ++ SCon{cc, rest}, S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}}, args, - up, subs, path, bs, pos, False{}, seen}) - -# the first letter of a short cluster that no argument has fails the step -def unknown_short.step( - +cc: Char, - +rest: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - h_dd: {String.eq("-" ++ SCon{cc, rest}, "--") == False{} : Bool}, - h_dash: {Char.is_eq(cc, '-') == False{} : Bool}, - h_none: {S.by_short(args, SCon{cc, SNil{}}) == None{} : Maybe<&2, S.Arg>}, - h_help: {S.short_help(SCon{cc, rest}, args, seen) == False{} : Bool} -) -> {Laws.dead_of(S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, - seen})) == Some{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}} - : Maybe<&2, S.ParseErr>}: - ee = Equal.sym(S.St, S.parse.step("-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, - False{}, seen}), S.St{S.Dead{S.UnknownFlag{path, "-" ++ SCon{cc, - rest}}}, args, up, subs, path, bs, pos, False{}, seen}, - Laws.unknown_short_step(cc, rest, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_none, h_help)) - %ee : {Laws.dead_of(_) == Some{S.UnknownFlag{path, "-" ++ SCon{cc, rest}}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.unknown_short( - spec, - pre, - more, - cc, - rest, - args, - up, - subs, - path, - bs, - pos, - seen, - h_at, - h_dd, - h_dash, - h_none, - h_help, - h_ver -): - +er = {S.UnknownFlag{path, "-" ++ SCon{cc, rest}} : S.ParseErr} - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, - Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["-" ++ SCon{cc, rest}]), more)), - Fail{S.version.err(spec, er)}, Fail{er}, - Laws.refused(spec, pre, more, "-" ++ SCon{cc, rest}, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, - seen}, er, h_at, - unknown_short.step(cc, rest, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_none, h_help)), - fs.keep(spec, path, "-" ++ SCon{cc, rest}, h_ver)) - -# a plain word with no positional left fails the step with Unexpected -def no_pos_left.step( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - h_dd: {String.eq(tok, "--") == False{} : Bool}, - h_help: {String.eq(tok, "help") == False{} : Bool}, - +h_dash: {String.starts_with(tok, "-") == False{} : Bool}, - h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} -) -> {Laws.dead_of(S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, [], False{}, - seen})) == Some{S.Unexpected{path, tok}} - : Maybe<&2, S.ParseErr>}: - ee = Equal.sym(S.St, S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, [], False{}, seen}), - S.parse.take_pos(tok, args, up, subs, path, bs, [], False{}), - Walk.wk.word_step(tok, args, up, subs, path, bs, [], seen, h_dd, h_help, h_dash, h_sub)) - %ee : {Laws.dead_of(_) == Some{S.Unexpected{path, tok}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.no_pos_left( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - seen, - h_at, - h_dd, - h_help, - h_dash, - h_sub -): - Laws.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, [], False{}, seen}, - S.Unexpected{path, tok}, h_at, - no_pos_left.step(tok, args, up, subs, path, bs, seen, h_dd, h_help, h_dash, h_sub)) - -# a plain word outside the next positional's choices fails the step with -# BadValue -def choice_refused.step( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - +name: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +rest: List<&2, S.Arg>, - h_dd: {String.eq(tok, "--") == False{} : Bool}, - h_help: {String.eq(tok, "help") == False{} : Bool}, - +h_dash: {String.starts_with(tok, "-") == False{} : Bool}, - h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>}, - h_bad: {S.allowed(cs, tok) == False{} : Bool} -) -> {Laws.dead_of(S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, - req, dflt, cs} <> rest, False{}, seen})) == Some{S.BadValue{path, name, tok}} - : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(S.St, S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, - hp, req, dflt, cs} <> rest, False{}, seen}), - S.parse.take_pos(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}), - Walk.wk.word_step(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - seen, h_dd, h_help, h_dash, - h_sub)) - %e1 : {Laws.dead_of(_) == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Bool, S.allowed(cs, tok), False{}, h_bad) - %e2 : {Laws.dead_of(S.parse.take_choice(_, name, tok, args, up, subs, path, bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{})) - == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.choice_refused( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - seen, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - h_at, - h_dd, - h_help, - h_dash, - h_sub, - h_bad -): - Laws.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, - req, dflt, cs} <> rest, False{}, seen}, S.BadValue{path, name, tok}, h_at, - choice_refused.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, - rest, h_dd, h_help, h_dash, h_sub, h_bad)) - -def Laws.long_arg_step( - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - h_dd, - h_cut, - h_name -): - e1 = Equal.sym(Bool, String.eq("--" ++ body, "--"), False{}, h_dd) - %e1 : {S.parse.step.end(_, "--" ++ body, args, up, subs, path, bs, pos, seen) - == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen) : S.St} - e0 = Equal.sym(Bool, String.starts_with(body, ""), True{}, Laws.prefix_empty(body)) - %e0 : {S.parse.step.kind.go(_, True{}, "--" ++ body, args, up, subs, path, bs, pos, seen) - == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen) : S.St} - ed = Equal.sym(String, String.drop(body, 0n), body, Laws.drop_zero(body)) - %ed : {S.parse.take_long(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen) : S.St} - e2 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) - %e2 : {S.parse.take_long.cut(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen) : S.St} - e3 = Equal.sym(Bool, String.is_empty(name), False{}, h_name) - %e3 : {S.parse.take_long.named(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen) : S.St} - {==} - -# a long option that gives a flag a value fails the step -def flag_long_valued.step( - +body: String, - +name: String, - +vv: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, - h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>}, - h_name: {String.is_empty(name) == False{} : Bool}, - h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, - h_once: {S.bound(bs, nn) == False{} : Bool} -) -> {Laws.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen})) - == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), - S.parse.take_long.found(S.by_long(args, name), name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen), - Laws.long_arg_step(body, name, Some{vv}, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) - %e1 : {Laws.dead_of(_) == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}}, h_found) - %e2 : {Laws.dead_of(S.parse.take_long.found(_, name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen)) - == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, h_once) - %e3 : {Laws.dead_of(S.parse.once(_, S.St{S.Dead{S.Unexpected{path, "--" ++ body}}, args, up, subs, path, bs, pos, - False{}, seen}, nn, path)) == Some{S.Unexpected{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.flag_long_valued( - spec, - pre, - more, - body, - name, - vv, - args, - up, - subs, - path, - bs, - pos, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_at, - h_dd, - h_cut, - h_name, - h_found, - h_once -): - Laws.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - S.Unexpected{path, "--" ++ body}, h_at, - flag_long_valued.step(body, name, vv, args, up, subs, path, bs, pos, seen, nn, sh, lo, hp, req, dflt, cs, - h_dd, h_cut, h_name, h_found, h_once)) - -# a flag-shaped word where a value is wanted fails the step -def value_flag_shaped.step( - +tok: String, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - h_flag: {S.looks_flag(tok) == True{} : Bool} -) -> {Laws.dead_of(S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen})) - == Some{S.NoValue{path, name}} : Maybe<&2, S.ParseErr>}: - ee = Equal.sym(Bool, S.looks_flag(tok), True{}, h_flag) - %ee : {Laws.dead_of(S.parse.step.need(_, name, tok, args, up, subs, path, bs, pos, raw, seen)) - == Some{S.NoValue{path, name}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.value_flag_shaped( - spec, - pre, - more, - tok, - name, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - h_at, - h_flag -): - Laws.refused(spec, pre, more, tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, - S.NoValue{path, name}, h_at, value_flag_shaped.step(tok, name, args, up, subs, path, bs, pos, raw, seen, h_flag)) - -def Laws.value_absent( - spec, - ws, - name, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - h_at -): - ee = Equal.sym(S.St, S.parse.walk(ws, S.parse.start(spec)), - S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, h_at) - %ee : {S.version.res(spec, S.parse.finish(_)) == Fail{S.NoValue{path, name}} : Result<&2, &2, S.ParseErr, S.Matched>} - {==} - -# a word after `help` that names no subcommand where the path stands fails -# the step -def help_unknown.step( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>} -) -> {Laws.dead_of(S.parse.step(tok, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen})) - == Some{S.Unexpected{path, tok}} : Maybe<&2, S.ParseErr>}: - ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, tok), None{}, h_sub) - %ee : {Laws.dead_of(S.parse.step.help_path(_, tok, args, up, subs, path, bs, pos, raw, seen)) - == Some{S.Unexpected{path, tok}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.help_unknown( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - h_at, - h_sub -): - Laws.refused(spec, pre, more, tok, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}, - S.Unexpected{path, tok}, h_at, help_unknown.step(tok, args, up, subs, path, bs, pos, raw, seen, h_sub)) - -# `--=...` fails the step -def long_no_name.step( - +body: String, - +name: String, - +val: Maybe<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, - h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - h_empty: {String.is_empty(name) == True{} : Bool} -) -> {Laws.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen})) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(Bool, String.eq("--" ++ body, "--"), False{}, h_dd) - %e1 : {Laws.dead_of(S.parse.step.end(_, "--" ++ body, args, up, subs, path, bs, pos, seen)) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - e0 = Equal.sym(Bool, String.starts_with(body, ""), True{}, Laws.prefix_empty(body)) - %e0 : {Laws.dead_of(S.parse.step.kind.go(_, True{}, "--" ++ body, args, up, subs, path, bs, pos, seen)) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - ed = Equal.sym(String, String.drop(body, 0n), body, Laws.drop_zero(body)) - %ed : {Laws.dead_of(S.parse.take_long(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) - %e2 : {Laws.dead_of(S.parse.take_long.cut(_, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - e3 = Equal.sym(Bool, String.is_empty(name), True{}, h_empty) - %e3 : {Laws.dead_of(S.parse.take_long.named(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, - False{}, seen)) - == Some{S.UnknownFlag{path, "--" ++ body}} : Maybe<&2, S.ParseErr>} - {==} - -# a word `--=...` read as `--version` would cut to the name `version`, not -# to no name -def lnv.clash( - +body: String, - +name: String, - +val: Maybe<&2, String>, - +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - +h_empty: {String.is_empty(name) == True{} : Bool}, - e: {String.eq("--" ++ body, "--version") == True{} : Bool} -) -> Empty: - ew = Eq.string_eq_true("--" ++ body, "--version", e) - eb = Equal.trans(String, body, String.drop(body, 0n), "version", - Equal.sym(String, String.drop(body, 0n), body, Laws.drop_zero(body)), - Equal.cong(String, String, ss => String.drop(ss, 2n), "--" ++ body, "--version", ew)) - ec = Equal.trans(String & Maybe<&2, String>, (name, val), S.cut_eq(body), S.cut_eq("version"), - Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut), - Equal.cong(String, String & Maybe<&2, String>, ss => S.cut_eq(ss), body, "version", eb)) - en = Equal.cong(String & Maybe<&2, String>, Bool, cv => String.is_empty(Pair.fst(String, Maybe<&2, String>, cv)), - (name, val), S.cut_eq("version"), ec) - Walk.wk.true_false(Equal.trans(Bool, True{}, String.is_empty(name), False{}, - Equal.sym(Bool, String.is_empty(name), True{}, h_empty), en)) - -# the same, as an answer told apart by cases -def lnv.go( - bb: Bool, - +body: String, - +name: String, - +val: Maybe<&2, String>, - +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - +h_empty: {String.is_empty(name) == True{} : Bool}, - e: {String.eq("--" ++ body, "--version") == bb : Bool} -) -> {String.eq("--" ++ body, "--version") == False{} : Bool}: - match bb: - case False{}: - e - case True{}: - Empty.absurd({String.eq("--" ++ body, "--version") == False{} : Bool}, - lnv.clash(body, name, val, h_cut, h_empty, e)) - -# a word `--=...` is not `--version` -def lnv.word( - +body: String, - +name: String, - +val: Maybe<&2, String>, - +h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - +h_empty: {String.is_empty(name) == True{} : Bool} -) -> {String.eq("--" ++ body, "--version") == False{} : Bool}: - lnv.go(String.eq("--" ++ body, "--version"), body, name, val, h_cut, h_empty, {==}) - -# so it asks for no version -def lnv.asks( - +spec: Shake.Cli, - +path: List<&2, String>, - +body: String, - name: String, - val: Maybe<&2, String>, - h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - h_empty: {String.is_empty(name) == True{} : Bool} -) -> {S.version.asks(spec, path, "--" ++ body) == False{} : Bool}: - match spec: - case S.Cli{+nn, +aa, +vv, +gg, +ss}: - ee = Equal.sym(Bool, String.eq("--" ++ body, "--version"), False{}, lnv.word(body, name, val, h_cut, h_empty)) - %ee : {Bool.and(Bool.or(_, Bool.and(String.eq("--" ++ body, "-V"), - Maybe.is_none(&2, S.Arg, S.by_long(gg, "version")))), - Bool.and(List.is_empty(&2, String, path), Maybe.is_some(&2, String, vv))) == False{} : Bool} - el = Equal.sym(Cmp, String.order.go(body, "", LT{}), LT{}, Eq.string_order_lt(body, "")) - %el : {Bool.and(Bool.and(Cmp.is_eq(_), Bool.not(Maybe.is_some(&2, S.Arg, S.by_long(gg, "version")))), - Bool.and(List.is_empty(&2, String, path), Maybe.is_some(&2, String, vv))) == False{} : Bool} - {==} - -def Laws.long_no_name( - spec, - pre, - more, - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - h_at, - h_dd, - h_cut, - h_empty -): - +er = {S.UnknownFlag{path, "--" ++ body} : S.ParseErr} - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, - Shake.parse(spec, List.append(&2, String, List.append(&2, String, pre, ["--" ++ body]), more)), - Fail{S.version.err(spec, er)}, Fail{er}, - Laws.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - er, h_at, - long_no_name.step(body, name, val, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_empty)), - fs.keep(spec, path, "--" ++ body, lnv.asks(spec, path, body, name, val, h_cut, h_empty))) - -def Laws.once_dead(st, _nn, _path): - match st: - case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: - {==} - -# a long option given again fails the step -def repeated_long_flag.step( - +body: String, - +name: String, - +val: Maybe<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, - h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - h_name: {String.is_empty(name) == False{} : Bool}, - h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, - h_again: {S.bound(bs, nn) == True{} : Bool} -) -> {Laws.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, - seen})) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), - S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen), - Laws.long_arg_step(body, name, val, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) - %e1 : {Laws.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs}}, h_found) - %e2 : {Laws.dead_of(S.parse.take_long.found(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - e3 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) - %e3 : {Laws.dead_of(S.parse.once(_, S.parse.take_flag(val, nn, "--" ++ body, args, up, subs, path, bs, pos, - False{}, seen), nn, path)) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - Laws.once_dead(S.parse.take_flag(val, nn, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen), nn, path) - -def Laws.repeated_long_flag( - spec, - pre, - more, - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_at, - h_dd, - h_cut, - h_name, - h_found, - h_again -): - Laws.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - S.Repeated{path, nn}, h_at, - repeated_long_flag.step(body, name, val, args, up, subs, path, bs, pos, seen, nn, sh, lo, hp, req, dflt, cs, - h_dd, h_cut, h_name, h_found, h_again)) - -# a long option given again fails the step -def repeated_long_opt.step( - +body: String, - +name: String, - +val: Maybe<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, - h_cut: {S.cut_eq(body) == (name, val) : String & Maybe<&2, String>}, - h_name: {String.is_empty(name) == False{} : Bool}, - h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, - h_again: {S.bound(bs, nn) == True{} : Bool} -) -> {Laws.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, - seen})) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), - S.parse.take_long.found(S.by_long(args, name), name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen), - Laws.long_arg_step(body, name, val, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) - %e1 : {Laws.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs}}, h_found) - %e2 : {Laws.dead_of(S.parse.take_long.found(_, name, val, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - e3 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) - %e3 : {Laws.dead_of(S.parse.once(_, S.parse.take_opt(val, nn, args, up, subs, path, bs, pos, False{}, - seen), nn, path)) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - Laws.once_dead(S.parse.take_opt(val, nn, args, up, subs, path, bs, pos, False{}, seen), nn, path) - -def Laws.repeated_long_opt( - spec, - pre, - more, - body, - name, - val, - args, - up, - subs, - path, - bs, - pos, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_at, - h_dd, - h_cut, - h_name, - h_found, - h_again -): - Laws.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - S.Repeated{path, nn}, h_at, - repeated_long_opt.step(body, name, val, args, up, subs, path, bs, pos, seen, nn, sh, lo, hp, req, dflt, cs, - h_dd, h_cut, h_name, h_found, h_again)) - -# the walk through `tok` is one step from where the words before it stood -def kept.stepped( - +spec: Shake.Cli, - +pre: List<&2, String>, - +tok: String, - +st: S.St, - h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} -) -> {S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)) == S.parse.step(tok, st) : S.St}: - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), - S.parse.walk([tok], S.parse.walk(pre, S.parse.start(spec))), S.parse.step(tok, st), - Laws.walk_append(pre, [tok], S.parse.start(spec)), - Equal.cong(S.St, S.St, xx => S.parse.walk([tok], xx), S.parse.walk(pre, S.parse.start(spec)), st, h_at)) - -# a binding's own name reads its value first -def kept.hit( - +name: String, - +vv: String, - +rest: List<&2, String> -) -> {S.get_all.bind.hit(String.eq(name, name), vv, rest) == vv <> rest : List<&2, String>}: - ee = Equal.sym(Bool, String.eq(name, name), True{}, Eq.string_eq_self(name)) - %ee : {S.get_all.bind.hit(_, vv, rest) == vv <> rest : List<&2, String>} - {==} - -# what `kept_all` claims -def kept_all.ty( - +found: Shake.Matched, - +path: List<&2, String>, - +name: String, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind> -) -> Type: - &after: List<&2, String> -> {Laws.all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), - List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after))} - : Maybe<&2, List<&2, String>>} - -# the bindings of the level `mid` stayed in when the words end: those the words -# after it made, those `mid` made, and those it had -def kept_all.ys(+xx: List<&2, S.Bind>, +xs: List<&2, S.Bind>, +bs: List<&2, S.Bind>) -> List<&2, S.Bind>: - List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs)) - -# the level's values, defaults in front, read back -def kept_all.vals(+args: List<&2, S.Arg>, +ys: List<&2, S.Bind>, +name: String) -> List<&2, String>: - S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, ys)), name) - -# what `kept_all.fill` claims -def kept_all.fill.ty( - +args: List<&2, S.Arg>, - +xx: List<&2, S.Bind>, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - +name: String -) -> Type: - &after: List<&2, String> -> {kept_all.vals(args, kept_all.ys(xx, xs, bs), name) - == List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), - List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after)) : List<&2, String>} - -# what `kept_all.read` claims -def kept_all.read.ty( - +found: Shake.Matched, - +path: List<&2, String>, - +args: List<&2, S.Arg>, - +ys: List<&2, S.Bind>, - +name: String -) -> Type: - {Laws.all_at(found, path, name) == Some{kept_all.vals(args, ys, name)} : Maybe<&2, List<&2, String>>} - -# a successful parse's Matched, read at the path where the words `mid` stayed: -# the level's bindings, all of them, with defaults in front, reversed -def kept_all.read( - +found: Shake.Matched, - +fs: S.St, - +st1: S.St, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +yy: List<&2, S.Bind>, - +xx: List<&2, S.Bind>, - +qs: List<&2, S.Level>, - +ms: List<&2, String>, - +name: String, - h_mid: {Grow.view(st1) == Grow.View{args, up, path, yy} : Grow.View}, - hd: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, - dep: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}, - e2: {Grow.gw.frame(fs) == Grow.gw.grown(xx, qs, ms, Grow.view(st1)) : Grow.Frame} -) -> kept_all.read.ty(found, path, args, List.append(&2, S.Bind, xx, yy), name): - +ys = List.append(&2, S.Bind, xx, yy) - +f0 = {S.Level{args, S.fill.args(args, ys)} : S.Level} - +fq = S.fill.levels(qs) - +fu = S.fill.levels(up) - +r0 = List.reverse(&2, S.Level, fu) - +rq = List.reverse(&2, S.Level, fq) - +pm = List.append(&2, String, path, ms) - +lvs = List.append(&2, S.Level, qs, S.Level{args, ys} <> up) - ef = Equal.trans(Grow.Frame, Grow.gw.frame(fs), Grow.gw.grown(xx, qs, ms, Grow.view(st1)), Grow.Frame{lvs, pm}, e2, - Equal.cong(Grow.View, Grow.Frame, vv => Grow.gw.grown(xx, qs, ms, vv), Grow.view(st1), Grow.View{args, up, - path, yy}, - h_mid)) - em = Equal.trans(S.Matched, found, Grow.gw.build_of(Grow.gw.frame(fs)), - S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), - Grow.gw.finished(fs, found, hd), - Equal.trans(S.Matched, Grow.gw.build_of(Grow.gw.frame(fs)), Grow.gw.build_of(Grow.Frame{lvs, pm}), - S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), - Equal.cong(Grow.Frame, S.Matched, fr => Grow.gw.build_of(fr), Grow.gw.frame(fs), Grow.Frame{lvs, pm}, ef), - Equal.cong(List<&2, S.Level>, S.Matched, ls => S.matched.build(ls, pm), - List.reverse(&2, S.Level, S.fill.levels(lvs)), List.append(&2, S.Level, r0, f0 <> rq), - Equal.trans(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(lvs)), - List.reverse(&2, S.Level, List.append(&2, S.Level, fq, f0 <> fu)), List.append(&2, S.Level, r0, f0 <> rq), - Equal.cong(List<&2, S.Level>, List<&2, S.Level>, ls => List.reverse(&2, S.Level, ls), S.fill.levels(lvs), - List.append(&2, S.Level, fq, f0 <> fu), Grow.gw.fill_mid(qs, args, ys, up)), - Grow.gw.lrev_mid(fq, f0, fu))))) - hl = Equal.trans(Nat, List.length(&2, S.Level, r0), List.length(&2, S.Level, fu), List.length(&2, String, path), - Grow.gw.len_rev(fu), - Equal.trans(Nat, List.length(&2, S.Level, fu), List.length(&2, S.Level, up), List.length(&2, String, path), - Grow.gw.len_fill(up), dep)) - ea = Equal.trans(Maybe<&2, S.Matched>, S.at(found, path), - S.at(S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), path), Some{S.matched.build(f0 <> rq, ms)}, - Equal.cong(S.Matched, Maybe<&2, S.Matched>, mm => S.at(mm, path), found, - S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), em), - Grow.gw.at_build(r0, path, f0, rq, ms, hl)) - Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, name), - Some{S.get_all.bind(S.own(S.matched.build(f0 <> rq, ms)), name)}, - Some{S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, ys)), name)}, - Equal.cong(Maybe<&2, S.Matched>, Maybe<&2, List<&2, String>>, mm => Laws.all_at.go(mm, name), S.at(found, path), - Some{S.matched.build(f0 <> rq, ms)}, ea), - Equal.cong(List<&2, S.Bind>, Maybe<&2, List<&2, String>>, bs2 => Some{S.get_all.bind(bs2, name)}, - S.own(S.matched.build(f0 <> rq, ms)), List.reverse(&2, S.Bind, S.fill.args(args, ys)), - Grow.gw.own_build(f0, rq, ms))) - -# the level's bindings, defaults in front, read back: those made before -# `mid`, those `mid` made, then the rest -def kept_all.fill( - +args: List<&2, S.Arg>, - +xx: List<&2, S.Bind>, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - +name: String, - wf: Grow.gw.Ext(S.fill.args(args, kept_all.ys(xx, xs, bs)), kept_all.ys(xx, xs, bs)) -) -> kept_all.fill.ty(args, xx, xs, bs, name): - (dx, ee) = wf - +defs = {dx : List<&2, S.Bind>} - +ys = List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs)) - +r1 = List.reverse.go(&2, S.Bind, xx, List.reverse(&2, S.Bind, defs)) - +gb = S.get_all.bind(List.reverse(&2, S.Bind, bs), name) - +gx = S.get_all.bind(List.reverse(&2, S.Bind, xs), name) - (S.get_all.bind(r1, name), - Equal.trans(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, ys)), name), - S.get_all.bind(List.reverse(&2, S.Bind, List.append(&2, S.Bind, defs, ys)), name), - List.append(&2, String, gb, List.append(&2, String, gx, S.get_all.bind(r1, name))), - Equal.cong(List<&2, S.Bind>, List<&2, String>, zs => S.get_all.bind(List.reverse(&2, S.Bind, zs), name), - S.fill.args(args, ys), List.append(&2, S.Bind, defs, ys), ee), - Equal.trans(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, List.append(&2, S.Bind, defs, ys)), name), - S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, bs), - List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1)), name), - List.append(&2, String, gb, List.append(&2, String, gx, S.get_all.bind(r1, name))), - Equal.cong(List<&2, S.Bind>, List<&2, String>, zs => S.get_all.bind(zs, name), - List.reverse(&2, S.Bind, List.append(&2, S.Bind, defs, ys)), - List.append(&2, S.Bind, List.reverse(&2, S.Bind, bs), List.append(&2, S.Bind, List.reverse(&2, S.Bind, - xs), r1)), - Grow.gw.rev_split(defs, xx, xs, bs)), - Equal.trans(List<&2, String>, - S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, bs), - List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1)), name), - List.append(&2, String, gb, - S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1), name)), - List.append(&2, String, gb, List.append(&2, String, gx, S.get_all.bind(r1, name))), - Laws.bind_values_append(List.reverse(&2, S.Bind, bs), List.append(&2, S.Bind, List.reverse(&2, S.Bind, - xs), r1), - name), - Equal.cong(List<&2, String>, List<&2, String>, zs => List.append(&2, String, gb, zs), - S.get_all.bind(List.append(&2, S.Bind, List.reverse(&2, S.Bind, xs), r1), name), - List.append(&2, String, gx, S.get_all.bind(r1, name)), - Laws.bind_values_append(List.reverse(&2, S.Bind, xs), r1, name)))))) - -# the read at the path, and the level's bindings read back -def kept_all.join( - +found: Shake.Matched, - +path: List<&2, String>, - +args: List<&2, S.Arg>, - +xx: List<&2, S.Bind>, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - +name: String, - er: kept_all.read.ty(found, path, args, kept_all.ys(xx, xs, bs), name), - wr: kept_all.fill.ty(args, xx, xs, bs, name) -) -> kept_all.ty(found, path, name, xs, bs): - (ax, ee) = wr - +after = {ax : List<&2, String>} - +zz = S.get_all.bind(List.reverse(&2, S.Bind, - S.fill.args(args, List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs)))), name) - +ww = List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), - List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, xs), name), after)) - (after, Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, name), Some{zz}, Some{ww}, er, - Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, zs => Some{zs}, zz, ww, ee))) - -# a walker grown from where `mid` stayed -def kept_all.fin( - +found: Shake.Matched, - +fs: S.St, - +st1: S.St, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - +name: String, - h_mid: {Grow.view(st1) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} : Grow.View}, - hd: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, - dep: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}, - gg: Grow.gw.Grown(fs, Grow.view(st1)) -) -> kept_all.ty(found, path, name, xs, bs): - (x2, r2) = gg - (q2, r3) = r2 - (m2, r4) = r3 - (_hl, e2) = r4 - +xx = {x2 : List<&2, S.Bind>} - kept_all.join(found, path, args, xx, xs, bs, name, - kept_all.read(found, fs, st1, args, up, path, List.append(&2, S.Bind, xs, bs), xx, q2, m2, name, h_mid, hd, dep, - e2), - kept_all.fill(args, xx, xs, bs, name, - Grow.gw.fill(args, List.append(&2, S.Bind, xx, List.append(&2, S.Bind, xs, bs))))) - -# the walk after `mid` grew the walker, or left it stuck, which no success -# follows -def kept_all.after( - +found: Shake.Matched, - +fs: S.St, - +st1: S.St, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - +name: String, - h_mid: {Grow.view(st1) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} : Grow.View}, - hd: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, - dep: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}, - rr: Grow.gw.Reach(fs, Grow.view(st1)) -) -> kept_all.ty(found, path, name, xs, bs): - match rr: - case Grow.Grew{gg}: - kept_all.fin(found, fs, st1, args, up, path, xs, bs, name, h_mid, hd, dep, gg) - case Grow.Stopped{hs}: - Empty.absurd(kept_all.ty(found, path, name, xs, bs), Grow.gw.live_done(fs, found, hd, hs)) - -# the walker `mid` left has one command above it per name of its path -def kept_all.dep( - +w0: S.St, - +st1: S.St, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +yy: List<&2, S.Bind>, - e_st: {w0 == st1 : S.St}, - h_mid: {Grow.view(st1) == Grow.View{args, up, path, yy} : Grow.View}, - dd: Grow.gw.Deep(w0) -) -> {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat}: - +lit = {Grow.Frame{S.Level{args, yy} <> up, path} : Grow.Frame} - +ef = {Equal.trans(Grow.Frame, Grow.gw.frame(w0), Grow.gw.frame(st1), lit, - Equal.cong(S.St, Grow.Frame, xx => Grow.gw.frame(xx), w0, st1, e_st), - Equal.trans(Grow.Frame, Grow.gw.frame(st1), Grow.gw.vframe(Grow.view(st1)), lit, Grow.gw.frame_view(st1), - Equal.cong(Grow.View, Grow.Frame, vv => Grow.gw.vframe(vv), Grow.view(st1), Grow.View{args, up, path, - yy}, h_mid))) - : {Grow.gw.frame(w0) == lit : Grow.Frame}} - Equal.cong(Nat, Nat, nn => Grow.gw.pred(nn), 1n+List.length(&2, S.Level, up), 1n+List.length(&2, String, path), - Equal.trans(Nat, List.length(&2, S.Level, S.Level{args, yy} <> up), - List.length(&2, S.Level, Grow.gw.flevels(Grow.gw.frame(w0))), 1n+List.length(&2, String, path), - Equal.cong(Grow.Frame, Nat, fr => List.length(&2, S.Level, Grow.gw.flevels(fr)), lit, Grow.gw.frame(w0), - Equal.sym(Grow.Frame, Grow.gw.frame(w0), lit, ef)), - Equal.trans(Nat, List.length(&2, S.Level, Grow.gw.flevels(Grow.gw.frame(w0))), - 1n+List.length(&2, String, Grow.gw.fpath(Grow.gw.frame(w0))), 1n+List.length(&2, String, path), dd, - Equal.cong(Grow.Frame, Nat, fr => 1n+List.length(&2, String, Grow.gw.fpath(fr)), Grow.gw.frame(w0), lit, ef)))) - -# the walk from the start to where `mid` stayed grew the walker, or left it -# stuck, which no success follows -def kept_all.start( - spec: Shake.Cli, - +w0: S.St, - +found: Shake.Matched, - +st1: S.St, - +more: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +xs: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - +name: String, - +e_st: {w0 == st1 : S.St}, - +h_mid: {Grow.view(st1) == Grow.View{args, up, path, List.append(&2, S.Bind, xs, bs)} : Grow.View}, - +hd: {S.parse.finish(S.parse.walk(more, st1)) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>}, - r0: Grow.gw.Reach(w0, Grow.view(S.parse.start(spec))), - r1: Grow.gw.Reach(S.parse.walk(more, st1), Grow.view(st1)) -) -> kept_all.ty(found, path, name, xs, bs): - match spec: - case S.Cli{_n, _a, _v, +sargs, _s}: - match r0: - case Grow.Grew{g0}: - kept_all.after(found, S.parse.walk(more, st1), st1, args, up, path, xs, bs, name, h_mid, hd, - kept_all.dep(w0, st1, args, up, path, List.append(&2, S.Bind, xs, bs), e_st, h_mid, - Grow.gw.depth(w0, sargs, [], g0)), - r1) - case Grow.Stopped{hs}: - Empty.absurd(kept_all.ty(found, path, name, xs, bs), - Grow.gw.live_done(S.parse.walk(more, st1), found, hd, - Grow.gw.stuck_walk(more, st1, - Equal.trans(Bool, Grow.gw.live(st1), Grow.gw.live(w0), False{}, - Equal.cong(S.St, Bool, xx => Grow.gw.live(xx), st1, w0, Equal.sym(S.St, w0, st1, e_st)), hs)))) - -def Laws.kept_all(spec, pre, mid, more, st, args, up, path, xs, bs, name, found, h_at, h_mid, h_done): - +st1 = S.parse.walk(mid, st) - +ww = List.append(&2, String, pre, mid) - +e_st = {Equal.trans(S.St, S.parse.walk(ww, S.parse.start(spec)), - S.parse.walk(mid, S.parse.walk(pre, S.parse.start(spec))), st1, Laws.walk_append(pre, mid, S.parse.start(spec)), - Equal.cong(S.St, S.St, xx => S.parse.walk(mid, xx), S.parse.walk(pre, S.parse.start(spec)), st, h_at)) - : {S.parse.walk(ww, S.parse.start(spec)) == st1 : S.St}} - e1 = Equal.cong(S.St, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.parse.finish(xx), - S.parse.walk(List.append(&2, String, ww, more), S.parse.start(spec)), - S.parse.walk(more, S.parse.walk(ww, S.parse.start(spec))), Laws.walk_append(ww, more, S.parse.start(spec))) - e2 = Equal.cong(S.St, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.parse.finish(S.parse.walk(more, xx)), - S.parse.walk(ww, S.parse.start(spec)), st1, e_st) - +raw = S.parse.finish(S.parse.walk(List.append(&2, String, ww, more), S.parse.start(spec))) - hd = Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, S.parse.finish(S.parse.walk(more, st1)), - raw, Done{found}, - Equal.sym(Result<&2, &2, S.ParseErr, S.Matched>, raw, - S.parse.finish(S.parse.walk(more, st1)), - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, raw, - S.parse.finish(S.parse.walk(more, S.parse.walk(ww, S.parse.start(spec)))), - S.parse.finish(S.parse.walk(more, st1)), e1, e2)), - vr.done(spec, raw, found, h_done)) - kept_all.start(spec, S.parse.walk(ww, S.parse.start(spec)), found, st1, more, args, up, path, - xs, bs, name, e_st, h_mid, hd, Grow.gw.reach(ww, S.parse.start(spec)), Grow.gw.reach(more, st1)) - -# what `kept` claims -def kept.ty(+found: Shake.Matched, +path: List<&2, String>, +name: String, +vv: String, +bs: List<&2, S.Bind>) -> Type: - &after: List<&2, String> -> {Laws.all_at(found, path, name) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), name), vv <> after)} - : Maybe<&2, List<&2, String>>} - -# one binding of the name, read back as its value -def kept.one( - +found: Shake.Matched, - +path: List<&2, String>, - +name: String, - +vv: String, - +bs: List<&2, S.Bind>, - ww: kept_all.ty(found, path, name, [S.Bind{name, vv}], bs) -) -> kept.ty(found, path, name, vv, bs): - (ax, ee) = ww - +after = {ax : List<&2, String>} - +gb = S.get_all.bind(List.reverse(&2, S.Bind, bs), name) - (after, Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, name), - Some{List.append(&2, String, gb, List.append(&2, String, S.get_all.bind.hit(String.eq(name, name), vv, []), - after))}, - Some{List.append(&2, String, gb, vv <> after)}, ee, - Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, - ys => Some{List.append(&2, String, gb, List.append(&2, String, ys, after))}, - S.get_all.bind.hit(String.eq(name, name), vv, []), [vv], kept.hit(name, vv, [])))) - -def Laws.kept(spec, pre, more, tok, st, args, up, path, name, vv, bs, found, h_at, h_step, h_done): - kept.one(found, path, name, vv, bs, - Laws.kept_all(spec, pre, [tok], more, st, args, up, path, [S.Bind{name, vv}], bs, name, found, h_at, h_step, - h_done)) - -# an option's value that is not flag-shaped and is allowed binds -def value.step( - +tok: String, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - h_flag: {S.looks_flag(tok) == False{} : Bool}, - h_ok: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == True{} : Bool} -) -> {S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen} : S.St}: - e1 = Equal.sym(Bool, S.looks_flag(tok), False{}, h_flag) - %e1 : {S.parse.step.need(_, name, tok, args, up, subs, path, bs, pos, raw, seen) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen} : S.St} - e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, name)), tok), True{}, h_ok) - %e2 : {S.parse.take_choice(_, name, tok, args, up, subs, path, bs, pos, raw, seen) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen} : S.St} - {==} - -def Laws.value_binds( - spec, - pre, - more, - tok, - name, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - found, - h_at, - h_flag, - h_ok, - h_done -): - Laws.kept(spec, pre, more, tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, args, up, path, - name, tok, bs, found, h_at, - Equal.cong(S.St, Grow.View, xx => Grow.view(xx), - S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, pos, raw, seen}, - value.step(tok, name, args, up, subs, path, bs, pos, raw, seen, h_flag, h_ok)), - h_done) - -# an allowed value taken by the next positional, through `take_choice` -def pos.take( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +raw: Bool, - +name: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +rest: List<&2, S.Arg>, - h_ok: {S.allowed(cs, tok) == True{} : Bool} -) -> {S.parse.take_pos(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, raw) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), raw, True{}} : S.St}: - e2 = Equal.sym(Bool, S.allowed(cs, tok), True{}, h_ok) - %e2 : {S.parse.take_choice(_, name, tok, args, up, subs, path, bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), raw, True{}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), raw, True{}} : S.St} - {==} - -# a plain word taken by the next positional -def pos.step( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - +name: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +rest: List<&2, S.Arg>, - h_dd: {String.eq(tok, "--") == False{} : Bool}, - h_help: {String.eq(tok, "help") == False{} : Bool}, - +h_dash: {String.starts_with(tok, "-") == False{} : Bool}, - h_sub: {S.find_sub(subs, tok) == None{} : Maybe<&2, S.Sub>}, - h_ok: {S.allowed(cs, tok) == True{} : Bool} -) -> {S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, - S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, seen}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St}: - Equal.trans(S.St, - S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, - False{}, seen}), - S.parse.take_pos(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, - Walk.wk.word_step(tok, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, seen, - h_dd, h_help, h_dash, h_sub), - pos.take(tok, args, up, subs, path, bs, False{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)) - -def Laws.pos_next( - spec, - pre, - tok, - args, - up, - subs, - path, - bs, - seen, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - h_at, - h_dd, - h_help, - h_dash, - h_sub, - h_ok -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, - seen} : S.St} - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), S.parse.step(tok, st), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, - kept.stepped(spec, pre, tok, st, h_at), - pos.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, rest, h_dd, h_help, h_dash, - h_sub, h_ok)) - -def Laws.pos_binds( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - seen, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - found, - h_at, - h_dd, - h_help, - h_dash, - h_sub, - h_ok, - h_done -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, - seen} : S.St} - Laws.kept(spec, pre, more, tok, st, args, up, path, name, tok, bs, found, h_at, - Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step(tok, st), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, - pos.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, rest, h_dd, h_help, - h_dash, h_sub, h_ok)), - h_done) - -def Laws.dd_step(spec, pre, args, up, subs, path, bs, pos, seen, h_at): - kept.stepped(spec, pre, "--", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, h_at) - -def Laws.raw_next( - spec, - pre, - tok, - args, - up, - subs, - path, - bs, - seen, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - h_at, - h_ok -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, True{}, - seen} : S.St} - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), S.parse.step(tok, st), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{}}, - kept.stepped(spec, pre, tok, st, h_at), - pos.take(tok, args, up, subs, path, bs, True{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)) - -def Laws.raw_binds( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - seen, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - found, - h_at, - h_ok, - h_done -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, True{}, - seen} : S.St} - Laws.kept(spec, pre, more, tok, st, args, up, path, name, tok, bs, found, h_at, - Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step(tok, st), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, tok} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{}}, - pos.take(tok, args, up, subs, path, bs, True{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)), - h_done) - -def Laws.raw_no_pos(spec, pre, more, tok, args, up, subs, path, bs, seen, h_at): - Laws.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, [], True{}, seen}, - S.Unexpected{path, tok}, h_at, {==}) - -# after `--`, a word the next positional's choices refuse fails the step -def raw_refused.step( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - +name: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +rest: List<&2, S.Arg>, - h_bad: {S.allowed(cs, tok) == False{} : Bool} -) -> {Laws.dead_of(S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, - req, dflt, cs} <> rest, True{}, seen})) == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>}: - e2 = Equal.sym(Bool, S.allowed(cs, tok), False{}, h_bad) - %e2 : {Laws.dead_of(S.parse.take_choice(_, name, tok, args, up, subs, path, bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), True{}, True{})) - == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.raw_refused( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - seen, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - h_at, - h_bad -): - Laws.refused(spec, pre, more, tok, S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, - dflt, cs} <> rest, True{}, seen}, S.BadValue{path, name, tok}, h_at, - raw_refused.step(tok, args, up, subs, path, bs, seen, name, sh, lo, kind, hp, req, dflt, cs, rest, h_bad)) - -# what `long.kind` claims of an argument kind -def long.kind.ty( - kk: S.ArgKind, - +nn: String, - +vv: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> Type: - {S.parse.take_arg.kind(kk, nn, Some{vv}, orig, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} - -# an option that takes a value now binds an allowed one -def long.kind( - kk: S.ArgKind, - +nn: String, - +vv: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - h_kind: {Laws.takes(kk, S.bound(bs, nn)) == True{} : Bool}, - h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} -) -> long.kind.ty(kk, nn, vv, orig, args, up, subs, path, bs, pos, seen): - match kk: - case S.Opt{}: - e1 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) - %e1 : {S.parse.once(_, S.parse.take_opt(Some{vv}, nn, args, up, subs, path, bs, pos, False{}, seen), nn, path) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} - e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) - %e2 : {S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} - {==} - case S.Many{}: - e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) - %e2 : {S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} - {==} - case S.Flag{}: - Empty.absurd(long.kind.ty(S.Flag{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), - Walk.wk.false_true(h_kind)) - case S.Pos{}: - Empty.absurd(long.kind.ty(S.Pos{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), - Walk.wk.false_true(h_kind)) - case S.Rest{}: - Empty.absurd(long.kind.ty(S.Rest{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), - Walk.wk.false_true(h_kind)) - -# a long option `--name=vv` that takes a value binds an allowed one -def long.step( - +body: String, - +name: String, - +vv: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, - h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>}, - h_name: {String.is_empty(name) == False{} : Bool}, - h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, - h_kind: {Laws.takes(kind, S.bound(bs, nn)) == True{} : Bool}, - h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} -) -> {S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St}: - e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), - S.parse.take_long.found(S.by_long(args, name), name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen), - Laws.long_arg_step(body, name, Some{vv}, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) - %e1 : {_ == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}}, h_found) - %e2 : {S.parse.take_long.found(_, name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen} : S.St} - long.kind(kind, nn, vv, "--" ++ body, args, up, subs, path, bs, pos, seen, h_kind, h_ok) - -def Laws.long_binds( - spec, - pre, - more, - body, - name, - vv, - args, - up, - subs, - path, - bs, - pos, - seen, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - found, - h_at, - h_dd, - h_cut, - h_name, - h_found, - h_kind, - h_ok, - h_done -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - Laws.kept(spec, pre, more, "--" ++ body, st, args, up, path, nn, vv, bs, found, h_at, - Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step("--" ++ body, st), - S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, False{}, seen}, - long.step(body, name, vv, args, up, subs, path, bs, pos, seen, nn, sh, lo, kind, hp, req, dflt, cs, h_dd, - h_cut, h_name, h_found, h_kind, h_ok)), - h_done) - -# an option's value that is not flag-shaped and is not allowed fails the step -def value_refused.step( - +tok: String, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - h_flag: {S.looks_flag(tok) == False{} : Bool}, - h_bad: {S.allowed(S.parse.choices(S.by_name(args, name)), tok) == False{} : Bool} -) -> {Laws.dead_of(S.parse.step(tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen})) - == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(Bool, S.looks_flag(tok), False{}, h_flag) - %e1 : {Laws.dead_of(S.parse.step.need(_, name, tok, args, up, subs, path, bs, pos, raw, seen)) - == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, name)), tok), False{}, h_bad) - %e2 : {Laws.dead_of(S.parse.take_choice(_, name, tok, args, up, subs, path, bs, pos, raw, seen)) - == Some{S.BadValue{path, name, tok}} : Maybe<&2, S.ParseErr>} - {==} - -def Laws.value_refused(spec, pre, more, tok, name, args, up, subs, path, bs, pos, raw, seen, h_at, h_flag, h_bad): - Laws.refused(spec, pre, more, tok, S.St{S.Need{name}, args, up, subs, path, bs, pos, raw, seen}, - S.BadValue{path, name, tok}, h_at, - value_refused.step(tok, name, args, up, subs, path, bs, pos, raw, seen, h_flag, h_bad)) - -# what `long_refused.kind` claims of an argument kind -def long_refused.kind.ty( - kk: S.ArgKind, - +nn: String, - +vv: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> Type: - {Laws.dead_of(S.parse.take_arg.kind(kk, nn, Some{vv}, orig, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} - -# an option that takes a value now refuses one outside its choices -def long_refused.kind( - kk: S.ArgKind, - +nn: String, - +vv: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - h_kind: {Laws.takes(kk, S.bound(bs, nn)) == True{} : Bool}, - h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == False{} : Bool} -) -> long_refused.kind.ty(kk, nn, vv, orig, args, up, subs, path, bs, pos, seen): - match kk: - case S.Opt{}: - e1 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) - %e1 : {Laws.dead_of(S.parse.once(_, S.parse.take_opt(Some{vv}, nn, args, up, subs, path, bs, pos, False{}, - seen), nn, path)) == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), False{}, h_bad) - %e2 : {Laws.dead_of(S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} - {==} - case S.Many{}: - e2 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), False{}, h_bad) - %e2 : {Laws.dead_of(S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} - {==} - case S.Flag{}: - Empty.absurd(long_refused.kind.ty(S.Flag{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), - Walk.wk.false_true(h_kind)) - case S.Pos{}: - Empty.absurd(long_refused.kind.ty(S.Pos{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), - Walk.wk.false_true(h_kind)) - case S.Rest{}: - Empty.absurd(long_refused.kind.ty(S.Rest{}, nn, vv, orig, args, up, subs, path, bs, pos, seen), - Walk.wk.false_true(h_kind)) - -# a long option `--name=vv` that takes a value refuses one outside its choices -def long_refused.step( - +body: String, - +name: String, - +vv: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - h_dd: {String.eq("--" ++ body, "--") == False{} : Bool}, - h_cut: {S.cut_eq(body) == (name, Some{vv}) : String & Maybe<&2, String>}, - h_name: {String.is_empty(name) == False{} : Bool}, - h_found: {S.by_long(args, name) == Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}} : Maybe<&2, S.Arg>}, - h_kind: {Laws.takes(kind, S.bound(bs, nn)) == True{} : Bool}, - h_bad: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == False{} : Bool} -) -> {Laws.dead_of(S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen})) - == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>}: - e1 = Equal.sym(S.St, S.parse.step("--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}), - S.parse.take_long.found(S.by_long(args, name), name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen), - Laws.long_arg_step(body, name, Some{vv}, args, up, subs, path, bs, pos, seen, h_dd, h_cut, h_name)) - %e1 : {Laws.dead_of(_) == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, name), Some{S.Arg{nn, sh, lo, kind, hp, req, dflt, cs}}, h_found) - %e2 : {Laws.dead_of(S.parse.take_long.found(_, name, Some{vv}, "--" ++ body, args, up, subs, path, bs, pos, False{}, - seen)) - == Some{S.BadValue{path, nn, vv}} : Maybe<&2, S.ParseErr>} - long_refused.kind(kind, nn, vv, "--" ++ body, args, up, subs, path, bs, pos, seen, h_kind, h_bad) - -def Laws.long_refused( - spec, - pre, - more, - body, - name, - vv, - args, - up, - subs, - path, - bs, - pos, - seen, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - h_at, - h_dd, - h_cut, - h_name, - h_found, - h_kind, - h_bad -): - Laws.refused(spec, pre, more, "--" ++ body, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}, - S.BadValue{path, nn, vv}, h_at, - long_refused.step(body, name, vv, args, up, subs, path, bs, pos, seen, nn, sh, lo, kind, hp, req, dflt, cs, - h_dd, h_cut, h_name, h_found, h_kind, h_bad)) - -# a string read back from its chars is itself -def cut.from_to(ss: String) -> {String.from_list(String.to_list(ss)) == ss : String}: - match ss: - case SNil{}: - {==} - case SCon{+hh, tt}: - Equal.cong(String, String, xx => SCon{hh, xx}, String.from_list(String.to_list(tt)), tt, cut.from_to(tt)) - -# appending two lists, then a third, is appending the first to the rest -def cut.app( - +aa: List<&2, Char>, - +bb: List<&2, Char>, - +cc: List<&2, Char> -) -> {List.append(&2, Char, List.append(&2, Char, aa, bb), cc) - == List.append(&2, Char, aa, List.append(&2, Char, bb, cc)) : List<&2, Char>}: - match aa: - case Nil{}: - {==} - case Con{+hh, +tt}: - Equal.cong(List<&2, Char>, List<&2, Char>, xs => hh <> xs, - List.append(&2, Char, List.append(&2, Char, tt, bb), cc), - List.append(&2, Char, tt, List.append(&2, Char, bb, cc)), cut.app(tt, bb, cc)) - -# reversing onto a list is reversing, then appending it -def cut.rev_acc( - xs: List<&2, Char>, - +acc: List<&2, Char> -) -> {List.reverse.go(&2, Char, xs, acc) - == List.append(&2, Char, List.reverse.go(&2, Char, xs, []), acc) : List<&2, Char>}: - match xs: - case Nil{}: - {==} - case Con{+hh, +tt}: - Equal.trans(List<&2, Char>, List.reverse.go(&2, Char, tt, hh <> acc), - List.append(&2, Char, List.reverse.go(&2, Char, tt, []), hh <> acc), - List.append(&2, Char, List.reverse.go(&2, Char, tt, [hh]), acc), - cut.rev_acc(tt, hh <> acc), - Equal.sym(List<&2, Char>, List.append(&2, Char, List.reverse.go(&2, Char, tt, [hh]), acc), - List.append(&2, Char, List.reverse.go(&2, Char, tt, []), hh <> acc), - Equal.trans(List<&2, Char>, - List.append(&2, Char, List.reverse.go(&2, Char, tt, [hh]), acc), - List.append(&2, Char, List.append(&2, Char, List.reverse.go(&2, Char, tt, []), [hh]), - acc), - List.append(&2, Char, List.reverse.go(&2, Char, tt, []), hh <> acc), - Equal.cong(List<&2, Char>, List<&2, Char>, ys => List.append(&2, Char, ys, acc), - List.reverse.go(&2, Char, tt, [hh]), - List.append(&2, Char, List.reverse.go(&2, Char, tt, []), [hh]), cut.rev_acc(tt, [hh])), - cut.app(List.reverse.go(&2, Char, tt, []), [hh], acc)))) - -# a char after a buffer is that char in front of the buffer folded onto acc -def cut.snoc( - xs: List<&2, Char>, - +hh: Char, - +acc: String -) -> {S.from.rev.go(List.append(&2, Char, xs, [hh]), acc) == SCon{hh, S.from.rev.go(xs, acc)} : String}: - match xs: - case Nil{}: - {==} - case Con{+xx, tt}: - cut.snoc(tt, hh, SCon{xx, acc}) - -# folding a string's chars, reversed, onto acc is the string in front of acc -def cut.fold( - ss: String, - +acc: String -) -> {S.from.rev.go(List.reverse.go(&2, Char, String.to_list(ss), []), acc) == String.append(ss, acc) : String}: - match ss: - case SNil{}: - {==} - case SCon{+hh, +tt}: - +rev = List.reverse.go(&2, Char, String.to_list(tt), []) - Equal.trans(String, - S.from.rev.go(List.reverse.go(&2, Char, String.to_list(tt), [hh]), acc), - S.from.rev.go(List.append(&2, Char, rev, [hh]), acc), - String.append(SCon{hh, tt}, acc), - Equal.cong(List<&2, Char>, String, xs => S.from.rev.go(xs, acc), - List.reverse.go(&2, Char, String.to_list(tt), [hh]), - List.append(&2, Char, rev, [hh]), - cut.rev_acc(String.to_list(tt), [hh])), - Equal.trans(String, - S.from.rev.go(List.append(&2, Char, rev, [hh]), acc), - SCon{hh, S.from.rev.go(rev, acc)}, - SCon{hh, String.append(tt, acc)}, - cut.snoc(rev, hh, acc), - Equal.cong(String, String, xx => SCon{hh, xx}, - S.from.rev.go(rev, acc), String.append(tt, acc), cut.fold(tt, acc)))) - -# a string in front of nothing is the string -def cut.app_nil(ss: String) -> {String.append(ss, SNil{}) == ss : String}: - match ss: - case SNil{}: - {==} - case SCon{+hh, tt}: - Equal.cong(String, String, xx => SCon{hh, xx}, String.append(tt, SNil{}), tt, cut.app_nil(tt)) - -# a char a string with no `=` starts with is not `=` -def cut.head( - +cc: Char, - +tt: String, - h_name: {Laws.no_eq(SCon{cc, tt}) == True{} : Bool} -) -> {Char.is_eq(cc, '=') == False{} : Bool}: - Walk.wk.not_true(Char.is_eq(cc, '='), Walk.wk.and_l(Bool.not(Char.is_eq(cc, '=')), Laws.no_eq(tt), h_name)) - -# the rest of a string with no `=` has none either -def cut.tail( - +cc: Char, - +tt: String, - h_name: {Laws.no_eq(SCon{cc, tt}) == True{} : Bool} -) -> {Laws.no_eq(tt) == True{} : Bool}: - Walk.wk.and_r(Bool.not(Char.is_eq(cc, '=')), Laws.no_eq(tt), h_name) - -# the cut, char by char: every char before the first `=` goes onto the -# accumulator, and the `=` ends the name -def cut.go_eq( - name: String, - +vv: String, - +acc: List<&2, Char>, - +h_name: {Laws.no_eq(name) == True{} : Bool} -) -> {S.cut_eq.go(String.to_list(name ++ ("=" ++ vv)), acc) - == (S.from.rev(List.reverse.go(&2, Char, String.to_list(name), acc)), Some{String.from_list(String.to_list(vv))}) - : String & Maybe<&2, String>}: - match name: - case SNil{}: - {==} - case SCon{+cc, +tt}: - +rest = String.to_list(tt ++ ("=" ++ vv)) - ee = Equal.sym(Bool, Char.is_eq(cc, '='), False{}, cut.head(cc, tt, h_name)) - %ee : {S.cut_eq.pick(_, acc, rest, S.cut_eq.go(rest, cc <> acc)) - == (S.from.rev(List.reverse.go(&2, Char, String.to_list(tt), cc <> acc)), - Some{String.from_list(String.to_list(vv))}) : String & Maybe<&2, String>} - cut.go_eq(tt, vv, cc <> acc, cut.tail(cc, tt, h_name)) - -# the cut, char by char, of a string with no `=` -def cut.go_none( - name: String, - +acc: List<&2, Char>, - +h_name: {Laws.no_eq(name) == True{} : Bool} -) -> {S.cut_eq.go(String.to_list(name), acc) - == (S.from.rev(List.reverse.go(&2, Char, String.to_list(name), acc)), None{}) : String & Maybe<&2, String>}: - match name: - case SNil{}: - {==} - case SCon{+cc, +tt}: - +rest = String.to_list(tt) - ee = Equal.sym(Bool, Char.is_eq(cc, '='), False{}, cut.head(cc, tt, h_name)) - %ee : {S.cut_eq.pick(_, acc, rest, S.cut_eq.go(rest, cc <> acc)) - == (S.from.rev(List.reverse.go(&2, Char, String.to_list(tt), cc <> acc)), None{}) - : String & Maybe<&2, String>} - cut.go_none(tt, cc <> acc, cut.tail(cc, tt, h_name)) - -# the name the cut reads back is the name -def cut.name(+name: String) -> {S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])) == name : String}: - Equal.trans(String, S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])), - String.append(name, SNil{}), name, cut.fold(name, SNil{}), cut.app_nil(name)) - -def Laws.cut_first_eq(name, vv, h_name): - +nm = S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])) - +vl = String.from_list(String.to_list(vv)) - Equal.trans(String & Maybe<&2, String>, S.cut_eq(name ++ ("=" ++ vv)), (nm, Some{vl}), (name, Some{vv}), - cut.go_eq(name, vv, [], h_name), - Equal.trans(String & Maybe<&2, String>, (nm, Some{vl}), (name, Some{vl}), (name, Some{vv}), - Equal.cong(String, String & Maybe<&2, String>, xx => (xx, Some{vl}), nm, name, cut.name(name)), - Equal.cong(String, String & Maybe<&2, String>, xx => (name, Some{xx}), vl, vv, cut.from_to(vv)))) - -def Laws.cut_no_eq(name, h_name): - +nm = S.from.rev(List.reverse.go(&2, Char, String.to_list(name), [])) - Equal.trans(String & Maybe<&2, String>, S.cut_eq(name), (nm, None{}), (name, None{}), - cut.go_none(name, [], h_name), - Equal.cong(String, String & Maybe<&2, String>, xx => (xx, None{}), nm, name, cut.name(name))) - -# a rest positional, as the walker holds it -def rest.arg( - +nm: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String> -) -> S.Arg: - S.Arg{nm, sh, lo, S.Rest{}, hp, req, dflt, cs} - -# before `--`, plain words bind the rest one by one -def rest.walk( - fs: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - +nm: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +h_plain: {Walk.all_plain(fs) == True{} : Bool}, - +h_sub: {Walk.wk.unfound(subs, fs) == True{} : Bool}, - +h_ok: {Laws.all_allowed(cs, fs) == True{} : Bool} -) -> {S.parse.walk(fs, S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, - seen}) - == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], - False{}, Walk.wk.seen(fs, seen)} : S.St}: - match fs: - case Nil{}: - {==} - case Con{+ww, +tl}: - +pl = Walk.wk.and_l(Walk.plain_word(ww), Walk.all_plain(tl), h_plain) - su = Walk.wk.and_l(Maybe.is_none(&2, S.Sub, S.find_sub(subs, ww)), Walk.wk.unfound(subs, tl), h_sub) - ok = Walk.wk.and_l(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok) - ee = Equal.sym(S.St, - S.parse.step(ww, S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, - seen}), - S.St{S.Free{}, args, up, subs, path, S.Bind{nm, ww} <> bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, - True{}}, - pos.step(ww, args, up, subs, path, bs, seen, nm, sh, lo, S.Rest{}, hp, req, dflt, cs, [], - Walk.wk.plain_dd(ww, pl), Walk.wk.plain_help(ww, pl), Walk.wk.plain_dash(ww, pl), - Walk.wk.none_of(S.find_sub(subs, ww), su), ok)) - %ee : {S.parse.walk(tl, _) - == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(tl, nm, S.Bind{nm, ww} <> bs), - [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, Walk.wk.seen(tl, True{})} : S.St} - rest.walk(tl, args, up, subs, path, S.Bind{nm, ww} <> bs, True{}, nm, sh, lo, hp, req, dflt, cs, - Walk.wk.and_r(Walk.plain_word(ww), Walk.all_plain(tl), h_plain), - Walk.wk.and_r(Maybe.is_none(&2, S.Sub, S.find_sub(subs, ww)), Walk.wk.unfound(subs, tl), h_sub), - Walk.wk.and_r(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok)) - -# after `--`, every word binds the rest one by one -def rest.raw( - fs: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - +nm: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +h_ok: {Laws.all_allowed(cs, fs) == True{} : Bool} -) -> {S.parse.walk(fs, S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, - seen}) - == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], - True{}, Walk.wk.seen(fs, seen)} : S.St}: - match fs: - case Nil{}: - {==} - case Con{+ww, +tl}: - ee = Equal.sym(S.St, - S.parse.take_pos(ww, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}), - S.St{S.Free{}, args, up, subs, path, S.Bind{nm, ww} <> bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, - True{}}, - pos.take(ww, args, up, subs, path, bs, True{}, nm, sh, lo, S.Rest{}, hp, req, dflt, cs, [], - Walk.wk.and_l(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok))) - %ee : {S.parse.walk(tl, _) - == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(tl, nm, S.Bind{nm, ww} <> bs), - [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, Walk.wk.seen(tl, True{})} : S.St} - rest.raw(tl, args, up, subs, path, S.Bind{nm, ww} <> bs, True{}, nm, sh, lo, hp, req, dflt, cs, - Walk.wk.and_r(S.allowed(cs, ww), Laws.all_allowed(cs, tl), h_ok)) - -# the words before `fs`, then `fs`, from where the words before left the walker -def rest.after( - +spec: Shake.Cli, - +pre: List<&2, String>, - +fs: List<&2, String>, - +st: S.St, - h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St} -) -> {S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)) == S.parse.walk(fs, st) : S.St}: - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)), - S.parse.walk(fs, S.parse.walk(pre, S.parse.start(spec))), S.parse.walk(fs, st), - Laws.walk_append(pre, fs, S.parse.start(spec)), - Equal.cong(S.St, S.St, xx => S.parse.walk(fs, xx), S.parse.walk(pre, S.parse.start(spec)), st, h_at)) - -def Laws.rest_next( - spec, - pre, - fs, - args, - up, - subs, - path, - bs, - seen, - nm, - sh, - lo, - hp, - req, - dflt, - cs, - h_at, - h_plain, - h_sub, - h_ok -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, seen} : S.St} - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)), S.parse.walk(fs, st), - S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, - Walk.wk.seen(fs, seen)}, - rest.after(spec, pre, fs, st, h_at), - rest.walk(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_plain, h_sub, h_ok)) - -def Laws.rest_raw_next(spec, pre, fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_at, h_ok): - +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, seen} : S.St} - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, fs), S.parse.start(spec)), S.parse.walk(fs, st), - S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, - Walk.wk.seen(fs, seen)}, - rest.after(spec, pre, fs, st, h_at), - rest.raw(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_ok)) - -# the bindings a walk pushes onto some it had are those it pushes onto none, -# then those -def rest.pushed( - fs: List<&2, String>, - +nm: String, - +acc: List<&2, S.Bind>, - +bs: List<&2, S.Bind> -) -> {Walk.wk.pushed(fs, nm, List.append(&2, S.Bind, acc, bs)) - == List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, acc), bs) : List<&2, S.Bind>}: - match fs: - case Nil{}: - {==} - case Con{+ww, tl}: - rest.pushed(tl, nm, S.Bind{nm, ww} <> acc, bs) - -# one binding of `nm` per word, in order -def rest.bs(fs: List<&2, String>, +nm: String) -> List<&2, S.Bind>: - match fs: - case Nil{}: - [] - case Con{+ww, tl}: - S.Bind{nm, ww} <> rest.bs(tl, nm) - -# the pushed bindings, reversed onto a list, are one per word in order -def rest.rev( - fs: List<&2, String>, - +nm: String, - +acc: List<&2, S.Bind>, - +out: List<&2, S.Bind> -) -> {List.reverse.go(&2, S.Bind, Walk.wk.pushed(fs, nm, acc), out) - == List.reverse.go(&2, S.Bind, acc, List.append(&2, S.Bind, rest.bs(fs, nm), out)) : List<&2, S.Bind>}: - match fs: - case Nil{}: - {==} - case Con{+ww, tl}: - rest.rev(tl, nm, S.Bind{nm, ww} <> acc, out) - -# bindings with nothing after them -def rest.nil_b(bs: List<&2, S.Bind>) -> {List.append(&2, S.Bind, bs, []) == bs : List<&2, S.Bind>}: - match bs: - case Nil{}: - {==} - case Con{+hh, tt}: - Equal.cong(List<&2, S.Bind>, List<&2, S.Bind>, xs => hh <> xs, List.append(&2, S.Bind, tt, - []), tt, rest.nil_b(tt)) - -# one binding of `nm` per word reads back the words -def rest.vals(fs: List<&2, String>, +nm: String) -> {S.get_all.bind(rest.bs(fs, nm), nm) == fs : List<&2, String>}: - match fs: - case Nil{}: - {==} - case Con{+ww, +tl}: - ee = Equal.sym(Bool, String.eq(nm, nm), True{}, Eq.string_eq_self(nm)) - %ee : {S.get_all.bind.hit(_, ww, S.get_all.bind(rest.bs(tl, nm), nm)) == ww <> tl : List<&2, String>} - Equal.cong(List<&2, String>, List<&2, String>, xs => ww <> xs, S.get_all.bind(rest.bs(tl, nm), nm), tl, - rest.vals(tl, nm)) - -# the rest's values the words pushed, read back in order, are the words -def rest.read( - +fs: List<&2, String>, - +nm: String -) -> {S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm) == fs : List<&2, String>}: - Equal.trans(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm), - S.get_all.bind(rest.bs(fs, nm), nm), fs, - Equal.cong(List<&2, S.Bind>, List<&2, String>, xs => S.get_all.bind(xs, nm), - List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), rest.bs(fs, nm), - Equal.trans(List<&2, S.Bind>, List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), - List.append(&2, S.Bind, rest.bs(fs, nm), []), rest.bs(fs, nm), rest.rev(fs, nm, [], []), - rest.nil_b(rest.bs(fs, nm)))), - rest.vals(fs, nm)) - -# what `rest_binds` claims -def rest.ty( - +found: Shake.Matched, - +path: List<&2, String>, - +nm: String, - +fs: List<&2, String>, - +bs: List<&2, S.Bind> -) -> Type: - &after: List<&2, String> -> {Laws.all_at(found, path, nm) - == Some{List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, bs), nm), List.append(&2, String, - fs, after))} - : Maybe<&2, List<&2, String>>} - -# the pushed bindings read back as the words -def rest.back( - +found: Shake.Matched, - +path: List<&2, String>, - +nm: String, - +fs: List<&2, String>, - +bs: List<&2, S.Bind>, - ww: kept_all.ty(found, path, nm, Walk.wk.pushed(fs, nm, []), bs) -) -> rest.ty(found, path, nm, fs, bs): - (ax, ee) = ww - +after = {ax : List<&2, String>} - +gb = S.get_all.bind(List.reverse(&2, S.Bind, bs), nm) - (after, Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, nm), - Some{List.append(&2, String, gb, - List.append(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm), after))}, - Some{List.append(&2, String, gb, List.append(&2, String, fs, after))}, ee, - Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, - xs => Some{List.append(&2, String, gb, List.append(&2, String, xs, after))}, - S.get_all.bind(List.reverse(&2, S.Bind, Walk.wk.pushed(fs, nm, [])), nm), fs, rest.read(fs, nm)))) - -# the view after the words, from the walker's state after them -def rest.view( - +fs: List<&2, String>, - +st: S.St, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +nm: String, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - ew: {S.parse.walk(fs, st) == S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), pos, raw, seen} : S.St} -) -> {Grow.view(S.parse.walk(fs, st)) - == Grow.View{args, up, path, List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, []), bs)} : Grow.View}: - Equal.trans(Grow.View, Grow.view(S.parse.walk(fs, st)), Grow.View{args, up, path, Walk.wk.pushed(fs, nm, bs)}, - Grow.View{args, up, path, List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, []), bs)}, - Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.walk(fs, st), - S.St{S.Free{}, args, up, subs, path, Walk.wk.pushed(fs, nm, bs), pos, raw, seen}, ew), - Equal.cong(List<&2, S.Bind>, Grow.View, xs => Grow.View{args, up, path, xs}, Walk.wk.pushed(fs, nm, bs), - List.append(&2, S.Bind, Walk.wk.pushed(fs, nm, []), bs), rest.pushed(fs, nm, [], bs))) - -def Laws.rest_binds( - spec, - pre, - fs, - more, - args, - up, - subs, - path, - bs, - seen, - nm, - sh, - lo, - hp, - req, - dflt, - cs, - found, - h_at, - h_plain, - h_sub, - h_ok, - h_done -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, seen} : S.St} - rest.back(found, path, nm, fs, bs, - Laws.kept_all(spec, pre, fs, more, st, args, up, path, Walk.wk.pushed(fs, nm, []), bs, nm, found, h_at, - rest.view(fs, st, args, up, subs, path, bs, nm, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], False{}, - Walk.wk.seen(fs, seen), - rest.walk(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_plain, h_sub, h_ok)), - h_done)) - -def Laws.rest_raw_binds( - spec, - pre, - fs, - more, - args, - up, - subs, - path, - bs, - seen, - nm, - sh, - lo, - hp, - req, - dflt, - cs, - found, - h_at, - h_ok, - h_done -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, seen} : S.St} - rest.back(found, path, nm, fs, bs, - Laws.kept_all(spec, pre, fs, more, st, args, up, path, Walk.wk.pushed(fs, nm, []), bs, nm, found, h_at, - rest.view(fs, st, args, up, subs, path, bs, nm, [rest.arg(nm, sh, lo, hp, req, dflt, cs)], True{}, - Walk.wk.seen(fs, seen), rest.raw(fs, args, up, subs, path, bs, seen, nm, sh, lo, hp, req, dflt, cs, h_ok)), - h_done)) - -# the two orders a binding's value can be reversed onto a list in agree -def fin.hit_rev( - hit: Bool, - +vv: String, - +xs: List<&2, String>, - +acc: List<&2, String> -) -> {List.reverse.go(&2, String, xs, S.get_all.bind.hit(hit, vv, acc)) - == List.reverse.go(&2, String, S.get_all.bind.hit(hit, vv, xs), acc) : List<&2, String>}: - match hit: - case True{}: - {==} - case False{}: - {==} - -# `get_all` of bindings reversed onto a list is its values reversed onto that -# list's values -def fin.g_rev_go( - xs: List<&2, S.Bind>, - +acc: List<&2, S.Bind>, - +nn: String -) -> {S.get_all.bind(List.reverse.go(&2, S.Bind, xs, acc), nn) - == List.reverse.go(&2, String, S.get_all.bind(xs, nn), S.get_all.bind(acc, nn)) : List<&2, String>}: - match xs: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Bind{+mm, +vv}: - Equal.trans(List<&2, String>, - S.get_all.bind(List.reverse.go(&2, S.Bind, tt, S.Bind{mm, vv} <> acc), nn), - List.reverse.go(&2, String, S.get_all.bind(tt, nn), - S.get_all.bind.hit(String.eq(mm, nn), vv, S.get_all.bind(acc, nn))), - List.reverse.go(&2, String, S.get_all.bind.hit(String.eq(mm, nn), vv, S.get_all.bind(tt, nn)), - S.get_all.bind(acc, nn)), - fin.g_rev_go(tt, S.Bind{mm, vv} <> acc, nn), - fin.hit_rev(String.eq(mm, nn), vv, S.get_all.bind(tt, nn), S.get_all.bind(acc, nn))) - -# `get_all` of reversed bindings is their values reversed -def fin.g_rev( - +xs: List<&2, S.Bind>, - +nn: String -) -> {S.get_all.bind(List.reverse(&2, S.Bind, xs), nn) == List.reverse(&2, String, S.get_all.bind(xs, nn)) - : List<&2, String>}: - fin.g_rev_go(xs, [], nn) - -# a bound name has values, and an unbound one none -def fin.bound_g.hit( - hit: Bool, - +vv: String, - +rest: Bool, - +vals: List<&2, String>, - ih: {rest == Bool.not(List.is_empty(&2, String, vals)) : Bool} -) -> {S.bound.at(hit, rest) == Bool.not(List.is_empty(&2, String, S.get_all.bind.hit(hit, vv, vals))) : Bool}: - match hit: - case True{}: - {==} - case False{}: - ih - -# whether a name is bound is whether it has values -def fin.bound_g( - xs: List<&2, S.Bind>, - +nn: String -) -> {S.bound(xs, nn) == Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))) : Bool}: - match xs: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Bind{+mm, +vv}: - fin.bound_g.hit(String.eq(mm, nn), vv, S.bound(tt, nn), S.get_all.bind(tt, nn), fin.bound_g(tt, nn)) - -# a list that is not nonempty is empty -def fin.empty_of( - xs: List<&2, String>, - hh: {Bool.not(List.is_empty(&2, String, xs)) == False{} : Bool} -) -> {xs == [] : List<&2, String>}: - match xs: - case Nil{}: - {==} - case Con{+x, +t}: - Empty.absurd({x <> t == [] : List<&2, String>}, Walk.wk.true_false(hh)) - -# an unbound name has no values -def fin.g_unbound( - +xs: List<&2, S.Bind>, - +nn: String, - hh: {S.bound(xs, nn) == False{} : Bool} -) -> {S.get_all.bind(xs, nn) == [] : List<&2, String>}: - fin.empty_of(S.get_all.bind(xs, nn), - Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))), S.bound(xs, nn), False{}, - Equal.sym(Bool, S.bound(xs, nn), Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))), - fin.bound_g(xs, nn)), hh)) - -# anything and False is False -def fin.and_false(bb: Bool) -> {Bool.and(bb, False{}) == False{} : Bool}: - match bb: - case True{}: - {==} - case False{}: - {==} - -# what `fin.empty_rev` says after one char -def fin.empty_rev.ih(+tt: List<&2, String>, +hh: String, +acc: List<&2, String>) -> Type: - {List.is_empty(&2, String, List.reverse.go(&2, String, tt, hh <> acc)) - == Bool.and(List.is_empty(&2, String, tt), False{}) : Bool} - -# a nonempty accumulator keeps the reversal nonempty -def fin.empty_rev.cons( - +tt: List<&2, String>, - +hh: String, - +acc: List<&2, String>, - ih: fin.empty_rev.ih(tt, hh, acc) -) -> {List.is_empty(&2, String, List.reverse.go(&2, String, tt, hh <> acc)) == False{} : Bool}: - Equal.trans(Bool, List.is_empty(&2, String, List.reverse.go(&2, String, tt, hh <> acc)), - Bool.and(List.is_empty(&2, String, tt), False{}), False{}, ih, fin.and_false(List.is_empty(&2, String, tt))) - -# anything and True is itself -def fin.and_true(bb: Bool) -> {Bool.and(bb, True{}) == bb : Bool}: - match bb: - case True{}: - {==} - case False{}: - {==} - -# a list reversed onto another is empty exactly when both are -def fin.empty_rev( - xs: List<&2, String>, - +acc: List<&2, String> -) -> {List.is_empty(&2, String, List.reverse.go(&2, String, xs, acc)) - == Bool.and(List.is_empty(&2, String, xs), List.is_empty(&2, String, acc)) : Bool}: - match xs: - case Nil{}: - match acc: - case Nil{}: - {==} - case Con{_x, _t}: - {==} - case Con{+hh, +tt}: - fin.empty_rev.cons(tt, hh, acc, fin.empty_rev(tt, hh <> acc)) - -# a reversed list is empty exactly when the list is -def fin.empty_rev0( - +xs: List<&2, String> -) -> {List.is_empty(&2, String, List.reverse(&2, String, xs)) - == List.is_empty(&2, String, xs) : Bool}: - Equal.trans(Bool, List.is_empty(&2, String, List.reverse(&2, String, xs)), - Bool.and(List.is_empty(&2, String, xs), True{}), List.is_empty(&2, String, xs), fin.empty_rev(xs, []), - fin.and_true(List.is_empty(&2, String, xs))) - -# a default of another name adds nothing to `nn`'s values, nor binds it -def fin.one_other( - skip: Bool, - +mm: String, - +vv: String, - +ff: List<&2, S.Bind>, - +nn: String, - h_ne: {String.eq(mm, nn) == False{} : Bool} -) -> {S.get_all.bind(S.fill.one(skip, mm, vv, ff), nn) == S.get_all.bind(ff, nn) : List<&2, String>}: - match skip: - case True{}: - {==} - case False{}: - ee = Equal.sym(Bool, String.eq(mm, nn), False{}, h_ne) - %ee : {S.get_all.bind.hit(_, vv, S.get_all.bind(ff, nn)) == S.get_all.bind(ff, nn) : List<&2, String>} - {==} - -# the same, for whether `nn` is bound -def fin.one_other_b( - skip: Bool, - +mm: String, - +vv: String, - +ff: List<&2, S.Bind>, - +nn: String, - h_ne: {String.eq(mm, nn) == False{} : Bool} -) -> {S.bound(S.fill.one(skip, mm, vv, ff), nn) == S.bound(ff, nn) : Bool}: - match skip: - case True{}: - {==} - case False{}: - ee = Equal.sym(Bool, String.eq(mm, nn), False{}, h_ne) - %ee : {S.bound.at(_, S.bound(ff, nn)) == S.bound(ff, nn) : Bool} - {==} - -# an argument of another name, with or without a default -def fin.go_other( - dd: Maybe<&2, String>, - +mm: String, - +ff: List<&2, S.Bind>, - +nn: String, - +h_ne: {String.eq(mm, nn) == False{} : Bool} -) -> {S.get_all.bind(S.fill.arg.go(dd, mm, ff), nn) == S.get_all.bind(ff, nn) : List<&2, String>}: - match dd: - case None{}: - {==} - case Some{+vv}: - fin.one_other(S.bound(ff, mm), mm, vv, ff, nn, h_ne) - -# the same, for whether `nn` is bound -def fin.go_other_b( - dd: Maybe<&2, String>, - +mm: String, - +ff: List<&2, S.Bind>, - +nn: String, - +h_ne: {String.eq(mm, nn) == False{} : Bool} -) -> {S.bound(S.fill.arg.go(dd, mm, ff), nn) == S.bound(ff, nn) : Bool}: - match dd: - case None{}: - {==} - case Some{+vv}: - fin.one_other_b(S.bound(ff, mm), mm, vv, ff, nn, h_ne) - -# the head of a list with no argument named `nn` is not named `nn` -def fin.named_head( - +mm: String, - +tt: List<&2, S.Arg>, - +nn: String, - +h_none: {Bool.and(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn)) == True{} : Bool} -) -> {String.eq(mm, nn) == False{} : Bool}: - Walk.wk.not_true(String.eq(mm, nn), Walk.wk.and_l(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn), h_none)) - -# the rest of a list with no argument named `nn` has none -def fin.named_tail( - +mm: String, - +tt: List<&2, S.Arg>, - +nn: String, - +h_none: {Bool.and(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn)) == True{} : Bool} -) -> {Laws.none_named(tt, nn) == True{} : Bool}: - Walk.wk.and_r(Bool.not(String.eq(mm, nn)), Laws.none_named(tt, nn), h_none) - -# filling the defaults of arguments none named `nn` changes none of its values -def fin.fill_other( - args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +nn: String, - +h_none: {Laws.none_named(args, nn) == True{} : Bool} -) -> {S.get_all.bind(S.fill.args(args, bs), nn) == S.get_all.bind(bs, nn) : List<&2, String>}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+mm, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c}: - Equal.trans(List<&2, String>, S.get_all.bind(S.fill.arg.go(dd, mm, S.fill.args(tt, bs)), nn), - S.get_all.bind(S.fill.args(tt, bs), nn), S.get_all.bind(bs, nn), - fin.go_other(dd, mm, S.fill.args(tt, bs), nn, fin.named_head(mm, tt, nn, h_none)), - fin.fill_other(tt, bs, nn, fin.named_tail(mm, tt, nn, h_none))) - -# the same, for whether `nn` is bound -def fin.fill_other_b( - args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +nn: String, - +h_none: {Laws.none_named(args, nn) == True{} : Bool} -) -> {S.bound(S.fill.args(args, bs), nn) == S.bound(bs, nn) : Bool}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+mm, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c}: - Equal.trans(Bool, S.bound(S.fill.arg.go(dd, mm, S.fill.args(tt, bs)), nn), S.bound(S.fill.args(tt, bs), nn), - S.bound(bs, nn), - fin.go_other_b(dd, mm, S.fill.args(tt, bs), nn, fin.named_head(mm, tt, nn, h_none)), - fin.fill_other_b(tt, bs, nn, fin.named_tail(mm, tt, nn, h_none))) - -# arguments before the one named `nn`, none named `nn`, change none of its -# values -def fin.fill_pre( - pa: List<&2, S.Arg>, - +rest: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +nn: String, - +h_none: {Laws.none_named(pa, nn) == True{} : Bool} -) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, rest), bs), nn) - == S.get_all.bind(S.fill.args(rest, bs), nn) : List<&2, String>}: - match pa: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+mm, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c}: - +ff = S.fill.args(List.append(&2, S.Arg, tt, rest), bs) - Equal.trans(List<&2, String>, S.get_all.bind(S.fill.arg.go(dd, mm, ff), nn), S.get_all.bind(ff, nn), - S.get_all.bind(S.fill.args(rest, bs), nn), - fin.go_other(dd, mm, ff, nn, fin.named_head(mm, tt, nn, h_none)), - fin.fill_pre(tt, rest, bs, nn, fin.named_tail(mm, tt, nn, h_none))) - -# the default goes in front, once, of values there were none of -def fin.fill_default.go( - +nn: String, - +dd: String, - +ff: List<&2, S.Bind>, - e1: {False{} == S.bound(ff, nn) : Bool}, - e2: {True{} == String.eq(nn, nn) : Bool}, - e3: {[] == S.get_all.bind(ff, nn) : List<&2, String>} -) -> {S.get_all.bind(S.fill.one(S.bound(ff, nn), nn, dd, ff), nn) == [dd] : List<&2, String>}: - %e1 : {S.get_all.bind(S.fill.one(_, nn, dd, ff), nn) == [dd] : List<&2, String>} - %e2 : {S.get_all.bind.hit(_, dd, S.get_all.bind(ff, nn)) == [dd] : List<&2, String>} - %e3 : {dd <> _ == [dd] : List<&2, String>} - {==} - -# the default of the argument named `nn`, when its command left it unbound -def fin.fill_default( - +pa: List<&2, S.Arg>, - +qa: List<&2, S.Arg>, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +ev: Maybe<&2, String>, - +dd: String, - +cs: List<&2, String>, - +bs: List<&2, S.Bind>, - h_pa: {Laws.none_named(pa, nn) == True{} : Bool}, - +h_qa: {Laws.none_named(qa, nn) == True{} : Bool}, - +h_free: {S.bound(bs, nn) == False{} : Bool} -) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, - Some{dd}}, cs} <> qa), bs), - nn) == [dd] : List<&2, String>}: - +ff = S.fill.args(qa, bs) - e1 = Equal.sym(Bool, S.bound(ff, nn), False{}, - Equal.trans(Bool, S.bound(ff, nn), S.bound(bs, nn), False{}, fin.fill_other_b(qa, bs, nn, h_qa), h_free)) - e2 = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) - e3 = Equal.sym(List<&2, String>, S.get_all.bind(ff, nn), [], - Equal.trans(List<&2, String>, S.get_all.bind(ff, nn), S.get_all.bind(bs, nn), [], fin.fill_other(qa, bs, nn, h_qa), - fin.g_unbound(bs, nn, h_free))) - Equal.trans(List<&2, String>, - S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, - cs} <> qa), - bs), nn), - S.get_all.bind(S.fill.one(S.bound(ff, nn), nn, dd, ff), nn), [dd], - fin.fill_pre(pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, cs} <> qa, bs, nn, h_pa), - fin.fill_default.go(nn, dd, ff, e1, e2, e3)) - -# a bound argument's values, with or without a default -def fin.kept_go( - dflt: Maybe<&2, String>, - +nn: String, - +ff: List<&2, S.Bind>, - +bs: List<&2, S.Bind>, - eg: {S.get_all.bind(ff, nn) == S.get_all.bind(bs, nn) : List<&2, String>}, - eb: {S.bound(ff, nn) == True{} : Bool} -) -> {S.get_all.bind(S.fill.arg.go(dflt, nn, ff), nn) == S.get_all.bind(bs, nn) : List<&2, String>}: - match dflt: - case None{}: - eg - case Some{+vv}: - ee = Equal.sym(Bool, S.bound(ff, nn), True{}, eb) - %ee : {S.get_all.bind(S.fill.one(_, nn, vv, ff), nn) == S.get_all.bind(bs, nn) : List<&2, String>} - eg - -# whatever the default, a bound argument keeps exactly its values -def fin.fill_kept( - +pa: List<&2, S.Arg>, - +qa: List<&2, S.Arg>, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +bs: List<&2, S.Bind>, - h_pa: {Laws.none_named(pa, nn) == True{} : Bool}, - +h_qa: {Laws.none_named(qa, nn) == True{} : Bool}, - h_bound: {S.bound(bs, nn) == True{} : Bool} -) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} <> qa), bs), - nn) == S.get_all.bind(bs, nn) : List<&2, String>}: - match dflt: - case S.Fallback{+ev, +dm}: - +ff = S.fill.args(qa, bs) - Equal.trans(List<&2, String>, - S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dm}, - cs} <> qa), - bs), nn), - S.get_all.bind(S.fill.arg.go(dm, nn, ff), nn), S.get_all.bind(bs, nn), - fin.fill_pre(pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dm}, cs} <> qa, bs, nn, h_pa), - fin.kept_go(dm, nn, ff, bs, fin.fill_other(qa, bs, nn, h_qa), - Equal.trans(Bool, S.bound(ff, nn), S.bound(bs, nn), True{}, fin.fill_other_b(qa, bs, nn, h_qa), h_bound))) - -# an argument with no default keeps exactly its values -def fin.fill_nodef( - +pa: List<&2, S.Arg>, - +qa: List<&2, S.Arg>, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +ev: Maybe<&2, String>, - +cs: List<&2, String>, - +bs: List<&2, S.Bind>, - h_pa: {Laws.none_named(pa, nn) == True{} : Bool}, - h_qa: {Laws.none_named(qa, nn) == True{} : Bool} -) -> {S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, - cs} <> qa), bs), - nn) == S.get_all.bind(bs, nn) : List<&2, String>}: - Equal.trans(List<&2, String>, - S.get_all.bind(S.fill.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, - cs} <> qa), bs), nn), - S.get_all.bind(S.fill.args(qa, bs), nn), S.get_all.bind(bs, nn), - fin.fill_pre(pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs} <> qa, bs, nn, h_pa), - fin.fill_other(qa, bs, - nn, h_qa)) - -# the frame of the walker the words leave: levels `qs` in front of the level -# `args`, `bs`, above the levels `up`, and names `ms` after the path -def fin.Framed( - +spec: Shake.Cli, - +ws: List<&2, String>, - +qs: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +ms: List<&2, String> -) -> Type: - {Grow.gw.frame(S.parse.walk(ws, S.parse.start(spec))) - == Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} - : Grow.Frame} - -# the Matched of the command at `path`: its level, defaults filled, with the -# levels below it -def fin.at( - +spec: Shake.Cli, - +ws: List<&2, String>, - +found: Shake.Matched, - +qs: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +ms: List<&2, String>, - h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}, - h_fr: fin.Framed(spec, ws, qs, args, bs, up, path, ms), - h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} -) -> {S.at(found, path) - == Some{S.matched.build(S.Level{args, S.fill.args(args, bs)} <> List.reverse(&2, S.Level, S.fill.levels(qs)), ms)} - : Maybe<&2, S.Matched>}: - +fs = S.parse.walk(ws, S.parse.start(spec)) - +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} - +fq = S.fill.levels(qs) - +fu = S.fill.levels(up) - +r0 = List.reverse(&2, S.Level, fu) - +rq = List.reverse(&2, S.Level, fq) - +pm = List.append(&2, String, path, ms) - +lvs = List.append(&2, S.Level, qs, S.Level{args, bs} <> up) - em = Equal.trans(S.Matched, found, Grow.gw.build_of(Grow.gw.frame(fs)), - S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), - Grow.gw.finished(fs, found, vr.done(spec, S.parse.finish(fs), found, h_done)), - Equal.trans(S.Matched, Grow.gw.build_of(Grow.gw.frame(fs)), Grow.gw.build_of(Grow.Frame{lvs, pm}), - S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), - Equal.cong(Grow.Frame, S.Matched, fr => Grow.gw.build_of(fr), Grow.gw.frame(fs), Grow.Frame{lvs, pm}, h_fr), - Equal.cong(List<&2, S.Level>, S.Matched, ls => S.matched.build(ls, pm), - List.reverse(&2, S.Level, S.fill.levels(lvs)), List.append(&2, S.Level, r0, f0 <> rq), - Equal.trans(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(lvs)), - List.reverse(&2, S.Level, List.append(&2, S.Level, fq, f0 <> fu)), List.append(&2, S.Level, r0, f0 <> rq), - Equal.cong(List<&2, S.Level>, List<&2, S.Level>, ls => List.reverse(&2, S.Level, ls), S.fill.levels(lvs), - List.append(&2, S.Level, fq, f0 <> fu), Grow.gw.fill_mid(qs, args, bs, up)), - Grow.gw.lrev_mid(fq, f0, fu))))) - hl = Equal.trans(Nat, List.length(&2, S.Level, r0), List.length(&2, S.Level, fu), List.length(&2, String, path), - Grow.gw.len_rev(fu), - Equal.trans(Nat, List.length(&2, S.Level, fu), List.length(&2, S.Level, up), List.length(&2, String, path), - Grow.gw.len_fill(up), h_len)) - Equal.trans(Maybe<&2, S.Matched>, S.at(found, path), - S.at(S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), path), Some{S.matched.build(f0 <> rq, ms)}, - Equal.cong(S.Matched, Maybe<&2, S.Matched>, mm => S.at(mm, path), found, - S.matched.build(List.append(&2, S.Level, r0, f0 <> rq), pm), em), - Grow.gw.at_build(r0, path, f0, rq, ms, hl)) - -# `get_all` in the Matched of the command at `path`: its bindings, defaults -# filled, read back -def fin.all( - +spec: Shake.Cli, - +ws: List<&2, String>, - +found: Shake.Matched, - +qs: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +ms: List<&2, String>, - +nn: String, - h_done: {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}, - h_fr: fin.Framed(spec, ws, qs, args, bs, up, path, ms), - h_len: {List.length(&2, S.Level, up) == List.length(&2, String, path) : Nat} -) -> {Laws.all_at(found, path, nn) - == Some{List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn))} : Maybe<&2, List<&2, String>>}: - +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} - +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) - Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, nn), - Some{S.get_all.bind(S.own(S.matched.build(f0 <> rq, ms)), nn)}, - Some{List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn))}, - Equal.cong(Maybe<&2, S.Matched>, Maybe<&2, List<&2, String>>, mm => Laws.all_at.go(mm, nn), S.at(found, path), - Some{S.matched.build(f0 <> rq, ms)}, fin.at(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len)), - Equal.trans(Maybe<&2, List<&2, String>>, Some{S.get_all.bind(S.own(S.matched.build(f0 <> rq, ms)), nn)}, - Some{S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, bs)), nn)}, - Some{List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn))}, - Equal.cong(List<&2, S.Bind>, Maybe<&2, List<&2, String>>, xs => Some{S.get_all.bind(xs, nn)}, - S.own(S.matched.build(f0 <> rq, ms)), List.reverse(&2, S.Bind, S.fill.args(args, bs)), - Grow.gw.own_build(f0, rq, ms)), - Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, xs => Some{xs}, - S.get_all.bind(List.reverse(&2, S.Bind, S.fill.args(args, bs)), nn), - List.reverse(&2, String, S.get_all.bind(S.fill.args(args, bs), nn)), fin.g_rev(S.fill.args(args, bs), nn)))) - -# `get_all` of the command at `path` reads `xs` -def fin.All(+found: Shake.Matched, +path: List<&2, String>, +nn: String, +xs: List<&2, String>) -> Type: - {Laws.all_at(found, path, nn) == Some{xs} : Maybe<&2, List<&2, String>>} - -# a level's values read back, from what filling it gives the name -def fin.back( - +found: Shake.Matched, - +path: List<&2, String>, - +nn: String, - +xs: List<&2, String>, - +ys: List<&2, String>, - ea: {Laws.all_at(found, path, nn) == Some{List.reverse(&2, String, xs)} : Maybe<&2, List<&2, String>>}, - ex: {xs == ys : List<&2, String>} -) -> {Laws.all_at(found, path, nn) == Some{List.reverse(&2, String, ys)} : Maybe<&2, List<&2, String>>}: - Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, nn), Some{List.reverse(&2, String, xs)}, - Some{List.reverse(&2, String, ys)}, ea, - Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, zs => Some{List.reverse(&2, String, zs)}, xs, ys, ex)) - -# the values the words bound, read back in binding order -def fin.back_bs( - +found: Shake.Matched, - +path: List<&2, String>, - +nn: String, - +bs: List<&2, S.Bind>, - ea: fin.All(found, path, nn, List.reverse(&2, String, S.get_all.bind(bs, nn))) -) -> {Laws.all_at(found, path, nn) == Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)} - : Maybe<&2, List<&2, String>>}: - Equal.trans(Maybe<&2, List<&2, String>>, Laws.all_at(found, path, nn), - Some{List.reverse(&2, String, S.get_all.bind(bs, nn))}, Some{S.get_all.bind(List.reverse(&2, S.Bind, bs), nn)}, ea, - Equal.cong(List<&2, String>, Maybe<&2, List<&2, String>>, zs => Some{zs}, - List.reverse(&2, String, S.get_all.bind(bs, nn)), S.get_all.bind(List.reverse(&2, S.Bind, bs), nn), - Equal.sym(List<&2, String>, S.get_all.bind(List.reverse(&2, S.Bind, bs), nn), - List.reverse(&2, String, S.get_all.bind(bs, nn)), fin.g_rev(bs, nn)))) - -def Laws.default_filled( - spec, - ws, - found, - qs, - pa, - qa, - nn, - sh, - lo, - kind, - hp, - req, - ev, - dd, - cs, - bs, - up, - path, - ms, - h_done, - h_fr, - h_len, - h_pa, - h_qa, - h_free -): - +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{dd}}, cs} <> qa) - fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), [dd], - fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), - fin.fill_default(pa, qa, nn, sh, lo, kind, hp, req, ev, dd, cs, bs, h_pa, h_qa, h_free)) - -def Laws.bound_kept( - spec, - ws, - found, - qs, - pa, - qa, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - bs, - up, - path, - ms, - h_done, - h_fr, - h_len, - h_pa, - h_qa, - h_bound -): - +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} <> qa) - fin.back_bs(found, path, nn, bs, - fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), S.get_all.bind(bs, nn), - fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), - fin.fill_kept(pa, qa, nn, sh, lo, kind, hp, req, dflt, cs, bs, h_pa, h_qa, h_bound))) - -def Laws.no_default( - spec, - ws, - found, - qs, - pa, - qa, - nn, - sh, - lo, - kind, - hp, - req, - ev, - cs, - bs, - up, - path, - ms, - h_done, - h_fr, - h_len, - h_pa, - h_qa -): - +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs} <> qa) - fin.back_bs(found, path, nn, bs, - fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), S.get_all.bind(bs, nn), - fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), - fin.fill_nodef(pa, qa, nn, sh, lo, kind, hp, req, ev, cs, bs, h_pa, h_qa))) - -def Laws.no_argument(spec, ws, found, qs, args, nn, bs, up, path, ms, h_done, h_fr, h_len, h_none): - fin.back_bs(found, path, nn, bs, - fin.back(found, path, nn, S.get_all.bind(S.fill.args(args, bs), nn), S.get_all.bind(bs, nn), - fin.all(spec, ws, found, qs, args, bs, up, path, ms, nn, h_done, h_fr, h_len), - fin.fill_other(args, bs, nn, h_none))) - -# Unit for None, Empty for Some: a motive that tells them apart -def fin.none_ty(mm: Maybe<&2, String>) -> Type: - match mm: - case None{}: - Unit - case Some{_n}: - Empty - -# Some is never None -def fin.some_none(+nn: String, hh: {Some{nn} == None{} : Maybe<&2, String>}) -> Empty: - ee = Equal.sym(Maybe<&2, String>, Some{nn}, None{}, hh) - %ee : fin.none_ty(_) - Unit{} - -# a required check that found nothing here found nothing -def fin.miss_none( - mm: Maybe<&2, String>, - +path: List<&2, String>, - +lvs: List<&2, S.Level>, - +found: Shake.Matched, - hh: {S.parse.finish.miss(mm, path, lvs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} -) -> {mm == None{} : Maybe<&2, String>}: - match mm: - case None{}: - {==} - case Some{+nn}: - Empty.absurd({Some{nn} == None{} : Maybe<&2, String>}, Grow.gw.fail_done(S.Missing{path, nn}, found, hh)) - -# a walker that finishes as a success found no required argument missing -def fin.finished_miss( - fs: S.St, - +found: Shake.Matched, - hh: {S.parse.finish(fs) == Done{found} : Result<&2, &2, S.ParseErr, S.Matched>} -) -> {S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs))))) == None{} - : Maybe<&2, String>}: - match fs: - case S.St{S.Free{}, +args, +up, _s, +path, +binds, _o, _r, _n}: - +lvs = List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up)) - fin.miss_none(S.miss.levels(lvs), path, lvs, found, hh) - case S.St{S.Dead{+ee}, +args, +up, _s, _p, +binds, _o, _r, _n}: - Empty.absurd({S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up))) == None{} - : Maybe<&2, String>}, Grow.gw.fail_done(ee, found, hh)) - case S.St{S.Need{+nn}, +args, +up, _s, +path, +binds, _o, _r, _n}: - Empty.absurd({S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up))) == None{} - : Maybe<&2, String>}, Grow.gw.fail_done(S.NoValue{path, nn}, found, hh)) - case S.St{S.Help{}, +args, +up, _s, +path, +binds, _o, _r, _n}: - Empty.absurd({S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up))) == None{} - : Maybe<&2, String>}, Grow.gw.fail_done(S.NeedHelp{path}, found, hh)) - -# a first-required pick that found nothing found nothing in either part -def fin.first_none_l( - here: Maybe<&2, String>, - +rest: Maybe<&2, String>, - hh: {S.parse.first_req.at(here, rest) == None{} : Maybe<&2, String>} -) -> {here == None{} : Maybe<&2, String>}: - match here: - case None{}: - {==} - case Some{+nn}: - Empty.absurd({Some{nn} == None{} : Maybe<&2, String>}, fin.some_none(nn, hh)) - -# the same, for the rest -def fin.first_none_r( - here: Maybe<&2, String>, - +rest: Maybe<&2, String>, - hh: {S.parse.first_req.at(here, rest) == None{} : Maybe<&2, String>} -) -> {rest == None{} : Maybe<&2, String>}: - match here: - case None{}: - hh - case Some{+nn}: - Empty.absurd({rest == None{} : Maybe<&2, String>}, fin.some_none(nn, hh)) - -# a required check over the levels that found nothing found nothing in this one -def fin.miss_mid( - r0: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +fb: List<&2, S.Bind>, - +rq: List<&2, S.Level>, - hh: {S.miss.levels(List.append(&2, S.Level, r0, S.Level{args, fb} <> rq)) == None{} : Maybe<&2, String>} -) -> {S.miss.args(args, fb) == None{} : Maybe<&2, String>}: - match r0: - case Nil{}: - fin.first_none_l(S.miss.args(args, fb), S.miss.levels(rq), hh) - case Con{ll, +tt}: - match ll: - case S.Level{+la, +lb}: - fin.miss_mid(tt, args, fb, rq, - fin.first_none_r(S.miss.args(la, lb), S.miss.levels(List.append(&2, S.Level, tt, S.Level{args, fb} <> rq)), - hh)) - -# a required check that found nothing found nothing further on -def fin.one_none( - need: Bool, - +nm: String, - +rest: Maybe<&2, String>, - hh: {S.miss.one(need, nm, rest) == None{} : Maybe<&2, String>} -) -> {rest == None{} : Maybe<&2, String>}: - match need: - case True{}: - Empty.absurd({rest == None{} : Maybe<&2, String>}, fin.some_none(nm, hh)) - case False{}: - hh - -# a required argument the check passed is bound -def fin.req_bound( - bb: Bool, - +nm: String, - +rest: Maybe<&2, String>, - hh: {S.miss.one(Bool.not(bb), nm, rest) == None{} : Maybe<&2, String>} -) -> {bb == True{} : Bool}: - match bb: - case True{}: - {==} - case False{}: - Empty.absurd({False{} == True{} : Bool}, fin.some_none(nm, hh)) - -# a command's arguments around a required one named `nn` -def fin.req_args( - +pa: List<&2, S.Arg>, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +dflt: S.Fallback, - +cs: List<&2, String>, - +qa: List<&2, S.Arg> -) -> List<&2, S.Arg>: - List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa) - -# a required check that found nothing leaves every required argument bound -def fin.miss_req( - pa: List<&2, S.Arg>, - +qa: List<&2, S.Arg>, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +dflt: S.Fallback, - +cs: List<&2, String>, - +fb: List<&2, S.Bind>, - hh: {S.miss.args(fin.req_args(pa, nn, sh, lo, kind, hp, dflt, cs, qa), fb) == None{} : Maybe<&2, String>} -) -> {S.bound(fb, nn) == True{} : Bool}: - match pa: - case Nil{}: - fin.req_bound(S.bound(fb, nn), nn, S.miss.args(qa, fb), hh) - case Con{hd, +tt}: - match hd: - case S.Arg{+mm, _s, _l, _k, _h, +rq, _d, _c}: - fin.miss_req(tt, qa, nn, sh, lo, kind, hp, dflt, cs, fb, - fin.one_none(Bool.and(rq, Bool.not(S.bound(fb, mm))), mm, - S.miss.args(List.append(&2, S.Arg, tt, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), fb), hh)) - -# the filled levels of a finished walker whose frame is known, root first -def fin.levels( - +qs: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level> -) -> {List.reverse(&2, S.Level, S.fill.levels(List.append(&2, S.Level, qs, S.Level{args, bs} <> up))) - == List.append(&2, S.Level, List.reverse(&2, S.Level, S.fill.levels(up)), - S.Level{args, S.fill.args(args, bs)} <> List.reverse(&2, S.Level, S.fill.levels(qs))) : List<&2, S.Level>}: - +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} - Equal.trans(List<&2, S.Level>, - List.reverse(&2, S.Level, S.fill.levels(List.append(&2, S.Level, qs, S.Level{args, bs} <> up))), - List.reverse(&2, S.Level, List.append(&2, S.Level, S.fill.levels(qs), f0 <> S.fill.levels(up))), - List.append(&2, S.Level, List.reverse(&2, S.Level, S.fill.levels(up)), - f0 <> List.reverse(&2, S.Level, S.fill.levels(qs))), - Equal.cong(List<&2, S.Level>, List<&2, S.Level>, ls => List.reverse(&2, S.Level, ls), - S.fill.levels(List.append(&2, S.Level, qs, S.Level{args, bs} <> up)), - List.append(&2, S.Level, S.fill.levels(qs), f0 <> S.fill.levels(up)), Grow.gw.fill_mid(qs, args, bs, up)), - Grow.gw.lrev_mid(S.fill.levels(qs), f0, S.fill.levels(up))) - -def Laws.required_present( - spec, - ws, - found, - qs, - pa, - qa, - nn, - sh, - lo, - kind, - hp, - dflt, - cs, - bs, - up, - path, - ms, - h_done, - h_fr, - h_len -): - +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa) - +fb = S.fill.args(args, bs) - +fs = S.parse.walk(ws, S.parse.start(spec)) - +lvs = List.append(&2, S.Level, qs, S.Level{args, bs} <> up) - +r0 = List.reverse(&2, S.Level, S.fill.levels(up)) - +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) - +f0 = {S.Level{args, fb} : S.Level} - +bm = S.matched.build(f0 <> rq, ms) - +hd = {h_done : {Shake.parse(spec, ws) == Done{found} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}} - +h_fr2 = {h_fr : {Grow.gw.frame(fs) == Grow.Frame{lvs, List.append(&2, String, path, ms)} : Grow.Frame}} - mn = Equal.trans(Maybe<&2, String>, S.miss.levels(List.append(&2, S.Level, r0, f0 <> rq)), - S.miss.levels(List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs))))), None{}, - Equal.cong(List<&2, S.Level>, Maybe<&2, String>, ls => S.miss.levels(ls), List.append(&2, S.Level, r0, f0 <> rq), - List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs)))), - Equal.sym(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs)))), - List.append(&2, S.Level, r0, f0 <> rq), - Equal.trans(List<&2, S.Level>, List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(Grow.gw.frame(fs)))), - List.reverse(&2, S.Level, S.fill.levels(lvs)), List.append(&2, S.Level, r0, f0 <> rq), - Equal.cong(Grow.Frame, List<&2, S.Level>, fr => List.reverse(&2, S.Level, S.fill.levels(Grow.gw.flevels(fr))), - Grow.gw.frame(fs), Grow.Frame{lvs, List.append(&2, String, path, ms)}, h_fr2), - fin.levels(qs, args, bs, up)))), - fin.finished_miss(fs, found, vr.done(spec, S.parse.finish(fs), found, hd))) - hb = fin.miss_req(pa, qa, nn, sh, lo, kind, hp, dflt, cs, fb, fin.miss_mid(r0, args, fb, rq, mn)) - eb = Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(S.own(bm), nn))), - Bool.not(List.is_empty(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, fb), nn))), True{}, - Equal.cong(List<&2, S.Bind>, Bool, xs => Bool.not(List.is_empty(&2, String, S.get_all.bind(xs, nn))), S.own(bm), - List.reverse(&2, S.Bind, fb), Grow.gw.own_build(f0, rq, ms)), - Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(List.reverse(&2, S.Bind, fb), nn))), - Bool.not(List.is_empty(&2, String, List.reverse(&2, String, S.get_all.bind(fb, nn)))), True{}, - Equal.cong(List<&2, String>, Bool, xs => Bool.not(List.is_empty(&2, String, xs)), - S.get_all.bind(List.reverse(&2, S.Bind, fb), nn), List.reverse(&2, String, S.get_all.bind(fb, nn)), - fin.g_rev(fb, nn)), - Equal.trans(Bool, Bool.not(List.is_empty(&2, String, List.reverse(&2, String, S.get_all.bind(fb, nn)))), - Bool.not(List.is_empty(&2, String, S.get_all.bind(fb, nn))), True{}, - Equal.cong(Bool, Bool, xx => Bool.not(xx), List.is_empty(&2, String, List.reverse(&2, String, - S.get_all.bind(fb, nn))), - List.is_empty(&2, String, S.get_all.bind(fb, nn)), fin.empty_rev0(S.get_all.bind(fb, nn))), - Equal.trans(Bool, Bool.not(List.is_empty(&2, String, S.get_all.bind(fb, nn))), S.bound(fb, nn), True{}, - Equal.sym(Bool, S.bound(fb, nn), Bool.not(List.is_empty(&2, String, S.get_all.bind(fb, nn))), - fin.bound_g(fb, nn)), hb)))) - Equal.trans(Maybe<&2, Bool>, Laws.present_at(found, path, nn), Laws.present_at.go(Some{bm}, nn), Some{True{}}, - Equal.cong(Maybe<&2, S.Matched>, Maybe<&2, Bool>, mm => Laws.present_at.go(mm, nn), S.at(found, path), Some{bm}, - fin.at(spec, ws, found, qs, args, bs, up, path, ms, hd, h_fr2, h_len)), - Equal.cong(Bool, Maybe<&2, Bool>, xx => Some{xx}, Bool.not(List.is_empty(&2, String, - S.get_all.bind(S.own(bm), nn))), - True{}, eb)) - -# a check that found something here finds something -def fin.one_some( - need: Bool, - +mm: String, - +rest: Maybe<&2, String>, - ww: &nm: String -> {rest == Some{nm} : Maybe<&2, String>} -) -> &nm: String -> {S.miss.one(need, mm, rest) == Some{nm} : Maybe<&2, String>}: - match need: - case True{}: - (mm, {==}) - case False{}: - ww - -# the required, unbound argument itself is found missing -def fin.miss_here( - +nn: String, - +fb: List<&2, S.Bind>, - +rest: Maybe<&2, String>, - h_unbound: {S.bound(fb, nn) == False{} : Bool} -) -> {S.miss.one(Bool.and(True{}, Bool.not(S.bound(fb, nn))), nn, rest) == Some{nn} : Maybe<&2, String>}: - ee = Equal.sym(Bool, S.bound(fb, nn), False{}, h_unbound) - %ee : {S.miss.one(Bool.and(True{}, Bool.not(_)), nn, rest) == Some{nn} : Maybe<&2, String>} - {==} - -# a required argument its command left unbound is found missing -def fin.miss_args_some( - pa: List<&2, S.Arg>, - +qa: List<&2, S.Arg>, - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +dflt: S.Fallback, - +cs: List<&2, String>, - +fb: List<&2, S.Bind>, - +h_unbound: {S.bound(fb, nn) == False{} : Bool} -) -> &nm: String -> {S.miss.args(List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), fb) - == Some{nm} : Maybe<&2, String>}: - match pa: - case Nil{}: - (nn, fin.miss_here(nn, fb, S.miss.args(qa, fb), h_unbound)) - case Con{hd, +tt}: - match hd: - case S.Arg{+mm, _s, _l, _k, _h, +rq, _d, _c}: - fin.one_some(Bool.and(rq, Bool.not(S.bound(fb, mm))), mm, - S.miss.args(List.append(&2, S.Arg, tt, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa), fb), - fin.miss_args_some(tt, qa, nn, sh, lo, kind, hp, dflt, cs, fb, h_unbound)) - -# a first-required pick with something found here or further on finds it -def fin.first_some( - here: Maybe<&2, String>, - +rest: Maybe<&2, String>, - ww: &nm: String -> {rest == Some{nm} : Maybe<&2, String>} -) -> &nm: String -> {S.parse.first_req.at(here, rest) == Some{nm} : Maybe<&2, String>}: - match here: - case Some{+nm}: - (nm, {==}) - case None{}: - ww - -# a required check over the levels finds what one level's check finds, or an -# earlier one -def fin.miss_levels_some( - r0: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +fb: List<&2, S.Bind>, - +rq: List<&2, S.Level>, - ww: &nm: String -> {S.miss.args(args, fb) == Some{nm} : Maybe<&2, String>} -) -> &nm: String -> {S.miss.levels(List.append(&2, S.Level, r0, S.Level{args, fb} <> rq)) == Some{nm} - : Maybe<&2, String>}: - match r0: - case Nil{}: - (n1, e1) = ww - +nm = {n1 : String} - (nm, Equal.cong(Maybe<&2, String>, Maybe<&2, String>, xx => S.parse.first_req.at(xx, S.miss.levels(rq)), - S.miss.args(args, fb), Some{nm}, e1)) - case Con{ll, +tt}: - match ll: - case S.Level{+la, +lb}: - fin.first_some(S.miss.args(la, lb), S.miss.levels(List.append(&2, S.Level, tt, S.Level{args, fb} <> rq)), - fin.miss_levels_some(tt, args, fb, rq, ww)) - -# what `required_missing` claims of a finished walker -def fin.missing.ty(+fs: S.St, +path: List<&2, String>) -> Type: - &nm: String -> {S.parse.finish(fs) == Fail{S.Missing{path, nm}} : Result<&2, &2, S.ParseErr, S.Matched>} - -# a free walker whose levels' required check finds a name fails with it -def fin.missing( - fs: S.St, - +lvs: List<&2, S.Level>, - +path: List<&2, String>, - +ll: List<&2, S.Level>, - h_free: {Laws.free(fs) == True{} : Bool}, - h_fr: {Grow.gw.frame(fs) == Grow.Frame{lvs, path} : Grow.Frame}, - e_ll: {List.reverse(&2, S.Level, S.fill.levels(lvs)) == ll : List<&2, S.Level>}, - ww: &nm: String -> {S.miss.levels(ll) == Some{nm} : Maybe<&2, String>} -) -> fin.missing.ty(fs, path): - match fs: - case S.St{S.Free{}, +args, +up, +subs, +pp, +binds, +pos, +raw, +seen}: - (n1, e1) = ww - +nm = {n1 : String} - +l0 = List.reverse(&2, S.Level, S.fill.levels(S.Level{args, binds} <> up)) - +h_fr2 = {h_fr : {Grow.Frame{S.Level{args, binds} <> up, pp} == Grow.Frame{lvs, path} : Grow.Frame}} - em = Equal.trans(Maybe<&2, String>, S.miss.levels(l0), S.miss.levels(ll), Some{nm}, - Equal.cong(List<&2, S.Level>, Maybe<&2, String>, ls => S.miss.levels(ls), l0, ll, - Equal.trans(List<&2, S.Level>, l0, List.reverse(&2, S.Level, S.fill.levels(lvs)), ll, - Equal.cong(Grow.Frame, List<&2, S.Level>, fr => List.reverse(&2, S.Level, - S.fill.levels(Grow.gw.flevels(fr))), - Grow.Frame{S.Level{args, binds} <> up, pp}, Grow.Frame{lvs, path}, h_fr2), e_ll)), e1) - ep = Equal.cong(Grow.Frame, List<&2, String>, fr => Grow.gw.fpath(fr), Grow.Frame{S.Level{args, binds} <> up, pp}, - Grow.Frame{lvs, path}, h_fr2) - (nm, Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, S.parse.finish.miss(S.miss.levels(l0), pp, l0), - Fail{S.Missing{pp, nm}}, Fail{S.Missing{path, nm}}, - Equal.cong(Maybe<&2, String>, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.parse.finish.miss(xx, pp, l0), - S.miss.levels(l0), Some{nm}, em), - Equal.cong(List<&2, String>, Result<&2, &2, S.ParseErr, S.Matched>, qq => Fail{S.Missing{qq, nm}}, pp, path, - ep))) - case S.St{S.Dead{_e}, _a, _g, _s, _p, _b, _o, _r, _n}: - Empty.absurd(fin.missing.ty(S.St{S.Dead{_e}, _a, _g, _s, _p, _b, _o, _r, _n}, path), Walk.wk.false_true(h_free)) - case S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}: - Empty.absurd(fin.missing.ty(S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}, path), Walk.wk.false_true(h_free)) - case S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}: - Empty.absurd(fin.missing.ty(S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}, path), Walk.wk.false_true(h_free)) - -def Laws.required_missing( - spec, - ws, - qs, - pa, - qa, - nn, - sh, - lo, - kind, - hp, - dflt, - cs, - bs, - up, - path, - h_free, - h_fr, - h_unbound -): - +args = List.append(&2, S.Arg, pa, S.Arg{nn, sh, lo, kind, hp, True{}, dflt, cs} <> qa) - +fb = S.fill.args(args, bs) - +r0 = List.reverse(&2, S.Level, S.fill.levels(up)) - +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) - vr.miss(spec, ws, path, fin.missing(S.parse.walk(ws, S.parse.start(spec)), - List.append(&2, S.Level, qs, S.Level{args, bs} <> up), path, - List.append(&2, S.Level, r0, S.Level{args, fb} <> rq), h_free, h_fr, fin.levels(qs, args, bs, up), - fin.miss_levels_some(r0, args, fb, rq, fin.miss_args_some(pa, qa, nn, sh, lo, kind, hp, dflt, cs, fb, h_unbound)))) - -# a plain word before `--` is read as a word: a subcommand or a positional -def enter.word( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - h_dd: {String.eq(tok, "--") == False{} : Bool}, - h_help: {String.eq(tok, "help") == False{} : Bool}, - +h_dash: {String.starts_with(tok, "-") == False{} : Bool} -) -> {S.parse.step(tok, S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen}) - == S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) : S.St}: - +rhs = S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) - e1 = Equal.sym(Bool, String.eq(tok, "--"), False{}, h_dd) - %e1 : {S.parse.step.end(_, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - e2 = Equal.sym(Bool, String.eq(tok, "help"), False{}, h_help) - %e2 : {S.parse.step.help(Bool.and(_, Bool.not(seen)), tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - e3 = Equal.sym(Bool, String.starts_with(tok, "--"), False{}, Walk.wk.dash(tok, h_dash)) - %e3 : {S.parse.step.kind.go(_, String.starts_with(tok, "-"), tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - e4 = Equal.sym(Bool, String.starts_with(tok, "-"), False{}, h_dash) - %e4 : {S.parse.step.kind.go(False{}, _, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - {==} - -# a found subcommand, entered or refused, as the first blocking positional says -def enter.go( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +sub: S.Sub, - h_sub: {S.find_sub(subs, tok) == Some{sub} : Maybe<&2, S.Sub>} -) -> {S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) - == S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen) : S.St}: - ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, tok), Some{sub}, h_sub) - %ee : {S.parse.word.go(_, tok, args, up, subs, path, bs, pos, False{}, seen) - == S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen) : S.St} - {==} - -# nothing blocking: the subcommand is entered -def enter.none( - +pos: List<&2, S.Arg>, - +name: String, - +about: String, - +next: List<&2, S.Arg>, - +kids: List<&2, S.Sub>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - h_req: {S.parse.first_req(pos) == None{} : Maybe<&2, String>} -) -> {S.parse.enter_or(S.parse.first_req(pos), S.Sub{name, about, next, kids}, args, up, subs, path, bs, pos, False{}, - seen) - == S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), - False{}, False{}} : S.St}: - er = Equal.sym(Maybe<&2, String>, S.parse.first_req(pos), None{}, h_req) - %er : {S.parse.enter_or(_, S.Sub{name, about, next, kids}, args, up, subs, path, bs, pos, False{}, seen) - == S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), - False{}, False{}} : S.St} - {==} - -# a blocking positional: the word fails the step with Missing of it -def enter.miss( - +pos: List<&2, S.Arg>, - +sub: S.Sub, - +nm: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +seen: Bool, - h_req: {S.parse.first_req(pos) == Some{nm} : Maybe<&2, String>} -) -> {Laws.dead_of(S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.Missing{path, nm}} : Maybe<&2, S.ParseErr>}: - er = Equal.sym(Maybe<&2, String>, S.parse.first_req(pos), Some{nm}, h_req) - %er : {Laws.dead_of(S.parse.enter_or(_, sub, args, up, subs, path, bs, pos, False{}, seen)) - == Some{S.Missing{path, nm}} : Maybe<&2, S.ParseErr>} - {==} - -# a step equal to a failed walker fails the same way -def enter.dead( - +tok: String, - +st: S.St, - +st2: S.St, - +err: S.ParseErr, - ee: {st2 == S.parse.step(tok, st) : S.St}, - hd: {Laws.dead_of(st2) == Some{err} : Maybe<&2, S.ParseErr>} -) -> {Laws.dead_of(S.parse.step(tok, st)) == Some{err} : Maybe<&2, S.ParseErr>}: - %ee : {Laws.dead_of(_) == Some{err} : Maybe<&2, S.ParseErr>} - hd - -def Laws.enter_step( - spec, - pre, - tok, - args, - up, - subs, - path, - bs, - pos, - seen, - name, - about, - next, - kids, - h_at, - h_dd, - h_help, - h_dash, - h_sub, - h_req -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - +sub = {S.Sub{name, about, next, kids} : S.Sub} - +rhs = {S.St{S.Free{}, next, S.Level{args, bs} <> up, kids, List.append(&2, String, path, [name]), [], S.pos_of(next), - False{}, False{}} : S.St} - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, [tok]), S.parse.start(spec)), S.parse.step(tok, st), rhs, - kept.stepped(spec, pre, tok, st, h_at), - Equal.trans(S.St, S.parse.step(tok, st), - S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen), rhs, - enter.word(tok, args, up, subs, path, bs, pos, seen, h_dd, h_help, h_dash), - Equal.trans(S.St, S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen), - S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), rhs, - enter.go(tok, args, up, subs, path, bs, pos, seen, sub, h_sub), - enter.none(pos, name, about, next, kids, args, up, subs, path, bs, seen, h_req)))) - -def Laws.enter_missing( - spec, - pre, - more, - tok, - args, - up, - subs, - path, - bs, - pos, - seen, - sub, - nm, - h_at, - h_dd, - h_help, - h_dash, - h_sub, - h_req -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e1 = Equal.sym(S.St, S.parse.step(tok, st), - S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), - Equal.trans(S.St, S.parse.step(tok, st), - S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen), - S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), - enter.word(tok, args, up, subs, path, bs, pos, seen, h_dd, h_help, h_dash), - enter.go(tok, args, up, subs, path, bs, pos, seen, sub, h_sub))) - Laws.refused(spec, pre, more, tok, st, S.Missing{path, nm}, h_at, - enter.dead(tok, st, S.parse.enter_or(S.parse.first_req(pos), sub, args, up, subs, path, bs, pos, False{}, seen), - S.Missing{path, nm}, e1, enter.miss(pos, sub, nm, args, up, subs, path, bs, seen, h_req))) - -# a positional that does not block a subcommand is skipped: a rest, one not -# required, or one with a default -def blk.req( - rk: Bool, - req: Bool, - +ev: Maybe<&2, String>, - dflt: Maybe<&2, String>, - +nm: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +cs: List<&2, String>, - hh: {Bool.not(Bool.and(Bool.not(rk), Bool.and(req, Maybe.is_none(&2, String, dflt)))) == True{} : Bool} -) -> {S.parse.first_req.skip(rk, S.Arg{nm, sh, lo, kind, hp, req, S.Fallback{ev, dflt}, cs}) == None{} : Maybe<&2, - String>}: - match rk: - case True{}: - {==} - case False{}: - match req: - case False{}: - {==} - case True{}: - match dflt: - case Some{_d}: - {==} - case None{}: - Empty.absurd({S.parse.first_req.skip(False{}, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, - None{}}, cs}) == None{} - : Maybe<&2, String>}, Walk.wk.false_true(hh)) - -# the same, from the argument's kind -def blk.skip( - +kind: S.ArgKind, - +req: Bool, - +dflt: S.Fallback, - +nm: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +cs: List<&2, String>, - hh: {Bool.not(Laws.blocks(S.Arg{nm, sh, lo, kind, hp, req, dflt, cs})) == True{} : Bool} -) -> {S.parse.first_req.skip(S.rest.is.kind(kind), S.Arg{nm, sh, lo, kind, hp, req, dflt, cs}) == None{} - : Maybe<&2, String>}: - match dflt: - case S.Fallback{+ev, dm}: - blk.req(S.rest.is.kind(kind), req, ev, dm, nm, sh, lo, kind, hp, cs, hh) - -# past a positional that does not block, the first blocking one is the rest's -def blk.past( - +kind: S.ArgKind, - +req: Bool, - +dflt: S.Fallback, - +nm: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +hp: String, - +cs: List<&2, String>, - +tt: List<&2, S.Arg>, - hh: {Bool.not(Laws.blocks(S.Arg{nm, sh, lo, kind, hp, req, dflt, cs})) == True{} : Bool} -) -> {S.parse.first_req(S.Arg{nm, sh, lo, kind, hp, req, dflt, cs} <> tt) == S.parse.first_req(tt) : Maybe<&2, String>}: - ee = Equal.sym(Maybe<&2, String>, - S.parse.first_req.skip(S.rest.is.kind(kind), S.Arg{nm, sh, lo, kind, hp, req, dflt, cs}), None{}, - blk.skip(kind, req, dflt, nm, sh, lo, hp, cs, hh)) - %ee : {S.parse.first_req.at(_, S.parse.first_req(tt)) == S.parse.first_req(tt) : Maybe<&2, String>} - {==} - -def Laws.none_blocking(pos, h_none): - match pos: - case Nil{}: - {==} - case Con{hd, +tt}: - match hd: - case S.Arg{+nm, +sh, +lo, +kind, +hp, +req, +dflt, +cs}: - +ab = {S.Arg{nm, sh, lo, kind, hp, req, dflt, cs} : S.Arg} - Equal.trans(Maybe<&2, String>, S.parse.first_req(ab <> tt), S.parse.first_req(tt), None{}, - blk.past(kind, req, dflt, nm, sh, lo, hp, cs, tt, - Walk.wk.and_l(Bool.not(Laws.blocks(ab)), Laws.none_block(tt), h_none)), - Laws.none_blocking(tt, Walk.wk.and_r(Bool.not(Laws.blocks(ab)), Laws.none_block(tt), h_none))) - -def Laws.first_blocking(pa, nm, sh, lo, kind, hp, ev, cs, qa, h_pa, h_kind): - match pa: - case Nil{}: - ee = Equal.sym(Bool, S.rest.is.kind(kind), False{}, h_kind) - %ee : {S.parse.first_req.at(S.parse.first_req.skip(_, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, - None{}}, cs}), - S.parse.first_req(qa)) == Some{nm} : Maybe<&2, String>} - {==} - case Con{hd, +tt}: - match hd: - case S.Arg{+n2, +s2, +l2, +k2, +h2, +r2, +d2, +c2}: - +ab = {S.Arg{n2, s2, l2, k2, h2, r2, d2, c2} : S.Arg} - +rest = List.append(&2, S.Arg, tt, S.Arg{nm, sh, lo, kind, hp, True{}, S.Fallback{ev, None{}}, cs} <> qa) - Equal.trans(Maybe<&2, String>, S.parse.first_req(ab <> rest), S.parse.first_req(rest), Some{nm}, - blk.past(k2, r2, d2, n2, s2, l2, h2, c2, rest, Walk.wk.and_l(Bool.not(Laws.blocks(ab)), - Laws.none_block(tt), h_pa)), - Laws.first_blocking(tt, nm, sh, lo, kind, hp, ev, cs, qa, - Walk.wk.and_r(Bool.not(Laws.blocks(ab)), Laws.none_block(tt), h_pa), h_kind)) - - -def Laws.help_step(spec, pre, args, up, subs, path, bs, pos, h_at): - kept.stepped(spec, pre, "help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}, h_at) - -# the first word of a list, or none -def hp.head(ns: List<&2, String>) -> String: - match ns: - case Nil{}: - "" - case Con{hh, _t}: - hh - -# what `hp.walk` claims -def hp.ty( - +ns: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> Type: - &kids: List<&2, S.Sub> -> {S.parse.walk(ns, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}) - == S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen} : S.St} - -# the names under a found subcommand, carried to its own subcommands -def hp.names_tr( - +tt: List<&2, String>, - +aa: List<&2, S.Sub>, - +bb: List<&2, S.Sub>, - ee: {aa == bb : List<&2, S.Sub>}, - hh: {Laws.help_names(tt, aa) == True{} : Bool} -) -> {Laws.help_names(tt, bb) == True{} : Bool}: - Equal.trans(Bool, Laws.help_names(tt, bb), Laws.help_names(tt, aa), True{}, - Equal.cong(List<&2, S.Sub>, Bool, kk => Laws.help_names(tt, kk), bb, aa, Equal.sym(List<&2, S.Sub>, aa, bb, - ee)), hh) - -# one name further down the help path, then the rest -def hp.cons( - +nn: String, - +tt: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +sn: String, - +sa: String, - +sg: List<&2, S.Arg>, - +kids: List<&2, S.Sub>, - e_fm: {S.find_sub(subs, nn) == Some{S.Sub{sn, sa, sg, kids}} : Maybe<&2, S.Sub>}, - ih: hp.ty(tt, args, up, kids, List.append(&2, String, path, [nn]), bs, pos, raw, seen) -) -> hp.ty(nn <> tt, args, up, subs, path, bs, pos, raw, seen): - (k2, e2) = ih - +k3 = {k2 : List<&2, S.Sub>} - +mid = {S.St{S.Help{}, args, up, kids, List.append(&2, String, path, [nn]), bs, pos, raw, seen} : S.St} - +fin_st = {S.St{S.Help{}, args, up, k3, List.append(&2, String, path, nn <> tt), bs, pos, raw, seen} : S.St} - es = Equal.cong(Maybe<&2, S.Sub>, S.St, mm => S.parse.walk(tt, S.parse.step.help_path(mm, nn, args, up, subs, - path, bs, - pos, raw, seen)), S.find_sub(subs, nn), Some{S.Sub{sn, sa, sg, kids}}, e_fm) - (k3, Equal.trans(S.St, S.parse.walk(nn <> tt, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}), - S.parse.walk(tt, mid), fin_st, es, - Equal.trans(S.St, S.parse.walk(tt, mid), - S.St{S.Help{}, args, up, k3, List.append(&2, String, List.append(&2, String, path, [nn]), tt), bs, pos, - raw, seen}, - fin_st, e2, - Equal.cong(List<&2, String>, S.St, pp => S.St{S.Help{}, args, up, k3, pp, bs, pos, raw, seen}, - List.append(&2, String, List.append(&2, String, path, [nn]), tt), List.append(&2, String, path, nn <> tt), - Grow.gw.sapp(path, [nn], tt))))) - -# words that each name a subcommand under the one before extend a help path -def hp.walk( - ns: List<&2, String>, - +subs: List<&2, S.Sub>, - fm: Maybe<&2, S.Sub>, - +e_fm: {S.find_sub(subs, hp.head(ns)) == fm : Maybe<&2, S.Sub>}, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +h_ns: {Laws.help_names(ns, subs) == True{} : Bool} -) -> hp.ty(ns, args, up, subs, path, bs, pos, raw, seen): - match ns: - case Nil{}: - (subs, Equal.cong(List<&2, String>, S.St, pp => S.St{S.Help{}, args, up, subs, pp, bs, pos, raw, seen}, path, - List.append(&2, String, path, []), Equal.sym(List<&2, String>, List.append(&2, String, path, []), path, - Grow.gw.nil_s(path)))) - case Con{+nn, +tt}: - match fm: - case None{}: - Empty.absurd(hp.ty(nn <> tt, args, up, subs, path, bs, pos, raw, seen), - Walk.wk.false_true(Equal.trans(Bool, False{}, Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), True{}, - Equal.cong(Maybe<&2, S.Sub>, Bool, mm => Maybe.is_some(&2, S.Sub, mm), None{}, S.find_sub(subs, nn), - Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, nn), None{}, e_fm)), - Walk.wk.and_l(Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), - Laws.help_names(tt, Laws.kids_of(S.find_sub(subs, nn))), h_ns)))) - case Some{ss}: - match ss: - case S.Sub{+sn, +sa, +sg, +kids}: - hp.cons(nn, tt, args, up, subs, path, bs, pos, raw, seen, sn, sa, sg, kids, e_fm, - hp.walk(tt, kids, S.find_sub(kids, hp.head(tt)), {==}, args, up, List.append(&2, String, path, - [nn]), bs, - pos, raw, seen, - hp.names_tr(tt, Laws.kids_of(S.find_sub(subs, nn)), kids, - Equal.cong(Maybe<&2, S.Sub>, List<&2, S.Sub>, mm => Laws.kids_of(mm), S.find_sub(subs, nn), - Some{S.Sub{sn, sa, sg, kids}}, e_fm), - Walk.wk.and_r(Maybe.is_some(&2, S.Sub, S.find_sub(subs, nn)), - Laws.help_names(tt, Laws.kids_of(S.find_sub(subs, nn))), h_ns)))) - -# the walk after the words before, read from where they left the walker -def hp.from( - +spec: Shake.Cli, - +pre: List<&2, String>, - +ns: List<&2, String>, - +st: S.St, - +big: S.St, - h_at: {S.parse.walk(pre, S.parse.start(spec)) == st : S.St}, - ew: {S.parse.walk(ns, st) == big : S.St} -) -> {S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)) == big : S.St}: - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)), S.parse.walk(ns, st), big, - rest.after(spec, pre, ns, st, h_at), ew) - -# the help walk, read from the start -def hp.at( - +spec: Shake.Cli, - +pre: List<&2, String>, - +ns: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - h_at: {S.parse.walk(pre, S.parse.start(spec)) == S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen} : S.St}, - ww: hp.ty(ns, args, up, subs, path, bs, pos, raw, seen) -) -> &kids: List<&2, S.Sub> -> {S.parse.walk(List.append(&2, String, pre, ns), S.parse.start(spec)) - == S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen} : S.St}: - (k1, e1) = ww - +kids = {k1 : List<&2, S.Sub>} - (kids, hp.from(spec, pre, ns, S.St{S.Help{}, args, up, subs, path, bs, pos, raw, seen}, - S.St{S.Help{}, args, up, kids, List.append(&2, String, path, ns), bs, pos, raw, seen}, h_at, e1)) - -def Laws.help_walk(spec, pre, ns, args, up, subs, path, bs, pos, raw, seen, h_at, h_ns): - hp.at(spec, pre, ns, args, up, subs, path, bs, pos, raw, seen, h_at, - hp.walk(ns, subs, S.find_sub(subs, hp.head(ns)), {==}, args, up, path, bs, pos, raw, seen, h_ns)) - -# the words leave the walker reading the help path `path` -def hp.ended( - +spec: Shake.Cli, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg> -) -> Type: - &kids: List<&2, S.Sub> -> {S.parse.walk(ws, S.parse.start(spec)) - == S.St{S.Help{}, args, up, kids, path, bs, pos, False{}, False{}} : S.St} - -# a help walk that ends reading its path fails with NeedHelp of it -def hp.need( - +spec: Shake.Cli, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - ww: hp.ended(spec, ws, args, up, path, bs, pos) -) -> {Shake.parse(spec, ws) == Fail{S.NeedHelp{path}} : Result<&2, &2, Shake.ParseErr, Shake.Matched>}: - (k1, e1) = ww - Equal.cong(S.St, Result<&2, &2, S.ParseErr, S.Matched>, xx => S.version.res(spec, S.parse.finish(xx)), - S.parse.walk(ws, S.parse.start(spec)), - S.St{S.Help{}, args, up, k1, path, bs, pos, False{}, False{}}, e1) - -def Laws.help_path(spec, pre, ns, args, up, subs, path, bs, pos, h_at, h_ns): - +ph = List.append(&2, String, pre, ["help"]) - hp.need(spec, List.append(&2, String, ph, ns), args, up, List.append(&2, String, path, ns), bs, pos, - Laws.help_walk(spec, ph, ns, args, up, subs, path, bs, pos, False{}, False{}, - Laws.help_step(spec, pre, args, up, subs, path, bs, pos, h_at), h_ns)) - -# a free walker with no long `help` reads `--help` as `help` -def dash_help.step( - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - h_none: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} -) -> {S.parse.step("--help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St}: - ee = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, "help"), None{}, h_none) - %ee : {S.parse.take_long.found(_, "help", None{}, "--help", args, up, subs, path, bs, pos, False{}, False{}) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - {==} - -def Laws.dash_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_none): - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ["--help"]), S.parse.start(spec)), - S.parse.step("--help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}), - S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}}, - kept.stepped(spec, pre, "--help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}, h_at), - dash_help.step(args, up, subs, path, bs, pos, h_none)) - -def Laws.dash_help_path(spec, pre, ns, args, up, subs, path, bs, pos, h_at, h_none, h_ns): - +ph = List.append(&2, String, pre, ["--help"]) - hp.need(spec, List.append(&2, String, ph, ns), args, up, List.append(&2, String, path, ns), bs, pos, - Laws.help_walk(spec, ph, ns, args, up, subs, path, bs, pos, False{}, False{}, - Laws.dash_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_none), h_ns)) - -# a free walker whose command spells neither `-h` nor `--help` reads `-h` as -# `help` -def short_help.step( - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - h_short: {S.by_short(args, "h") == None{} : Maybe<&2, S.Arg>}, - h_long: {S.by_long(args, "help") == None{} : Maybe<&2, S.Arg>} -) -> {S.parse.step("-h", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St}: - e1 = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, "h"), None{}, h_short) - %e1 : {S.parse.take_short.go(Bool.and(True{}, Bool.and(True{}, Bool.and(Maybe.is_none(&2, S.Arg, _), - Maybe.is_none(&2, S.Arg, S.by_long(args, "help"))))), "h", "-h", args, up, subs, path, bs, pos, False{}, - False{}) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_long(args, "help"), None{}, h_long) - %e2 : {S.parse.take_short.go(Bool.and(True{}, Bool.and(True{}, Bool.and(True{}, Maybe.is_none(&2, S.Arg, _)))), - "h", "-h", args, up, subs, path, bs, pos, False{}, False{}) - == S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}} : S.St} - {==} - -def Laws.short_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_short, h_long): - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ["-h"]), S.parse.start(spec)), - S.parse.step("-h", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}), - S.St{S.Help{}, args, up, subs, path, bs, pos, False{}, False{}}, - kept.stepped(spec, pre, "-h", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, False{}}, h_at), - short_help.step(args, up, subs, path, bs, pos, h_short, h_long)) - -def Laws.short_help_path(spec, pre, ns, args, up, subs, path, bs, pos, h_at, h_short, h_long, h_ns): - +ph = List.append(&2, String, pre, ["-h"]) - hp.need(spec, List.append(&2, String, ph, ns), args, up, List.append(&2, String, path, ns), bs, pos, - Laws.help_walk(spec, ph, ns, args, up, subs, path, bs, pos, False{}, False{}, - Laws.short_help_step(spec, pre, args, up, subs, path, bs, pos, h_at, h_short, h_long), h_ns)) - -def Laws.start_state(_name, _about, _version, _args, _subs): - {==} - -# keeping an argument in front of two lists is keeping it in front of the first -def pof.put_app( - keep: Bool, - +hh: S.Arg, - +aa: List<&2, S.Arg>, - +bb: List<&2, S.Arg> -) -> {S.pos_of.put(keep, hh, List.append(&2, S.Arg, aa, bb)) - == List.append(&2, S.Arg, S.pos_of.put(keep, hh, aa), bb) : List<&2, S.Arg>}: - match keep: - case True{}: - {==} - case False{}: - {==} - -def Laws.pos_of_app(xs, ys): - match xs: - case Nil{}: - {==} - case Con{+hh, +tt}: - Equal.trans(List<&2, S.Arg>, S.pos_of.put(S.pos.is(hh), hh, S.pos_of(List.append(&2, S.Arg, tt, ys))), - S.pos_of.put(S.pos.is(hh), hh, List.append(&2, S.Arg, S.pos_of(tt), S.pos_of(ys))), - List.append(&2, S.Arg, S.pos_of.put(S.pos.is(hh), hh, S.pos_of(tt)), S.pos_of(ys)), - Equal.cong(List<&2, S.Arg>, List<&2, S.Arg>, zs => S.pos_of.put(S.pos.is(hh), hh, zs), - S.pos_of(List.append(&2, S.Arg, tt, ys)), List.append(&2, S.Arg, S.pos_of(tt), S.pos_of(ys)), - Laws.pos_of_app(tt, ys)), - pof.put_app(S.pos.is(hh), hh, S.pos_of(tt), S.pos_of(ys))) - -def Laws.pos_of_pos(aa, h_pos): - ee = Equal.sym(Bool, S.pos.is(aa), True{}, h_pos) - %ee : {S.pos_of.put(_, aa, []) == [aa] : List<&2, S.Arg>} - {==} - -def Laws.pos_of_opt(aa, h_opt): - ee = Equal.sym(Bool, S.pos.is(aa), False{}, h_opt) - %ee : {S.pos_of.put(_, aa, []) == [] : List<&2, S.Arg>} - {==} - -# after a positional is bound, `help` reaches the positional it binds -def hw.step( - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>} -) -> {S.parse.step("help", S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, True{}}) - == S.parse.take_pos("help", args, up, subs, path, bs, pos, False{}) : S.St}: - ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, "help"), None{}, h_sub) - %ee : {S.parse.word.go(_, "help", args, up, subs, path, bs, pos, False{}, True{}) - == S.parse.take_pos("help", args, up, subs, path, bs, pos, False{}) : S.St} - {==} - -# `help` taken by the next positional -def hw.full( - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +name: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +dflt: S.Fallback, - +cs: List<&2, String>, - +rest: List<&2, S.Arg>, - h_sub: {S.find_sub(subs, "help") == None{} : Maybe<&2, S.Sub>}, - h_ok: {S.allowed(cs, "help") == True{} : Bool} -) -> {S.parse.step("help", S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, - cs} <> rest, - False{}, True{}}) - == S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}} : S.St}: - +pp = {S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest : List<&2, S.Arg>} - Equal.trans(S.St, S.parse.step("help", S.St{S.Free{}, args, up, subs, path, bs, pp, False{}, True{}}), - S.parse.take_pos("help", args, up, subs, path, bs, pp, False{}), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, - hw.step(args, up, subs, path, bs, pp, h_sub), - pos.take("help", args, up, subs, path, bs, False{}, name, sh, lo, kind, hp, req, dflt, cs, rest, h_ok)) - -def Laws.help_word_next( - spec, - pre, - args, - up, - subs, - path, - bs, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - h_at, - h_sub, - h_ok -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, True{}} - : S.St} - Equal.trans(S.St, S.parse.walk(List.append(&2, String, pre, ["help"]), S.parse.start(spec)), S.parse.step("help", st), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, - kept.stepped(spec, pre, "help", st, h_at), - hw.full(args, up, subs, path, bs, name, sh, lo, kind, hp, req, dflt, cs, rest, h_sub, h_ok)) - -def Laws.help_word_binds( - spec, - pre, - more, - args, - up, - subs, - path, - bs, - name, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - rest, - found, - h_at, - h_sub, - h_ok, - h_done -): - +st = {S.St{S.Free{}, args, up, subs, path, bs, S.Arg{name, sh, lo, kind, hp, req, dflt, cs} <> rest, False{}, True{}} - : S.St} - Laws.kept(spec, pre, more, "help", st, args, up, path, name, "help", bs, found, h_at, - Equal.cong(S.St, Grow.View, xx => Grow.view(xx), S.parse.step("help", st), - S.St{S.Free{}, args, up, subs, path, S.Bind{name, "help"} <> bs, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hp, req, dflt, cs}), - S.Arg{name, sh, lo, kind, hp, req, dflt, cs}, rest), False{}, True{}}, - hw.full(args, up, subs, path, bs, name, sh, lo, kind, hp, req, dflt, cs, rest, h_sub, h_ok)), - h_done) - -# one letter against a failed walker keeps it failed, read or skipped -def cd.letter( - done: Bool, - +cc: Char, - +rest: String, - +orig: String, - +err: S.ParseErr, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> {S.cluster.letter(done, cc, rest, orig, S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen}) - == S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen} : S.St}: - match done: - case True{}: - {==} - case False{}: - {==} - -def Laws.cluster_dead(body, orig, err, args, up, subs, path, bs, pos, raw, seen, done): - match body: - case SNil{}: - {==} - case SCon{+cc, +rest}: - +dd = {S.St{S.Dead{err}, args, up, subs, path, bs, pos, raw, seen} : S.St} - ee = Equal.sym(S.St, S.cluster.letter(done, cc, rest, orig, dd), dd, - cd.letter(done, cc, rest, orig, err, args, up, subs, path, bs, pos, raw, seen)) - %ee : {S.cluster(rest, orig, _, S.cluster.done(done, cc, rest, dd)) == dd : S.St} - Laws.cluster_dead(rest, orig, err, args, up, subs, path, bs, pos, raw, seen, S.cluster.done(done, cc, rest, dd)) - -# one letter of a cluster in a free walker, once the argument it spells is -# known: `take_arg` with the value the letter's kind gives, then the rest, -# ended or not as the kind says -def cl.step( - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +aa: S.Arg, - h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{aa} : Maybe<&2, S.Arg>} -) -> {S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) - == S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, - path, bs, pos, - raw, seen), S.cluster.ends(Some{aa}, rest)) : S.St}: - ee = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, SCon{cc, SNil{}}), Some{aa}, h_found) - %ee : {S.cluster(rest, orig, S.parse.take_arg(_, S.cluster.val(_, rest), orig, args, up, subs, path, bs, pos, raw, - seen), S.cluster.ends(_, rest)) - == S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, path, bs, - pos, raw, seen), S.cluster.ends(Some{aa}, rest)) : S.St} - {==} - -# the same, the other way round, for rewriting a goal about the cluster -def cl.back( - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +aa: S.Arg, - h_found: {S.by_short(args, SCon{cc, SNil{}}) == Some{aa} : Maybe<&2, S.Arg>} -) -> {S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, - path, bs, pos, - raw, seen), S.cluster.ends(Some{aa}, rest)) - == S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}) : S.St}: - Equal.sym(S.St, S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, bs, pos, raw, seen}, False{}), - S.cluster(rest, orig, S.parse.take_arg(Some{aa}, S.cluster.val(Some{aa}, rest), orig, args, up, subs, path, bs, pos, - raw, seen), S.cluster.ends(Some{aa}, rest)), - cl.step(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found)) - -def Laws.short_step(cc, rest, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_help): - +tok = "-" ++ SCon{cc, rest} - +st = {S.St{S.Free{}, args, up, subs, path, bs, pos, False{}, seen} : S.St} - e1 = Equal.sym(Bool, String.eq(tok, "--"), False{}, h_dd) - %e1 : {S.parse.step.end(_, tok, args, up, subs, path, bs, pos, seen) == S.cluster(SCon{cc, rest}, tok, st, - False{}) : S.St} - e4 = Equal.sym(Cmp, String.order.go(SCon{cc, rest}, "elp", LT{}), LT{}, Eq.string_order_lt(SCon{cc, rest}, "elp")) - %e4 : {S.parse.step.help(Bool.and(Cmp.is_eq(_), Bool.not(seen)), tok, args, up, subs, path, bs, pos, seen) - == S.cluster(SCon{cc, rest}, tok, st, False{}) : S.St} - e3 = Equal.sym(Bool, Char.is_eq(cc, '-'), False{}, h_dash) - %e3 : {S.parse.step.kind.go(String.starts_with.go.if("", rest, _), True{}, tok, args, up, subs, path, bs, pos, seen) - == S.cluster(SCon{cc, rest}, tok, st, False{}) : S.St} - e5 = Equal.sym(Bool, S.short_help(SCon{cc, rest}, args, seen), False{}, h_help) - %e5 : {S.parse.take_short.go(_, SCon{cc, rest}, tok, args, up, subs, path, bs, pos, False{}, seen) - == S.cluster(SCon{cc, rest}, tok, st, False{}) : S.St} - {==} - -# a cluster whose walker has failed stays failed, whatever letters are left -def cl.dead( - +rest: String, - +orig: String, - xx: S.St, - +done: Bool, - +err: S.ParseErr, - hh: {Laws.dead_of(xx) == Some{err} : Maybe<&2, S.ParseErr>} -) -> {Laws.dead_of(S.cluster(rest, orig, xx, done)) == Some{err} : Maybe<&2, S.ParseErr>}: - match xx: - case S.St{S.Dead{+ee}, +a, +g, +s, +p, +b, +o, +r, +n}: - ec = Equal.sym(S.St, S.cluster(rest, orig, S.St{S.Dead{ee}, a, g, s, p, b, o, r, n}, done), - S.St{S.Dead{ee}, a, g, s, p, b, o, r, n}, Laws.cluster_dead(rest, orig, ee, a, g, s, p, b, o, r, n, done)) - %ec : {Laws.dead_of(_) == Some{err} : Maybe<&2, S.ParseErr>} - hh - case S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n}: - none.absurd(&1, {Laws.dead_of(S.cluster(rest, orig, S.St{S.Free{}, _a, _g, _s, _p, _b, _o, _r, _n}, - done)) == Some{err} - : Maybe<&2, S.ParseErr>}, err, hh) - case S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}: - none.absurd(&1, {Laws.dead_of(S.cluster(rest, orig, S.St{S.Need{_m}, _a, _g, _s, _p, _b, _o, _r, _n}, done)) - == Some{err} : Maybe<&2, S.ParseErr>}, err, hh) - case S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}: - none.absurd(&1, {Laws.dead_of(S.cluster(rest, orig, S.St{S.Help{}, _a, _g, _s, _p, _b, _o, _r, _n}, - done)) == Some{err} - : Maybe<&2, S.ParseErr>}, err, hh) - -def Laws.letter_flag( - cc, - rest, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_found, - h_once, - h_noeq -): - +aa = {S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs} : S.Arg} - +rhs = {S.cluster(rest, orig, S.St{S.Free{}, args, up, subs, path, S.Bind{nn, "true"} <> bs, pos, raw, seen}, False{}) - : S.St} - e0 = cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {_ == rhs : S.St} - e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) - %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Flag{}, nn, S.cluster.val.flag(_, rest), orig, args, up, - subs, path, - bs, pos, raw, seen), _) == rhs : S.St} - e2 = Equal.sym(Bool, S.bound(bs, nn), False{}, h_once) - %e2 : {S.cluster(rest, orig, S.parse.once(_, S.parse.take_flag(None{}, nn, orig, args, up, subs, path, bs, - pos, raw, seen), - nn, path), False{}) == rhs : S.St} - {==} - -def Laws.letter_flag_eq( - cc, - rest, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_found, - h_once, - h_eq -): - +aa = {S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs} : S.Arg} - +dd = {S.St{S.Dead{S.Unexpected{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - e0 = cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {_ == dd : S.St} - e1 = Equal.sym(Bool, String.starts_with(rest, "="), True{}, h_eq) - %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Flag{}, nn, S.cluster.val.flag(_, rest), orig, args, up, - subs, path, - bs, pos, raw, seen), _) == dd : S.St} - e2 = Equal.sym(Bool, S.bound(bs, nn), False{}, h_once) - %e2 : {S.cluster(rest, orig, S.parse.once(_, dd, nn, path), True{}) == dd : S.St} - Laws.cluster_done(rest, orig, dd) - -def Laws.letter_unknown(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, h_none): - +dd = {S.St{S.Dead{S.UnknownFlag{path, orig}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - e2 = Equal.sym(Maybe<&2, S.Arg>, S.by_short(args, SCon{cc, SNil{}}), None{}, h_none) - %e2 : {S.cluster(rest, orig, - S.parse.take_arg(_, S.cluster.val(_, rest), orig, args, up, subs, path, bs, pos, raw, seen), - S.cluster.ends(_, rest)) == dd : S.St} - Laws.cluster_done(rest, orig, dd) - -def Laws.letter_flag_again( - cc, - rest, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_found, - h_again -): - +aa = {S.Arg{nn, sh, lo, S.Flag{}, hp, req, dflt, cs} : S.Arg} - +vv = S.cluster.val.flag(String.starts_with(rest, "="), rest) - e0 = cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {Laws.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) - %e2 : {Laws.dead_of(S.cluster(rest, orig, S.parse.once(_, S.parse.take_flag(vv, nn, orig, args, up, subs, - path, bs, pos, - raw, seen), nn, path), String.starts_with(rest, "="))) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - cl.dead(rest, orig, S.parse.once(True{}, S.parse.take_flag(vv, nn, orig, args, up, subs, path, bs, pos, raw, - seen), nn, - path), String.starts_with(rest, "="), S.Repeated{path, nn}, - Laws.once_dead(S.parse.take_flag(vv, nn, orig, args, up, subs, path, bs, pos, raw, seen), nn, path)) - -def Laws.letter_opt_again( - cc, - rest, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - hp, - req, - dflt, - cs, - h_found, - h_again -): - +aa = {S.Arg{nn, sh, lo, S.Opt{}, hp, req, dflt, cs} : S.Arg} - +vv = S.short_of.val(String.starts_with(rest, "="), rest) - e0 = cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {Laws.dead_of(_) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - e2 = Equal.sym(Bool, S.bound(bs, nn), True{}, h_again) - %e2 : {Laws.dead_of(S.cluster(rest, orig, S.parse.once(_, S.parse.take_opt(vv, nn, args, up, subs, path, bs, pos, raw, - seen), nn, path), True{})) == Some{S.Repeated{path, nn}} : Maybe<&2, S.ParseErr>} - cl.dead(rest, orig, S.parse.once(True{}, S.parse.take_opt(vv, nn, args, up, subs, path, bs, pos, raw, seen), - nn, path), - True{}, S.Repeated{path, nn}, Laws.once_dead(S.parse.take_opt(vv, nn, args, up, subs, path, bs, pos, raw, - seen), nn, path)) - -# what `lv` claims of an argument kind -def lv.ty( - kk: S.ArgKind, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String -) -> Type: - {S.cluster(rest, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, rest), orig, args, up, subs, path, - bs, pos, raw, seen), - S.cluster.ends.kind(kk, rest)) == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, - rest} <> bs, pos, raw, seen} : S.St} - -# a letter spelling an option that takes a value now, with a glued value its -# choices allow, by the option's kind -def lv( - kk: S.ArgKind, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String, - h_kind: {Laws.takes(kk, S.bound(bs, nn)) == True{} : Bool}, - h_noeq: {String.starts_with(rest, "=") == False{} : Bool}, - h_some: {String.is_empty(rest) == False{} : Bool}, - h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == True{} : Bool} -) -> lv.ty(kk, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn): - match kk: - case S.Opt{}: - +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} - e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) - %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, S.short_of.val(_, rest), orig, args, up, - subs, path, bs, pos, raw, seen), True{}) - == T : S.St} - e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) - %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, - Bool.pick(Maybe<&2, String>, _, None{}, - Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} - e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) - %e3 : {S.cluster(rest, orig, S.parse.once(_, S.parse.take_opt(Some{rest}, nn, args, up, subs, path, bs, - pos, raw, seen), nn, path), True{}) == T - : S.St} - e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), True{}, h_ok) - %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), - True{}) == T : S.St} - Laws.cluster_done(rest, orig, T) - case S.Many{}: - +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} - e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) - %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, S.short_of.val(_, rest), orig, args, up, - subs, path, bs, pos, raw, seen), True{}) - == T : S.St} - e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) - %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, - Bool.pick(Maybe<&2, String>, _, None{}, - Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} - e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), True{}, h_ok) - %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), - True{}) == T : S.St} - Laws.cluster_done(rest, orig, T) - case S.Flag{}: - Empty.absurd(lv.ty(S.Flag{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), - Walk.wk.false_true(h_kind)) - case S.Pos{}: - Empty.absurd(lv.ty(S.Pos{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - case S.Rest{}: - Empty.absurd(lv.ty(S.Rest{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), - Walk.wk.false_true(h_kind)) - -# what `lb` claims of an argument kind -def lb.ty( - kk: S.ArgKind, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String -) -> Type: - {S.cluster(rest, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, rest), orig, args, up, subs, path, - bs, pos, raw, seen), - S.cluster.ends.kind(kk, rest)) == S.St{S.Dead{S.BadValue{path, nn, - rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# the same, with a glued value its choices refuse -def lb( - kk: S.ArgKind, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String, - h_kind: {Laws.takes(kk, S.bound(bs, nn)) == True{} : Bool}, - h_noeq: {String.starts_with(rest, "=") == False{} : Bool}, - h_some: {String.is_empty(rest) == False{} : Bool}, - h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), rest) == False{} : Bool} -) -> lb.ty(kk, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn): - match kk: - case S.Opt{}: - +T = {S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) - %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, S.short_of.val(_, rest), orig, args, up, - subs, path, bs, pos, raw, seen), True{}) - == T : S.St} - e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) - %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Opt{}, nn, - Bool.pick(Maybe<&2, String>, _, None{}, - Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} - e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) - %e3 : {S.cluster(rest, orig, S.parse.once(_, S.parse.take_opt(Some{rest}, nn, args, up, subs, path, bs, - pos, raw, seen), nn, path), True{}) == T - : S.St} - e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), False{}, h_ok) - %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), - True{}) == T : S.St} - Laws.cluster_done(rest, orig, T) - case S.Many{}: - +T = {S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - e1 = Equal.sym(Bool, String.starts_with(rest, "="), False{}, h_noeq) - %e1 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, S.short_of.val(_, rest), orig, args, up, - subs, path, bs, pos, raw, seen), True{}) - == T : S.St} - e2 = Equal.sym(Bool, String.is_empty(rest), False{}, h_some) - %e2 : {S.cluster(rest, orig, S.parse.take_arg.kind(S.Many{}, nn, - Bool.pick(Maybe<&2, String>, _, None{}, - Some{rest}), orig, args, up, subs, path, bs, pos, raw, seen), True{}) == T : S.St} - e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), rest), False{}, h_ok) - %e4 : {S.cluster(rest, orig, S.parse.take_choice(_, nn, rest, args, up, subs, path, bs, pos, raw, seen), - True{}) == T : S.St} - Laws.cluster_done(rest, orig, T) - case S.Flag{}: - Empty.absurd(lb.ty(S.Flag{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), - Walk.wk.false_true(h_kind)) - case S.Pos{}: - Empty.absurd(lb.ty(S.Pos{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - case S.Rest{}: - Empty.absurd(lb.ty(S.Rest{}, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn), - Walk.wk.false_true(h_kind)) - -# what `ln` claims of an argument kind -def ln.ty( - kk: S.ArgKind, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String -) -> Type: - {S.cluster(SNil{}, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, SNil{}), orig, args, up, subs, - path, bs, pos, raw, seen), - S.cluster.ends.kind(kk, SNil{})) == S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} - -# a letter spelling an option that takes a value now, last in its word -def ln( - kk: S.ArgKind, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String, - h_kind: {Laws.takes(kk, S.bound(bs, nn)) == True{} : Bool} -) -> ln.ty(kk, orig, args, up, subs, path, bs, pos, raw, seen, nn): - match kk: - case S.Opt{}: - e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) - %e3 : {S.parse.once(_, S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen}, nn, - path) == S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} - {==} - case S.Many{}: - {==} - case S.Flag{}: - Empty.absurd(ln.ty(S.Flag{}, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - case S.Pos{}: - Empty.absurd(ln.ty(S.Pos{}, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - case S.Rest{}: - Empty.absurd(ln.ty(S.Rest{}, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - -# what `le` claims of an argument kind -def le.ty( - kk: S.ArgKind, - +vv: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String -) -> Type: - {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(kk, nn, S.cluster.val.kind(kk, "=" ++ vv), orig, args, up, - subs, path, bs, pos, raw, seen), - S.cluster.ends.kind(kk, "=" ++ vv)) == S.St{S.Free{}, args, up, subs, path, S.Bind{nn, - vv} <> bs, pos, raw, seen} : S.St} - -# a letter spelling an option that takes a value now, then `=` and a value -def le( - kk: S.ArgKind, - +vv: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +bs: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +nn: String, - h_kind: {Laws.takes(kk, S.bound(bs, nn)) == True{} : Bool}, - h_ok: {S.allowed(S.parse.choices(S.by_name(args, nn)), vv) == True{} : Bool} -) -> le.ty(kk, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn): - match kk: - case S.Opt{}: - +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} - ep = Equal.sym(Bool, String.starts_with(vv, ""), True{}, Laws.prefix_empty(vv)) - %ep : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Opt{}, nn, S.short_of.val(_, "=" ++ vv), orig, args, up, - subs, path, bs, pos, raw, seen), True{}) == T : S.St} - ed = Equal.sym(String, String.drop(vv, 0n), vv, Laws.drop_zero(vv)) - %ed : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Opt{}, nn, Some{_}, orig, args, up, subs, path, - bs, pos, raw, seen), True{}) == T - : S.St} - e3 = Equal.sym(Bool, S.bound(bs, nn), False{}, Walk.wk.not_true(S.bound(bs, nn), h_kind)) - %e3 : {S.cluster("=" ++ vv, orig, S.parse.once(_, S.parse.take_opt(Some{vv}, nn, args, up, subs, path, bs, - pos, raw, seen), nn, path), True{}) == T - : S.St} - e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) - %e4 : {S.cluster("=" ++ vv, orig, S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, raw, - seen), True{}) == T : S.St} - Laws.cluster_done("=" ++ vv, orig, T) - case S.Many{}: - +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} - ep = Equal.sym(Bool, String.starts_with(vv, ""), True{}, Laws.prefix_empty(vv)) - %ep : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Many{}, nn, S.short_of.val(_, "=" ++ vv), orig, - args, up, - subs, path, bs, pos, raw, seen), True{}) == T : S.St} - ed = Equal.sym(String, String.drop(vv, 0n), vv, Laws.drop_zero(vv)) - %ed : {S.cluster("=" ++ vv, orig, S.parse.take_arg.kind(S.Many{}, nn, Some{_}, orig, args, up, subs, path, - bs, pos, raw, seen), True{}) == T - : S.St} - e4 = Equal.sym(Bool, S.allowed(S.parse.choices(S.by_name(args, nn)), vv), True{}, h_ok) - %e4 : {S.cluster("=" ++ vv, orig, S.parse.take_choice(_, nn, vv, args, up, subs, path, bs, pos, raw, - seen), True{}) == T : S.St} - Laws.cluster_done("=" ++ vv, orig, T) - case S.Flag{}: - Empty.absurd(le.ty(S.Flag{}, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - case S.Pos{}: - Empty.absurd(le.ty(S.Pos{}, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - case S.Rest{}: - Empty.absurd(le.ty(S.Rest{}, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn), Walk.wk.false_true(h_kind)) - -def Laws.letter_value( - cc, - rest, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - h_found, - h_kind, - h_noeq, - h_some, - h_ok -): - +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} - +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, rest} <> bs, pos, raw, seen} : S.St} - e0 = cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {_ == T : S.St} - lv(kind, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind, h_noeq, h_some, h_ok) - -def Laws.letter_value_bad( - cc, - rest, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - h_found, - h_kind, - h_noeq, - h_some, - h_bad -): - +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} - +T = {S.St{S.Dead{S.BadValue{path, nn, rest}}, args, up, subs, path, bs, pos, raw, seen} : S.St} - e0 = cl.back(cc, rest, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {_ == T : S.St} - lb(kind, rest, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind, h_noeq, h_some, h_bad) - -def Laws.letter_value_next( - cc, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - h_found, - h_kind -): - +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} - +T = {S.St{S.Need{nn}, args, up, subs, path, bs, pos, raw, seen} : S.St} - e0 = cl.back(cc, SNil{}, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {_ == T : S.St} - ln(kind, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind) - -def Laws.letter_value_eq( - cc, - vv, - orig, - args, - up, - subs, - path, - bs, - pos, - raw, - seen, - nn, - sh, - lo, - kind, - hp, - req, - dflt, - cs, - h_found, - h_kind, - h_ok -): - +aa = {S.Arg{nn, sh, lo, kind, hp, req, dflt, cs} : S.Arg} - +T = {S.St{S.Free{}, args, up, subs, path, S.Bind{nn, vv} <> bs, pos, raw, seen} : S.St} - e0 = cl.back(cc, "=" ++ vv, orig, args, up, subs, path, bs, pos, raw, seen, aa, h_found) - %e0 : {_ == T : S.St} - le(kind, vv, orig, args, up, subs, path, bs, pos, raw, seen, nn, h_kind, h_ok) - -def Laws.plain_word_step(tok, args, up, subs, path, bs, pos, seen, h_dd, h_dash, h_help): - +rhs = S.parse.word.go(S.find_sub(subs, tok), tok, args, up, subs, path, bs, pos, False{}, seen) - e1 = Equal.sym(Bool, String.eq(tok, "--"), False{}, h_dd) - %e1 : {S.parse.step.end(_, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - e2 = Equal.sym(Bool, Bool.and(String.eq(tok, "help"), Bool.not(seen)), False{}, h_help) - %e2 : {S.parse.step.help(_, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - e3 = Equal.sym(Bool, String.starts_with(tok, "--"), False{}, Walk.wk.dash(tok, h_dash)) - %e3 : {S.parse.step.kind.go(_, String.starts_with(tok, "-"), tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - e4 = Equal.sym(Bool, String.starts_with(tok, "-"), False{}, h_dash) - %e4 : {S.parse.step.kind.go(False{}, _, tok, args, up, subs, path, bs, pos, seen) == rhs : S.St} - {==} - -def Laws.dash_word_step(_args, _up, _subs, _path, _bs, _pos, _seen): - {==} - -# the help walk renders the page it reaches -def hl.go( - path: List<&2, String>, - how: S.How -) -> {S.help.go(path, how) == Laws.page(Laws.reach(path, how)) : String}: - match path: - case Nil{}: - match how: - case S.How{_b, _a, _v, _g, _s, _u}: - {==} - case Con{+hh, tt}: - hl.go(tt, S.help.step(how, hh)) - -# the usage walk reads the usage line of the page it reaches -def hl.usage( - path: List<&2, String>, - how: S.How -) -> {S.usage.go(path, how) == Laws.usage_line(Laws.reach(path, how)) : String}: - match path: - case Nil{}: - match how: - case S.How{_b, _a, _v, _g, _s, _u}: - {==} - case Con{+hh, tt}: - hl.usage(tt, S.help.step(how, hh)) - -def Laws.help_page(spec, path): - match spec: - case S.Cli{+name, +about, +version, +args, +subs}: - hl.go(path, S.How{name, about, version, args, subs, name}) - -def Laws.reach_child(bin, about, version, args, subs, usage, name, sn, sa, sargs, ssubs, h_sub): - ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, name), Some{S.Sub{sn, sa, sargs, ssubs}}, h_sub) - %ee : {S.help.step.go(_, bin, about, version, args, subs, name, usage) - == S.How{sn, sa, None{}, sargs, ssubs, usage ++ " " ++ name} : S.How} - {==} - -def Laws.reach_skip(bin, about, version, args, subs, usage, name, h_sub): - ee = Equal.sym(Maybe<&2, S.Sub>, S.find_sub(subs, name), None{}, h_sub) - %ee : {S.help.step.go(_, bin, about, version, args, subs, name, usage) == S.How{bin, about, version, args, - subs, usage} - : S.How} - {==} - -def Laws.err_path_at(_at, _word, _name, _value): - {==} - -# the usage line an error's text shows is the one its command's page shows -def et.usage( - spec: Shake.Cli, - +path: List<&2, String> -) -> {S.usage.at(spec, path) == Laws.usage_line(Laws.reach(path, Laws.root(spec))) : String}: - match spec: - case S.Cli{+name, +about, +version, +args, +subs}: - hl.usage(path, S.How{name, about, version, args, subs, name}) - -# an error's text, with its usage line read as its page's -def et.wrap( - +spec: Shake.Cli, - +at: List<&2, String>, - +msg: String -) -> {S.err.wrap(spec, at, msg) - == msg ++ "\n\n" ++ Laws.usage_line(Laws.reach(at, Laws.root(spec))) ++ "\n\n" ++ S.err.hint(at) : String}: - Equal.cong(String, String, uu => msg ++ "\n\n" ++ uu ++ "\n\n" ++ S.err.hint(at), S.usage.at(spec, at), - Laws.usage_line(Laws.reach(at, Laws.root(spec))), et.usage(spec, at)) - -# what `err_text_usage` claims of an error -def et.ty(+spec: Shake.Cli, +err: Shake.ParseErr) -> Type: - &msg: String -> {Shake.err_text(spec, err) - == msg ++ "\n\n" ++ Laws.usage_line(Laws.reach(Shake.err_path(err), Laws.root(spec))) ++ "\n\n" ++ - S.err.hint(Shake.err_path(err)) : String} - -# Unit for None, Empty for Some: a motive that tells them apart -def et.none_ty(mm: Maybe<&2, List<&2, String>>) -> Type: - match mm: - case None{}: - Unit - case Some{_p}: - Empty - -# a request for help has a help path -def et.some_none(+path: List<&2, String>, hh: {Some{path} == None{} : Maybe<&2, List<&2, String>>}) -> Empty: - ee = Equal.sym(Maybe<&2, List<&2, String>>, Some{path}, None{}, hh) - %ee : et.none_ty(_) - Unit{} - -def Laws.err_text_usage(spec, err, h_err, h_ver): - match err: - case S.UnknownFlag{+at, +word}: - ("error: unexpected argument '" ++ word ++ "' found" ++ S.sug.tip(S.sug.word(spec, at, word)), - et.wrap(spec, at, "error: unexpected argument '" ++ word ++ "' found" ++ S.sug.tip(S.sug.word(spec, at, word)))) - case S.Missing{+at, +name}: - ("error: the following required argument was not provided:\n " ++ S.err.name(spec, at, name), - et.wrap(spec, at, "error: the following required argument was not provided:\n " ++ S.err.name(spec, at, name))) - case S.NoValue{+at, +name}: - ("error: a value is required for '" ++ S.err.name(spec, at, name) ++ "' but none was supplied", - et.wrap(spec, at, "error: a value is required for '" ++ S.err.name(spec, at, name) ++ "' but none was supplied")) - case S.BadValue{+at, +name, +value}: - ("error: invalid value '" ++ value ++ "' for '" ++ S.err.name(spec, at, name) ++ "'" ++ - S.err.values(S.err.find(spec, at, name)), - et.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.name(spec, at, name) ++ "'" ++ - S.err.values(S.err.find(spec, at, name)))) - case S.NeedHelp{+path}: - Empty.absurd(et.ty(spec, S.NeedHelp{path}), et.some_none(path, h_err)) - case S.NeedVersion{}: - Empty.absurd(et.ty(spec, S.NeedVersion{}), Walk.wk.true_false(h_ver)) - case S.Unexpected{+at, +arg}: - ("error: unexpected argument '" ++ arg ++ "' found", - et.wrap(spec, at, "error: unexpected argument '" ++ arg ++ "' found")) - case S.Repeated{+at, +name}: - ("error: the argument '" ++ S.err.name(spec, at, name) ++ "' cannot be used multiple times", - et.wrap(spec, at, "error: the argument '" ++ S.err.name(spec, at, name) ++ "' cannot be used multiple times")) - -# a string with nothing after it is itself -def hs.nil(xs: String) -> {xs ++ "" == xs : String}: - match xs: - case SNil{}: - {==} - case SCon{+hh, +tt}: - Equal.cong(String, String, ys => SCon{hh, ys}, tt ++ "", tt, hs.nil(tt)) - -# the Commands lines are the subcommands' lines, then `help`'s -def hs.cmds( - +subs: List<&2, S.Sub>, - +ww: Nat -) -> {S.help.cmd_lines(subs, ww) == Laws.sub_lines(List.append(&2, S.Sub, subs, [Laws.help_sub()]), ww) : String}: - match subs: - case Nil{}: - Equal.sym(String, S.help.cmd_line("help", "Print this message or the help of the given subcommand(s)", ww) ++ "", - S.help.cmd_line("help", "Print this message or the help of the given subcommand(s)", ww), - hs.nil(S.help.cmd_line("help", "Print this message or the help of the given subcommand(s)", ww))) - case Con{+hh, +tt}: - Equal.cong(String, String, xs => S.help.cmd_line(S.help.sub_name(hh), S.help.sub_about(hh), ww) ++ xs, - S.help.cmd_lines(tt, ww), Laws.sub_lines(List.append(&2, S.Sub, tt, [Laws.help_sub()]), ww), hs.cmds(tt, ww)) - -def Laws.cmds_listed(subs): - Equal.cong(String, String, xs => "Commands:\n" ++ xs ++ "\n", S.help.cmd_lines(subs, S.help.cmd_width(subs, 0n)), - Laws.sub_lines(List.append(&2, S.Sub, subs, [Laws.help_sub()]), S.help.cmd_width(subs, 0n)), - hs.cmds(subs, S.help.cmd_width(subs, 0n))) - -# an option's line, then the lines after it -def hs.opt_next( - +aa: S.Arg, - +tt: List<&2, S.Arg>, - +ww: Nat, - ee: {S.help.opt_lines(tt, ww) == Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, tt), ww) : String} -) -> {S.help.opt_line(aa, ww) ++ "\n" ++ S.help.opt_lines(tt, ww) - == S.help.opt_line(aa, ww) ++ "\n" ++ Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, tt), ww) : String}: - Equal.cong(String, String, xs => S.help.opt_line(aa, ww) ++ "\n" ++ xs, S.help.opt_lines(tt, ww), - Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, tt), ww), ee) - -# the Options lines are the lines of the flags and options, in order -def hs.opts( - +args: List<&2, S.Arg>, - +ww: Nat -) -> {S.help.opt_lines(args, ww) == Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, args), ww) : String}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+n, +s, +l, kind, +h, +r, +d, +c}: - match kind: - case S.Flag{}: - hs.opt_next(S.Arg{n, s, l, S.Flag{}, h, r, d, c}, tt, ww, hs.opts(tt, ww)) - case S.Opt{}: - hs.opt_next(S.Arg{n, s, l, S.Opt{}, h, r, d, c}, tt, ww, hs.opts(tt, ww)) - case S.Many{}: - hs.opt_next(S.Arg{n, s, l, S.Many{}, h, r, d, c}, tt, ww, hs.opts(tt, ww)) - case S.Pos{}: - hs.opts(tt, ww) - case S.Rest{}: - hs.opts(tt, ww) - -def Laws.opts_listed(args): - Equal.cong(String, String, xs => S.help.opts.go(xs), S.help.opt_lines(args, S.help.opt_width(args, 0n)), - Laws.opt_lines(List.filter(~S.Arg, ~Laws.is_opt, args), S.help.opt_width(args, 0n)), - hs.opts(args, S.help.opt_width(args, 0n))) - -# a positional's hint, then the hints after it -def hs.pos_next( - +aa: S.Arg, - +tt: List<&2, S.Arg>, - ee: {S.help.pos(tt) == Laws.marks(List.filter(~S.Arg, ~S.pos.is, tt)) : String} -) -> {S.help.pos.one(aa) ++ S.help.pos(tt) == S.help.pos.one(aa) ++ Laws.marks(List.filter(~S.Arg, ~S.pos.is, tt)) - : String}: - Equal.cong(String, String, xs => S.help.pos.one(aa) ++ xs, S.help.pos(tt), - Laws.marks(List.filter(~S.Arg, ~S.pos.is, tt)), ee) - -# the usage hints are the marks of the positionals, in order -def hs.pos(+args: List<&2, S.Arg>) -> {S.help.pos(args) == Laws.marks(List.filter(~S.Arg, ~S.pos.is, args)) : String}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+n, +s, +l, kind, +h, r, +d, +c}: - match kind: - case S.Flag{}: - hs.pos(tt) - case S.Opt{}: - hs.pos(tt) - case S.Many{}: - hs.pos(tt) - case S.Pos{}: - match r: - case True{}: - hs.pos_next(S.Arg{n, s, l, S.Pos{}, h, True{}, d, c}, tt, hs.pos(tt)) - case False{}: - hs.pos_next(S.Arg{n, s, l, S.Pos{}, h, False{}, d, c}, tt, hs.pos(tt)) - case S.Rest{}: - match r: - case True{}: - hs.pos_next(S.Arg{n, s, l, S.Rest{}, h, True{}, d, c}, tt, hs.pos(tt)) - case False{}: - hs.pos_next(S.Arg{n, s, l, S.Rest{}, h, False{}, d, c}, tt, hs.pos(tt)) - -def Laws.usage_listed(name, args, subs): - Equal.cong(String, String, xs => "Usage: " ++ name ++ S.help.opts_hint(args) ++ xs ++ S.help.cmd_hint(subs), - S.help.pos(args), Laws.marks(List.filter(~S.Arg, ~S.pos.is, args)), hs.pos(args)) - -# two report lists, each equal to another, one after the other -def sc.both( - +x1: List<&2, K.SpecErr>, - +y1: List<&2, K.SpecErr>, - +x2: List<&2, K.SpecErr>, - +y2: List<&2, K.SpecErr>, - e1: {x1 == y1 : List<&2, K.SpecErr>}, - e2: {x2 == y2 : List<&2, K.SpecErr>} -) -> {K.both(x1, x2) == K.both(y1, y2) : List<&2, K.SpecErr>}: - Equal.trans(List<&2, K.SpecErr>, K.both(x1, x2), K.both(y1, x2), K.both(y1, y2), - Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, zz => K.both(zz, x2), x1, y1, e1), - Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, zz => K.both(y1, zz), x2, y2, e2)) - -# a listed-or-not answer, then the rest's -def sc.ok.at( - bb: Bool, - +r1: Bool, - +r2: Bool, - ee: {r1 == r2 : Bool} -) -> {S.allowed.ok.at(bb, r1) == List.any.if(_u => r2, bb) : Bool}: - match bb: - case True{}: - {==} - case False{}: - ee - -# a value is listed among choices when the choices contain it -def sc.ok( - cs: List<&2, String>, - +vv: String -) -> {S.allowed.ok(cs, vv) == List.contains(~String, ~String.eq, cs, vv) : Bool}: - match cs: - case Nil{}: - {==} - case Con{+hh, +tt}: - sc.ok.at(String.eq(hh, vv), S.allowed.ok(tt, vv), List.contains(~String, ~String.eq, tt, vv), sc.ok(tt, vv)) - -# the default's report is the one the law names -def sc.dflt( - +path: List<&2, String>, - +name: String, - +cs: List<&2, String>, - dflt: Maybe<&2, String> -) -> {K.args.default(dflt, cs, path, name) == Laws.off_choice(path, name, cs, dflt) : List<&2, K.SpecErr>}: - match dflt: - case None{}: - {==} - case Some{+vv}: - Equal.cong(Bool, List<&2, K.SpecErr>, - bb => K.one(Bool.and(Bool.not(List.is_empty(&2, String, cs)), Bool.not(bb)), - K.DefaultNotChoice{path, name, vv}), - S.allowed.ok(cs, vv), List.contains(~String, ~String.eq, cs, vv), sc.ok(cs, vv)) - -# the argument reports, from the arguments before -def sc.args( - args: List<&2, S.Arg>, - +path: List<&2, String>, - +before: List<&2, S.Arg> -) -> {K.args.dups(args, path, Laws.arg_names(before), Laws.arg_shorts(before), Laws.arg_longs(before)) - == Laws.args_reports(args, path, before) : List<&2, K.SpecErr>}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+name, +short, +long, +kind, +help, +req, S.Fallback{+ev, +dflt}, +cs}: - Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, - zz => K.both( - K.one(List.contains(~String, ~String.eq, Laws.arg_names(before), name), K.SameName{path, name}), - K.both(K.one(K.spelled.seen(short, Laws.arg_shorts(before)), K.SameShort{path, S.text.of(short, "")}), - K.both(K.one(K.spelled.seen(long, Laws.arg_longs(before)), K.SameLong{path, S.text.of(long, "")}), - zz))), - K.both(K.args.default(dflt, cs, path, name), - K.args.dups(tt, path, name <> Laws.arg_names(before), K.spelled.add(short, Laws.arg_shorts(before)), - K.spelled.add(long, Laws.arg_longs(before)))), - K.both(Laws.off_choice(path, name, cs, dflt), - Laws.args_reports(tt, path, S.Arg{name, short, long, kind, help, req, S.Fallback{ev, dflt}, - cs} <> before)), - sc.both(K.args.default(dflt, cs, path, name), Laws.off_choice(path, name, cs, dflt), - K.args.dups(tt, path, name <> Laws.arg_names(before), K.spelled.add(short, Laws.arg_shorts(before)), - K.spelled.add(long, Laws.arg_longs(before))), - Laws.args_reports(tt, path, S.Arg{name, short, long, kind, help, req, S.Fallback{ev, dflt}, - cs} <> before), - sc.dflt(path, name, cs, dflt), - sc.args(tt, path, S.Arg{name, short, long, kind, help, req, S.Fallback{ev, dflt}, cs} <> before))) - -# the positional reports, from the positionals before -def sc.pos( - pos: List<&2, S.Arg>, - +path: List<&2, String>, - +before: List<&2, S.Arg> -) -> {K.pos.order(pos, path, Laws.any_optional(before)) == Laws.pos_reports(pos, path, before) - : List<&2, K.SpecErr>}: - match pos: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+name, +short, +long, +kind, +help, +req, +dflt, +cs}: - Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, - zz => K.both( - K.one(Bool.and(S.rest.is.kind(kind), Bool.not(List.is_empty(&2, S.Arg, tt))), K.RestNotLast{path, name}), - K.both(K.one(Bool.and(req, Laws.any_optional(before)), K.RequiredAfterOptional{path, name}), zz)), - K.pos.order(tt, path, Bool.or(Laws.any_optional(before), Bool.not(req))), - Laws.pos_reports(tt, path, S.Arg{name, short, long, kind, help, req, dflt, cs} <> before), - sc.pos(tt, path, S.Arg{name, short, long, kind, help, req, dflt, cs} <> before)) - -# the subcommand reports, from the subcommands before -def sc.subs( - subs: List<&2, S.Sub>, - +path: List<&2, String>, - +before: List<&2, S.Sub> -) -> {K.subs.dups(subs, path, Laws.sub_names(before)) == Laws.sub_reports(subs, path, before) : List<&2, K.SpecErr>}: - match subs: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Sub{+name, +about, +args, +kids}: - Equal.cong(List<&2, K.SpecErr>, List<&2, K.SpecErr>, - zz => K.both(K.one(List.contains(~String, ~String.eq, Laws.sub_names(before), name), K.SameSub{path, name}), - K.both(K.one(String.eq(name, "help"), K.HelpSub{path}), zz)), - K.subs.dups(tt, path, name <> Laws.sub_names(before)), - Laws.sub_reports(tt, path, S.Sub{name, about, args, kids} <> before), - sc.subs(tt, path, S.Sub{name, about, args, kids} <> before)) - -# one command's reports -def sc.cmd( - +path: List<&2, String>, - +args: List<&2, S.Arg>, - +subs: List<&2, S.Sub> -) -> {K.cmd(path, args, subs) == Laws.cmd_reports(path, args, subs) : List<&2, K.SpecErr>}: - sc.both(K.args.dups(args, path, [], [], []), Laws.args_reports(args, path, []), - K.both(K.pos.order(S.pos_of(args), path, False{}), K.subs.dups(subs, path, [])), - K.both(Laws.pos_reports(S.pos_of(args), path, []), Laws.sub_reports(subs, path, [])), - sc.args(args, path, []), - sc.both(K.pos.order(S.pos_of(args), path, False{}), Laws.pos_reports(S.pos_of(args), path, []), - K.subs.dups(subs, path, []), Laws.sub_reports(subs, path, []), - sc.pos(S.pos_of(args), path, []), sc.subs(subs, path, []))) - -# appending names is associative -def sc.app( - aa: List<&2, String>, - +bb: List<&2, String>, - +cc: List<&2, String> -) -> {List.append(&2, String, List.append(&2, String, aa, bb), cc) - == List.append(&2, String, aa, List.append(&2, String, bb, cc)) : List<&2, String>}: - match aa: - case Nil{}: - {==} - case Con{+hh, +tt}: - Equal.cong(List<&2, String>, List<&2, String>, xs => hh <> xs, - List.append(&2, String, List.append(&2, String, tt, bb), cc), - List.append(&2, String, tt, List.append(&2, String, bb, cc)), sc.app(tt, bb, cc)) - -# reversing names onto a list is reversing them, then appending it -def sc.rev_go( - xs: List<&2, String>, - +acc: List<&2, String> -) -> {List.reverse.go(&2, String, xs, acc) == List.append(&2, String, List.reverse.go(&2, String, xs, []), acc) - : List<&2, String>}: - match xs: - case Nil{}: - {==} - case Con{+hh, +tt}: - Equal.trans(List<&2, String>, List.reverse.go(&2, String, tt, hh <> acc), - List.append(&2, String, List.reverse.go(&2, String, tt, []), hh <> acc), - List.append(&2, String, List.reverse.go(&2, String, tt, [hh]), acc), - sc.rev_go(tt, hh <> acc), - Equal.sym(List<&2, String>, List.append(&2, String, List.reverse.go(&2, String, tt, [hh]), acc), - List.append(&2, String, List.reverse.go(&2, String, tt, []), hh <> acc), - Equal.trans(List<&2, String>, - List.append(&2, String, List.reverse.go(&2, String, tt, [hh]), acc), - List.append(&2, String, List.append(&2, String, List.reverse.go(&2, String, tt, []), [hh]), acc), - List.append(&2, String, List.reverse.go(&2, String, tt, []), hh <> acc), - Equal.cong(List<&2, String>, List<&2, String>, ys => List.append(&2, String, ys, acc), - List.reverse.go(&2, String, tt, [hh]), - List.append(&2, String, List.reverse.go(&2, String, tt, []), [hh]), sc.rev_go(tt, [hh])), - sc.app(List.reverse.go(&2, String, tt, []), [hh], acc)))) - -# a reversed path with a name in front is the path, then the name -def sc.rev( - +name: String, - back: List<&2, String> -) -> {List.reverse(&2, String, name <> back) == List.append(&2, String, List.reverse(&2, String, back), [name]) - : List<&2, String>}: - sc.rev_go(back, [name]) - -# every subcommand's reports, under a reversed path -def sc.forest( - subs: List<&2, S.Sub>, - +back: List<&2, String> -) -> {K.forest(subs, back) == Laws.below(subs, List.reverse(&2, String, back)) : List<&2, K.SpecErr>}: - match subs: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Sub{+name, _a, +args, +kids}: - +rn = List.reverse(&2, String, name <> back) - +rp = List.append(&2, String, List.reverse(&2, String, back), [name]) - sc.both(K.cmd(rn, args, kids), Laws.cmd_reports(rp, args, kids), - K.both(K.forest(kids, name <> back), K.forest(tt, back)), - K.both(Laws.below(kids, rp), Laws.below(tt, List.reverse(&2, String, back))), - Equal.trans(List<&2, K.SpecErr>, K.cmd(rn, args, kids), Laws.cmd_reports(rn, args, kids), - Laws.cmd_reports(rp, args, kids), sc.cmd(rn, args, kids), - Equal.cong(List<&2, String>, List<&2, K.SpecErr>, pp => Laws.cmd_reports(pp, args, kids), rn, rp, - sc.rev(name, back))), - sc.both(K.forest(kids, name <> back), Laws.below(kids, rp), K.forest(tt, back), - Laws.below(tt, List.reverse(&2, String, back)), - Equal.trans(List<&2, K.SpecErr>, K.forest(kids, name <> back), Laws.below(kids, rn), - Laws.below(kids, rp), sc.forest(kids, name <> back), - Equal.cong(List<&2, String>, List<&2, K.SpecErr>, pp => Laws.below(kids, pp), rn, rp, - sc.rev(name, back))), - sc.forest(tt, back))) - -def Laws.check_listed(_name, _about, _version, args, subs): - sc.both(K.cmd([], args, subs), Laws.cmd_reports([], args, subs), K.forest(subs, []), Laws.below(subs, []), - sc.cmd([], args, subs), sc.forest(subs, [])) - -# ---- SHAKE-PARSE-1: every value bound is a piece of a word, `true` or a default - -# anything or true is true -def gv.or_t(bb: Bool) -> {Bool.or(bb, True{}) == True{} : Bool}: - match bb: - case True{}: - {==} - case False{}: - {==} - -# both halves of a true `and` are true -def gv.and_tt( - aa: Bool, - +bb: Bool, - ha: {aa == True{} : Bool}, - hb: {bb == True{} : Bool} -) -> {Bool.and(aa, bb) == True{} : Bool}: - match aa: - case True{}: - hb - case False{}: - Empty.absurd({Bool.and(False{}, bb) == True{} : Bool}, Walk.wk.false_true(ha)) - -# the left half of a true `and` -def gv.and_l(aa: Bool, bb: Bool, +hh: {Bool.and(aa, bb) == True{} : Bool}) -> {aa == True{} : Bool}: - match aa: - case True{}: - {==} - case False{}: - Empty.absurd({False{} == True{} : Bool}, Walk.wk.false_true(hh)) - -# the right half of a true `and` -def gv.and_r(aa: Bool, +bb: Bool, +hh: {Bool.and(aa, bb) == True{} : Bool}) -> {bb == True{} : Bool}: - match aa: - case True{}: - hh - case False{}: - Empty.absurd({bb == True{} : Bool}, Walk.wk.false_true(hh)) - -# a word is what is left of itself -def gv.ends_self(ww: String) -> {Laws.ends(ww, ww) == True{} : Bool}: - match ww: - case SNil{}: - {==} - case SCon{+hh, +tt}: - ee = Equal.sym(Bool, String.eq(SCon{hh, tt}, SCon{hh, tt}), True{}, Eq.string_eq_self(SCon{hh, tt})) - %ee : {Bool.or(_, Laws.ends(tt, SCon{hh, tt})) == True{} : Bool} - {==} - -# what is left of a word's tail is left of the word -def gv.ends_tail( - +cc: Char, - +tt: String, - +vv: String, - +hh: {Laws.ends(tt, vv) == True{} : Bool} -) -> {Laws.ends(SCon{cc, tt}, vv) == True{} : Bool}: - ee = Equal.sym(Bool, Laws.ends(tt, vv), True{}, hh) - %ee : {Bool.or(String.eq(vv, SCon{cc, tt}), _) == True{} : Bool} - gv.or_t(String.eq(vv, SCon{cc, tt})) - -# dropping nothing leaves the string -def gv.drop0(ss: String) -> {String.drop(ss, 0n) == ss : String}: - match ss: - case SNil{}: - {==} - case SCon{_c, _t}: - {==} - -# whether every piece of `ww` is a piece of one of the words -def gv.sufs(ww: String, +ws: List<&2, String>) -> Bool: - match ww: - case SNil{}: - Laws.piece(ws, "") - case SCon{+cc, +tt}: - Bool.and(Laws.piece(ws, SCon{cc, tt}), gv.sufs(tt, ws)) - -# every piece of `ww` is a piece of one of the words -def gv.In(+ww: String, +ws: List<&2, String>) -> Type: - {gv.sufs(ww, ws) == True{} : Bool} - -# a word's tail is within the words when the word is -def gv.in_tail(+cc: Char, +tt: String, +ws: List<&2, String>, hh: gv.In(SCon{cc, tt}, ws)) -> gv.In(tt, ws): - gv.and_r(Laws.piece(ws, SCon{cc, tt}), gv.sufs(tt, ws), hh) - -# a word with chars dropped from its front is within the words when it is -def gv.in_drop(ww: String, kk: Nat, +ws: List<&2, String>, hh: gv.In(ww, ws)) -> gv.In(String.drop(ww, kk), ws): - match ww kk: - case SNil{} _: - hh - case SCon{+cc, +tt} 0n: - hh - case SCon{+cc, +tt} 1n+pp: - gv.in_drop(tt, pp, ws, gv.in_tail(cc, tt, ws, hh)) - -# a word within the words is a piece of them -def gv.in_piece(ww: String, +ws: List<&2, String>, hh: gv.In(ww, ws)) -> {Laws.piece(ws, ww) == True{} : Bool}: - match ww: - case SNil{}: - hh - case SCon{+cc, +tt}: - gv.and_l(Laws.piece(ws, SCon{cc, tt}), gv.sufs(tt, ws), hh) - -# what is left of a word once some chars are dropped is left of it -def gv.ends_drop(ww: String, kk: Nat) -> {Laws.ends(ww, String.drop(ww, kk)) == True{} : Bool}: - match ww kk: - case SNil{} _: - {==} - case SCon{+cc, +tt} 0n: - gv.ends_self(SCon{cc, tt}) - case SCon{+cc, +tt} 1n+(+pp): - gv.ends_tail(cc, tt, String.drop(tt, pp), gv.ends_drop(tt, pp)) - -# what is left of a word of a list is a piece of the list -def gv.mid_piece( - pre: List<&2, String>, - +tok: String, - +rest: List<&2, String>, - +vv: String, - +hv: {Laws.ends(tok, vv) == True{} : Bool} -) -> {Laws.piece(List.append(&2, String, pre, tok <> rest), vv) == True{} : Bool}: - match pre: - case Nil{}: - ee = Equal.sym(Bool, Laws.ends(tok, vv), True{}, hv) - %ee : {Bool.or(_, Laws.piece(rest, vv)) == True{} : Bool} - {==} - case Con{+pp, +pt}: - ih = gv.mid_piece(pt, tok, rest, vv, hv) - ee = Equal.sym(Bool, Laws.piece(List.append(&2, String, pt, tok <> rest), vv), True{}, ih) - %ee : {Bool.or(Laws.ends(pp, vv), _) == True{} : Bool} - gv.or_t(Laws.ends(pp, vv)) - -# the tail of a string, empty for the empty one -def gv.tail(ss: String) -> String: - match ss: - case SNil{}: - SNil{} - case SCon{_c, tt}: - tt - -# whether a string is empty, as a type: the empty one is not a char in front -def gv.snil_ty(ss: String) -> Type: - match ss: - case SNil{}: - Unit - case SCon{_c, _t}: - Empty - -# the empty string is never a char in front of one -def gv.snil_scon(+cc: Char, +tt: String, ee: {SNil{} == SCon{cc, tt} : String}) -> Empty: - %ee : gv.snil_ty(_) - Unit{} - -# dropping one more char leaves the tail of what was left -def gv.drop_succ( - ww: String, - kk: Nat, - +cc: Char, - +tt: String, - +ee: {String.drop(ww, kk) == SCon{cc, tt} : String} -) -> {String.drop(ww, 1n+kk) == tt : String}: - match ww kk: - case SNil{} _: - Empty.absurd({SNil{} == tt : String}, gv.snil_scon(cc, tt, ee)) - case SCon{+dd, +uu} 0n: - Equal.trans(String, String.drop(uu, 0n), uu, tt, gv.drop0(uu), - Equal.cong(String, String, ss => gv.tail(ss), SCon{dd, uu}, SCon{cc, tt}, ee)) - case SCon{+dd, +uu} 1n+pp: - gv.drop_succ(uu, pp, cc, tt, ee) - -# what is left of a word once some chars are dropped, named -def gv.ends_at( - +tok: String, - +kk: Nat, - +xx: String, - +ee: {String.drop(tok, kk) == xx : String} -) -> {Laws.ends(tok, xx) == True{} : Bool}: - %ee : {Laws.ends(tok, _) == True{} : Bool} - gv.ends_drop(tok, kk) - -# every suffix of a word of a list is within the list -def gv.sufs_in( - xx: String, - +tok: String, - +kk: Nat, - +pre: List<&2, String>, - +rest: List<&2, String>, - +ee: {String.drop(tok, kk) == xx : String} -) -> gv.In(xx, List.append(&2, String, pre, tok <> rest)): - match xx: - case SNil{}: - gv.mid_piece(pre, tok, rest, SNil{}, gv.ends_at(tok, kk, SNil{}, ee)) - case SCon{+cc, +tt}: - gv.and_tt(Laws.piece(List.append(&2, String, pre, tok <> rest), SCon{cc, tt}), - gv.sufs(tt, List.append(&2, String, pre, tok <> rest)), - gv.mid_piece(pre, tok, rest, SCon{cc, tt}, gv.ends_at(tok, kk, SCon{cc, tt}, ee)), - gv.sufs_in(tt, tok, 1n+kk, pre, rest, gv.drop_succ(tok, kk, cc, tt, ee))) - -# a word of a list is within it -def gv.in_mid( - +pre: List<&2, String>, - +tok: String, - +rest: List<&2, String> -) -> gv.In(tok, List.append(&2, String, pre, tok <> rest)): - gv.sufs_in(tok, tok, 0n, pre, rest, gv.drop0(tok)) - -# a value is `true` or a piece of one of the words -def gv.ok(+ws: List<&2, String>, +vv: String) -> Bool: - Bool.or(String.eq(vv, "true"), Laws.piece(ws, vv)) - -# a value within the words is one -def gv.ok_in(+ws: List<&2, String>, +vv: String, hh: gv.In(vv, ws)) -> {gv.ok(ws, vv) == True{} : Bool}: - ee = Equal.sym(Bool, Laws.piece(ws, vv), True{}, gv.in_piece(vv, ws, hh)) - %ee : {Bool.or(String.eq(vv, "true"), _) == True{} : Bool} - gv.or_t(String.eq(vv, "true")) - -# every binding's value is one -def gv.all(bs: List<&2, S.Bind>, +ws: List<&2, String>) -> Bool: - match bs: - case Nil{}: - True{} - case Con{hh, tt}: - S.Bind{_n, vv} = hh - Bool.and(gv.ok(ws, vv), gv.all(tt, ws)) - -# every level's bindings' values are -def gv.ups(lvs: List<&2, S.Level>, +ws: List<&2, String>) -> Bool: - match lvs: - case Nil{}: - True{} - case Con{hh, tt}: - S.Level{_a, bs} = hh - Bool.and(gv.all(bs, ws), gv.ups(tt, ws)) - -# every value a walker holds, at every level, is one -def gv.good(st: S.St, +ws: List<&2, String>) -> Bool: - S.St{_m, _a, up, _s, _p, binds, _o, _r, _n} = st - Bool.and(gv.all(binds, ws), gv.ups(up, ws)) - -# a walker whose values all are -def gv.G(st: S.St, +ws: List<&2, String>) -> Type: - {gv.good(st, ws) == True{} : Bool} - -# a value, when there is one, is -def gv.mok(mv: Maybe<&2, String>, +ws: List<&2, String>) -> Bool: - match mv: - case None{}: - True{} - case Some{vv}: - gv.ok(ws, vv) - -# the value of a name and value cut, when there is one, is -def gv.pok(nv: String & Maybe<&2, String>, +ws: List<&2, String>) -> Bool: - (_n, mv) = nv - gv.mok(mv, ws) - -# a walker that keeps its bindings and the levels above -def gv.keep( - +binds: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +ws: List<&2, String>, - +hb: {gv.all(binds, ws) == True{} : Bool}, - +hu: {gv.ups(up, ws) == True{} : Bool} -) -> {Bool.and(gv.all(binds, ws), gv.ups(up, ws)) == True{} : Bool}: - gv.and_tt(gv.all(binds, ws), gv.ups(up, ws), hb, hu) - -# the values a walker holds, as a type -def gv.B(+binds: List<&2, S.Bind>, +ws: List<&2, String>) -> Type: - {gv.all(binds, ws) == True{} : Bool} - -# the values of the levels above, as a type -def gv.U(+up: List<&2, S.Level>, +ws: List<&2, String>) -> Type: - {gv.ups(up, ws) == True{} : Bool} - -# a binding of a value that is one -def gv.put( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hv: {gv.ok(ws, val) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), ws): - gv.and_tt(Bool.and(gv.ok(ws, val), gv.all(binds, ws)), gv.ups(up, ws), - gv.and_tt(gv.ok(ws, val), gv.all(binds, ws), hv, hb), hu) - -# a value bound when allowed -def gv.choice( - ok: Bool, - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hv: {gv.ok(ws, val) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), ws): - match ok: - case True{}: - gv.put(name, val, args, up, subs, path, binds, pos, raw, seen, ws, hv, hb, hu) - case False{}: - gv.keep(binds, up, ws, hb, hu) - -# an option's value -def gv.opt_val( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hv: {gv.ok(ws, val) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), ws): - gv.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, - raw, seen, ws, hv, hb, hu) - -# a flag binds `true` -def gv.flag( - val: Maybe<&2, String>, - +name: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match val: - case None{}: - gv.put(name, "true", args, up, subs, path, binds, pos, raw, seen, ws, {==}, hb, hu) - case Some{_v}: - gv.keep(binds, up, ws, hb, hu) - -# an option: its value now, or it waits -def gv.opt( - val: Maybe<&2, String>, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hm: {gv.mok(val, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), ws): - match val: - case None{}: - gv.keep(binds, up, ws, hb, hu) - case Some{vv}: - gv.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) - -# the once-check keeps the walker's values -def gv.once( - again: Bool, - st: S.St, - +name: String, - +path: List<&2, String>, - +ws: List<&2, String>, - hg: gv.G(st, ws) -) -> gv.G(S.parse.once(again, st, name, path), ws): - match again: - case True{}: - match st: - case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: - hg - case False{}: - hg - -# a found argument, by its kind -def gv.akind( - kk: S.ArgKind, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hm: {gv.mok(val, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match kk: - case S.Flag{}: - gv.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), - name, path, ws, gv.flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen, ws, hb, hu)) - case S.Opt{}: - gv.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), - name, path, ws, gv.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu)) - case S.Many{}: - gv.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) - case S.Pos{}: - gv.keep(binds, up, ws, hb, hu) - case S.Rest{}: - gv.keep(binds, up, ws, hb, hu) - -# a found argument, or an unknown flag -def gv.arg( - found: Maybe<&2, S.Arg>, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hm: {gv.mok(val, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gv.keep(binds, up, ws, hb, hu) - case Some{aa}: - match aa: - case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: - gv.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) - -# an unknown long option: a request for help, or an unknown flag -def gv.lhelp( - hit: Bool, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match hit: - case True{}: - gv.keep(binds, up, ws, hb, hu) - case False{}: - gv.keep(binds, up, ws, hb, hu) - -# a long option the current command spells, or an unknown one -def gv.found( - found: Maybe<&2, S.Arg>, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hm: {gv.mok(val, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gv.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen, ws, hb, hu) - case Some{aa}: - gv.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) - -# a long option with a name -def gv.named( - empty: Bool, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hm: {gv.mok(val, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match empty: - case True{}: - gv.keep(binds, up, ws, hb, hu) - case False{}: - gv.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hm, hb, hu) - -# a long option after the `=` cut -def gv.cut( - nv: String & Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hp: {gv.pok(nv, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match nv: - case (+name, +val): - gv.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hp, hb, hu) - -# the `=` cut's value, when the `=` is here -def gv.cut_pick( - eq: Bool, - +acc: List<&2, Char>, - +tt: List<&2, Char>, - rest: String & Maybe<&2, String>, - +ws: List<&2, String>, - +hr: {gv.pok(rest, ws) == True{} : Bool}, - +ht: {gv.ok(ws, String.from_list(tt)) == True{} : Bool} -) -> {gv.pok(S.cut_eq.pick(eq, acc, tt, rest), ws) == True{} : Bool}: - match eq: - case True{}: - ht - case False{}: - hr - -# the `=` cut's value is within the words when the body is -def gv.cut_go( - cs: List<&2, Char>, - +acc: List<&2, Char>, - +ws: List<&2, String>, - hh: gv.In(String.from_list(cs), ws) -) -> {gv.pok(S.cut_eq.go(cs, acc), ws) == True{} : Bool}: - match cs: - case Nil{}: - {==} - case Con{+cc, +tt}: - +hi = {gv.in_tail(cc, String.from_list(tt), ws, hh) : {gv.sufs(String.from_list(tt), ws) == True{} : Bool}} - gv.cut_pick(Char.is_eq(cc, '='), acc, tt, S.cut_eq.go(tt, cc <> acc), ws, gv.cut_go(tt, cc <> acc, ws, hi), - gv.ok_in(ws, String.from_list(tt), hi)) - -# a string read as chars and back is itself -def gv.round(ss: String) -> {String.from_list(String.to_list(ss)) == ss : String}: - match ss: - case SNil{}: - {==} - case SCon{+hh, +tt}: - Equal.cong(String, String, xs => SCon{hh, xs}, String.from_list(String.to_list(tt)), tt, gv.round(tt)) - -# a long option body within the words -def gv.long( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - hh: gv.In(body, ws), - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): - ee = Equal.sym(String, String.from_list(String.to_list(body)), body, gv.round(body)) - hr = {%ee : gv.In(_, ws) - hh : gv.In(String.from_list(String.to_list(body)), ws)} - gv.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen, ws, - gv.cut_go(String.to_list(body), [], ws, hr), hb, hu) - -# a flag letter's value: the rest after `=` -def gv.cflag( - eq: Bool, - +rest: String, - +ws: List<&2, String>, - hh: gv.In(rest, ws) -) -> {gv.mok(S.cluster.val.flag(eq, rest), ws) == True{} : Bool}: - match eq: - case True{}: - gv.ok_in(ws, rest, hh) - case False{}: - {==} - -# the rest of the cluster, when there is any -def gv.crest( - rest: String, - +ws: List<&2, String>, - hh: gv.In(rest, ws) -) -> {gv.mok(Bool.pick(Maybe<&2, String>, String.is_empty(rest), None{}, Some{rest}), ws) == True{} : Bool}: - match rest: - case SNil{}: - {==} - case SCon{+cc, +tt}: - gv.ok_in(ws, SCon{cc, tt}, hh) - -# an option letter's value: after one `=`, or the rest -def gv.sval( - eq: Bool, - +rest: String, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool} -) -> {gv.mok(S.short_of.val(eq, rest), ws) == True{} : Bool}: - match eq: - case True{}: - gv.ok_in(ws, String.drop(rest, 1n), gv.in_drop(rest, 1n, ws, hh)) - case False{}: - gv.crest(rest, ws, hh) - -# a letter's value is within the words when the rest of the cluster is -def gv.cval( - found: Maybe<&2, S.Arg>, - +rest: String, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool} -) -> {gv.mok(S.cluster.val(found, rest), ws) == True{} : Bool}: - match found: - case None{}: - {==} - case Some{aa}: - match aa: - case S.Arg{_n, _s, _l, kind, _h, _r, _d, _c}: - match kind: - case S.Flag{}: - gv.cflag(String.starts_with(rest, "="), rest, ws, hh) - case S.Opt{}: - gv.sval(String.starts_with(rest, "="), rest, ws, hh) - case S.Many{}: - gv.sval(String.starts_with(rest, "="), rest, ws, hh) - case S.Pos{}: - {==} - case S.Rest{}: - {==} - -# one letter, by the walker's mode -def gv.capply_mode( - mode: S.Mode, - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match mode: - case S.Dead{_e}: - gv.keep(binds, up, ws, hb, hu) - case S.Free{}: - gv.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, ws, gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hu) - case S.Need{_n}: - gv.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, ws, gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hu) - case S.Help{}: - gv.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, ws, gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hu) - -# one letter against the walker -def gv.capply( - st: S.St, - +cc: Char, - +rest: String, - +orig: String, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool}, - +hg: {gv.good(st, ws) == True{} : Bool} -) -> gv.G(S.cluster.apply(st, cc, rest, orig), ws): - match st: - case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - gv.capply_mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, - gv.and_l(gv.all(binds, ws), gv.ups(up, ws), hg), gv.and_r(gv.all(binds, ws), gv.ups(up, ws), hg)) - -# one letter, unless the cluster has ended -def gv.letter( - done: Bool, - +cc: Char, - +rest: String, - +orig: String, - st: S.St, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool}, - +hg: {gv.good(st, ws) == True{} : Bool} -) -> gv.G(S.cluster.letter(done, cc, rest, orig, st), ws): - match done: - case True{}: - hg - case False{}: - gv.capply(st, cc, rest, orig, ws, hh, hg) - -# every letter of a cluster within the words -def gv.cluster( - body: String, - +orig: String, - +st: S.St, - +done: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - +hg: {gv.good(st, ws) == True{} : Bool} -) -> gv.G(S.cluster(body, orig, st, done), ws): - match body: - case SNil{}: - hg - case SCon{+cc, +rest}: - +hr = {gv.in_tail(cc, rest, ws, hh) : {gv.sufs(rest, ws) == True{} : Bool}} - gv.cluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), ws, hr, - gv.letter(done, cc, rest, orig, st, ws, hr, hg)) - -# a short option body within the words: a request for help, or a cluster -def gv.sgo( - hit: Bool, - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - +hb: {gv.all(binds, ws) == True{} : Bool}, - +hu: {gv.ups(up, ws) == True{} : Bool} -) -> gv.G(S.parse.take_short.go(hit, body, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match hit: - case True{}: - gv.keep(binds, up, ws, hb, hu) - case False{}: - gv.cluster(body, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, raw, seen}, False{}, ws, hh, - gv.keep(binds, up, ws, hb, hu)) - -# a short option body within the words -def gv.short( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): - gv.sgo(S.short_help(body, args, seen), body, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) - -# a positional binds the word -def gv.take_pos( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - pos: List<&2, S.Arg>, - +raw: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), ws): - match pos: - case Nil{}: - gv.keep(binds, up, ws, hb, hu) - case Con{hp, +tt}: - match hp: - case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: - gv.choice(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), - S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}, ws, gv.ok_in(ws, tok, hh), hb, hu) - -# entering a subcommand moves the bindings up a level -def gv.enter( - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +ws: List<&2, String>, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.enter(ss, args, up, path, binds), ws): - match ss: - case S.Sub{_n, _a, _g, _c}: - gv.keep(binds, up, ws, hb, hu) - -# a subcommand entered, or a missing positional -def gv.enter_or( - miss: Maybe<&2, String>, - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), ws): - match miss: - case None{}: - gv.enter(ss, args, up, path, binds, ws, hb, hu) - case Some{_n}: - gv.keep(binds, up, ws, hb, hu) - -# a word: a subcommand, or a positional -def gv.word_go( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gv.take_pos(tok, args, up, subs, path, binds, pos, raw, ws, hh, hb, hu) - case Some{ss}: - gv.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen, ws, hb, hu) - -# a word -def gv.word( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), ws): - gv.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) - -# `-` alone is a word; otherwise a cluster -def gv.short_go( - alone: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), ws): - match alone: - case True{}: - gv.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hu) - case False{}: - gv.short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, - gv.in_drop(tok, 1n, ws, hh), hb, hu) - -# a short option or a word -def gv.step_short( - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), ws): - match dash: - case True{}: - gv.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) - case False{}: - gv.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hu) - -# a long option, a short one, or a word -def gv.kind_go( - long: Bool, - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), ws): - match long: - case True{}: - gv.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, - gv.in_drop(tok, 2n, ws, hh), hb, hu) - case False{}: - gv.step_short(dash, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) - -# a word by its shape -def gv.skind( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), ws): - gv.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen, - ws, - hh, hb, hu) - -# `help` starts a help path, keeping the bindings -def gv.help( - hit: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), ws): - match hit: - case True{}: - gv.keep(binds, up, ws, hb, hu) - case False{}: - gv.skind(tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) - -# `--` switches to positionals, keeping the bindings -def gv.end( - end: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), ws): - match end: - case True{}: - gv.keep(binds, up, ws, hb, hu) - case False{}: - gv.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen, ws, hh, - hb, hu) - -# after `--` a word is a positional -def gv.raw( - raw: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), ws): - match raw: - case True{}: - gv.take_pos(tok, args, up, subs, path, binds, pos, True{}, ws, hh, hb, hu) - case False{}: - gv.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) - -# a pending option binds the word -def gv.need( - flag: Bool, - +name: String, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match flag: - case True{}: - gv.keep(binds, up, ws, hb, hu) - case False{}: - gv.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen, ws, gv.ok_in(ws, tok, hh), hb, hu) - -# a word of a help path keeps the bindings -def gv.help_path( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - _hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.help_path(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gv.keep(binds, up, ws, hb, hu) - case Some{ss}: - match ss: - case S.Sub{_n, _a, _g, _c}: - gv.keep(binds, up, ws, hb, hu) - -# one word, by the walker's mode -def gv.mode( - mode: S.Mode, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - hb: gv.B(binds, ws), - hu: gv.U(up, ws) -) -> gv.G(S.parse.step.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match mode: - case S.Dead{_e}: - gv.keep(binds, up, ws, hb, hu) - case S.Need{name}: - gv.need(S.looks_flag(tok), name, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) - case S.Help{}: - gv.help_path(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hu) - case S.Free{}: - gv.raw(raw, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hu) - -# one word within the words keeps every value a walker holds one -def gv.step( - +tok: String, - st: S.St, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hg: {gv.good(st, ws) == True{} : Bool} -) -> gv.G(S.parse.step(tok, st), ws): - match st: - case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - gv.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, - gv.and_l(gv.all(binds, ws), gv.ups(up, ws), hg), gv.and_r(gv.all(binds, ws), gv.ups(up, ws), hg)) - -# a walker's values, from one list of words to an equal one -def gv.tr( - st: S.St, - +aa: List<&2, String>, - +bb: List<&2, String>, - +ee: {aa == bb : List<&2, String>}, - +hg: {gv.good(st, bb) == True{} : Bool} -) -> {gv.good(st, aa) == True{} : Bool}: - e2 = Equal.sym(List<&2, String>, aa, bb, ee) - %e2 : {gv.good(st, _) == True{} : Bool} - hg - -# walking the words after `pre` keeps every value one of the words' pieces -def gv.walk( - tt: List<&2, String>, - +pre: List<&2, String>, - +st: S.St, - +hg: {gv.good(st, List.append(&2, String, pre, tt)) == True{} : Bool} -) -> {gv.good(S.parse.walk(tt, st), List.append(&2, String, pre, tt)) == True{} : Bool}: - match tt: - case Nil{}: - hg - case Con{+tok, +rest}: - +ll = List.append(&2, String, pre, tok <> rest) - +mm = List.append(&2, String, List.append(&2, String, pre, [tok]), rest) - +ee = {sc.app(pre, [tok], rest) : {mm == ll : List<&2, String>}} - g1 = gv.step(tok, st, ll, gv.in_mid(pre, tok, rest), hg) - gw = gv.walk(rest, List.append(&2, String, pre, [tok]), S.parse.step(tok, st), - gv.tr(S.parse.step(tok, st), mm, ll, ee, g1)) - gv.tr(S.parse.walk(rest, S.parse.step(tok, st)), ll, mm, Equal.sym(List<&2, String>, mm, ll, ee), gw) - -# a fresh walker holds no values -def gv.start(spec: Shake.Cli, +ws: List<&2, String>) -> {gv.good(S.parse.start(spec), ws) == True{} : Bool}: - match spec: - case S.Cli{_n, _a, _v, _g, _s}: - {==} - -# the values of a frame's levels -def gv.fups(fr: Grow.Frame, +ws: List<&2, String>) -> Bool: - Grow.Frame{lvs, _p} = fr - gv.ups(lvs, ws) - -# a walker's frame holds its values -def gv.frame( - st: S.St, - +ws: List<&2, String>, - hg: {gv.good(st, ws) == True{} : Bool} -) -> {gv.fups(Grow.gw.frame(st), ws) == True{} : Bool}: - match st: - case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: - hg - -# a level in the middle of a list of levels whose values all are -def gv.ups_mid( - qs: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +ws: List<&2, String>, - +hh: {gv.ups(List.append(&2, S.Level, qs, S.Level{args, bs} <> up), ws) == True{} : Bool} -) -> {gv.all(bs, ws) == True{} : Bool}: - match qs: - case Nil{}: - gv.and_l(gv.all(bs, ws), gv.ups(up, ws), hh) - case Con{qq, +qt}: - match qq: - case S.Level{_a, +qb}: - gv.ups_mid(qt, args, bs, up, ws, - gv.and_r(gv.all(qb, ws), gv.ups(List.append(&2, S.Level, qt, S.Level{args, bs} <> up), ws), hh)) - -# `true` or a piece is given -def gv.or3( - ee: Bool, - pp: Bool, - +dd: Bool, - +hh: {Bool.or(ee, pp) == True{} : Bool} -) -> {Bool.or(ee, Bool.or(pp, dd)) == True{} : Bool}: - match ee: - case True{}: - {==} - case False{}: - match pp: - case True{}: - {==} - case False{}: - Empty.absurd({Bool.or(False{}, Bool.or(False{}, dd)) == True{} : Bool}, Walk.wk.false_true(hh)) - -# a list of values that all are one is given -def gv.all_given( - bs: List<&2, S.Bind>, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +hh: {gv.all(bs, ws) == True{} : Bool} -) -> {Laws.all_given(bs, ws, args) == True{} : Bool}: - match bs: - case Nil{}: - {==} - case Con{hd, +tt}: - match hd: - case S.Bind{+nn, +vv}: - gv.and_tt(Laws.given(ws, args, S.Bind{nn, vv}), Laws.all_given(tt, ws, args), - gv.or3(String.eq(vv, "true"), Laws.piece(ws, vv), Laws.is_default(args, nn, vv), - gv.and_l(gv.ok(ws, vv), gv.all(tt, ws), hh)), - gv.all_given(tt, ws, args, gv.and_r(gv.ok(ws, vv), gv.all(tt, ws), hh))) - -# a default of more arguments is still one -def gv.dmono( - ee: Bool, - pp: Bool, - +xx: Bool, - dd: Bool, - +hh: {Bool.or(ee, Bool.or(pp, dd)) == True{} : Bool} -) -> {Bool.or(ee, Bool.or(pp, Bool.or(xx, dd))) == True{} : Bool}: - match ee: - case True{}: - {==} - case False{}: - match pp: - case True{}: - {==} - case False{}: - e2 = Equal.sym(Bool, dd, True{}, hh) - %e2 : {Bool.or(xx, _) == True{} : Bool} - gv.or_t(xx) - -# bindings given against some arguments are given against more -def gv.given_mono( - xs: List<&2, S.Bind>, - +ws: List<&2, String>, - +aa: S.Arg, - +tt: List<&2, S.Arg>, - +hh: {Laws.all_given(xs, ws, tt) == True{} : Bool} -) -> {Laws.all_given(xs, ws, aa <> tt) == True{} : Bool}: - match xs: - case Nil{}: - {==} - case Con{hd, +xt}: - match hd: - case S.Bind{+nn, +vv}: - match aa: - case S.Arg{+an, +sh, +lo, +kind, +hp, +req, S.Fallback{+ev, +ad}, +cs}: - gv.and_tt(Laws.given(ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, ad}, cs} <> tt, S.Bind{nn, vv}), - Laws.all_given(xt, ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, ad}, cs} <> tt), - gv.dmono(String.eq(vv, "true"), Laws.piece(ws, vv), - Bool.and(String.eq(an, nn), Laws.default_is(ad, vv)), Laws.is_default(tt, nn, vv), - gv.and_l(Laws.given(ws, tt, S.Bind{nn, vv}), Laws.all_given(xt, ws, tt), hh)), - gv.given_mono(xt, ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, ad}, cs}, tt, - gv.and_r(Laws.given(ws, tt, S.Bind{nn, vv}), Laws.all_given(xt, ws, tt), hh))) - -# an argument's own default is given -def gv.dself( - +ee: Bool, - +pp: Bool, - +nn: String, - +vv: String, - dd: Bool -) -> {Bool.or(ee, Bool.or(pp, Bool.or(Bool.and(String.eq(nn, nn), String.eq(vv, vv)), dd))) == True{} : Bool}: - e1 = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) - e2 = Equal.sym(Bool, String.eq(vv, vv), True{}, Eq.string_eq_self(vv)) - e3 = Equal.sym(Bool, Bool.or(pp, True{}), True{}, gv.or_t(pp)) - %e1 : {Bool.or(ee, Bool.or(pp, Bool.or(Bool.and(_, String.eq(vv, vv)), dd))) == True{} : Bool} - %e2 : {Bool.or(ee, Bool.or(pp, Bool.or(Bool.and(True{}, _), dd))) == True{} : Bool} - %e3 : {Bool.or(ee, _) == True{} : Bool} - gv.or_t(ee) - -# a default goes in front when its name is unbound -def gv.fone( - skip: Bool, - +nn: String, - +vv: String, - +rr: List<&2, S.Bind>, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +hr: {Laws.all_given(rr, ws, args) == True{} : Bool}, - +hd: {Laws.given(ws, args, S.Bind{nn, vv}) == True{} : Bool} -) -> {Laws.all_given(S.fill.one(skip, nn, vv, rr), ws, args) == True{} : Bool}: - match skip: - case True{}: - hr - case False{}: - gv.and_tt(Laws.given(ws, args, S.Bind{nn, vv}), Laws.all_given(rr, ws, args), hd, hr) - -# the defaults filled in are the arguments' own -def gv.fill( - args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +ws: List<&2, String>, - +hh: {gv.all(bs, ws) == True{} : Bool} -) -> {Laws.all_given(S.fill.args(args, bs), ws, args) == True{} : Bool}: - match args: - case Nil{}: - gv.all_given(bs, ws, [], hh) - case Con{aa, +tt}: - match aa: - case S.Arg{+an, +sh, +lo, +kind, +hp, +req, S.Fallback{+ev, ad}, +cs}: - match ad: - case None{}: - gv.given_mono(S.fill.args(tt, bs), ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, None{}}, cs}, tt, - gv.fill(tt, bs, ws, hh)) - case Some{+dv}: - gv.fone(S.bound(S.fill.args(tt, bs), an), an, dv, S.fill.args(tt, bs), ws, - S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, Some{dv}}, cs} <> tt, - gv.given_mono(S.fill.args(tt, bs), ws, S.Arg{an, sh, lo, kind, hp, req, S.Fallback{ev, Some{dv}}, - cs}, tt, - gv.fill(tt, bs, ws, hh)), - gv.dself(String.eq(dv, "true"), Laws.piece(ws, dv), an, dv, Laws.is_default(tt, an, dv))) - -# reversed bindings are given when they are -def gv.rev_given( - xs: List<&2, S.Bind>, - +acc: List<&2, S.Bind>, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +hx: {Laws.all_given(xs, ws, args) == True{} : Bool}, - +ha: {Laws.all_given(acc, ws, args) == True{} : Bool} -) -> {Laws.all_given(List.reverse.go(&2, S.Bind, xs, acc), ws, args) == True{} : Bool}: - match xs: - case Nil{}: - ha - case Con{+hd, +tt}: - gv.rev_given(tt, hd <> acc, ws, args, - gv.and_r(Laws.given(ws, args, hd), Laws.all_given(tt, ws, args), hx), - gv.and_tt(Laws.given(ws, args, hd), Laws.all_given(acc, ws, args), - gv.and_l(Laws.given(ws, args, hd), Laws.all_given(tt, ws, args), hx), ha)) - -# the bindings of a finished walker's level at `path` all are one -def gv.level( - +spec: Shake.Cli, - +ws: List<&2, String>, - +qs: List<&2, S.Level>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +ms: List<&2, String>, - h_fr: fin.Framed(spec, ws, qs, args, bs, up, path, ms) -) -> {gv.all(bs, ws) == True{} : Bool}: - +fs = S.parse.walk(ws, S.parse.start(spec)) - +fr = {Grow.Frame{List.append(&2, S.Level, qs, S.Level{args, bs} <> up), List.append(&2, String, path, ms)} - : Grow.Frame} - fg = gv.frame(fs, ws, gv.walk(ws, [], S.parse.start(spec), gv.start(spec, ws))) - gv.ups_mid(qs, args, bs, up, ws, - Equal.trans(Bool, gv.fups(fr, ws), gv.fups(Grow.gw.frame(fs), ws), True{}, - Equal.cong(Grow.Frame, Bool, ff => gv.fups(ff, ws), fr, Grow.gw.frame(fs), - Equal.sym(Grow.Frame, Grow.gw.frame(fs), fr, h_fr)), - fg)) - -def Laws.values_given(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len): - +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} - +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) - ea = Equal.sym(Maybe<&2, S.Matched>, S.at(found, path), Some{S.matched.build(f0 <> rq, ms)}, - fin.at(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len)) - eo = Equal.sym(List<&2, S.Bind>, S.own(S.matched.build(f0 <> rq, ms)), - List.reverse(&2, S.Bind, S.fill.args(args, bs)), Grow.gw.own_build(f0, rq, ms)) - %ea : {Laws.given_at.go(_, ws, args) == Some{True{}} : Maybe<&2, Bool>} - %eo : {Some{Laws.all_given(_, ws, args)} == Some{True{}} : Maybe<&2, Bool>} - Equal.cong(Bool, Maybe<&2, Bool>, bb => Some{bb}, - Laws.all_given(List.reverse(&2, S.Bind, S.fill.args(args, bs)), ws, args), True{}, - gv.rev_given(S.fill.args(args, bs), [], ws, args, - gv.fill(args, bs, ws, gv.level(spec, ws, qs, args, bs, up, path, ms, h_fr)), {==})) - -# ---- SHAKE-PARSE-1: every value a command binds is named after one of its -# arguments and is a piece of the words read while it was current - -# an argument's name -def gn.name(aa: S.Arg) -> String: - S.Arg{nn, _s, _l, _k, _h, _r, _d, _c} = aa - nn - -# whether a binding is named after one of `args` and its value is `true` or a -# piece of one of the words -def gn.nok(+args: List<&2, S.Arg>, +ws: List<&2, String>, bb: S.Bind) -> Bool: - S.Bind{+nn, +vv} = bb - Bool.and(Laws.has_arg(args, nn), gv.ok(ws, vv)) - -# whether every binding is -def gn.all(bs: List<&2, S.Bind>, +args: List<&2, S.Arg>, +ws: List<&2, String>) -> Bool: - match bs: - case Nil{}: - True{} - case Con{hh, tt}: - Bool.and(gn.nok(args, ws, hh), gn.all(tt, args, ws)) - -# whether every pending positional is one of `args` -def gn.pin(pos: List<&2, S.Arg>, +args: List<&2, S.Arg>) -> Bool: - match pos: - case Nil{}: - True{} - case Con{hh, tt}: - S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c} = hh - Bool.and(Laws.has_arg(args, nn), gn.pin(tt, args)) - -# whether an option waiting for its value is one of `args` -def gn.min(mode: S.Mode, +args: List<&2, S.Arg>) -> Bool: - match mode: - case S.Need{nn}: - Laws.has_arg(args, nn) - case S.Free{}: - True{} - case S.Help{}: - True{} - case S.Dead{_e}: - True{} - -# a walker whose waiting option and pending positionals are its command's own -def gn.wf(st: S.St) -> Bool: - S.St{mode, +args, _g, _s, _p, _b, pos, _r, _n} = st - Bool.and(gn.min(mode, args), gn.pin(pos, args)) - -# that, and every binding of its command is named after one of its arguments -# and is `true` or a piece of the words -def gn.good(st: S.St, +ws: List<&2, String>) -> Bool: - S.St{mode, +args, _g, _s, _p, binds, pos, _r, _n} = st - Bool.and(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws)) - -# a walker that is good, as a type -def gn.G(st: S.St, +ws: List<&2, String>) -> Type: - {gn.good(st, ws) == True{} : Bool} - -# a walker that keeps its command, its bindings and its positionals, free -def gn.keep( - +args: List<&2, S.Arg>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +ws: List<&2, String>, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> {Bool.and(gn.pin(pos, args), gn.all(binds, args, ws)) == True{} : Bool}: - gv.and_tt(gn.pin(pos, args), gn.all(binds, args, ws), hp, hb) - -# `x` or something true is true -def gn.or_rt(+xx: Bool, yy: Bool, +hh: {yy == True{} : Bool}) -> {Bool.or(xx, yy) == True{} : Bool}: - ee = Equal.sym(Bool, yy, True{}, hh) - %ee : {Bool.or(xx, _) == True{} : Bool} - gv.or_t(xx) - -# an argument is one of a list it heads -def gn.has_self(aa: S.Arg, +tt: List<&2, S.Arg>) -> {Laws.has_arg(aa <> tt, gn.name(aa)) == True{} : Bool}: - match aa: - case S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c}: - ee = Equal.sym(Bool, String.eq(nn, nn), True{}, Eq.string_eq_self(nn)) - %ee : {Bool.or(_, Laws.has_arg(tt, nn)) == True{} : Bool} - {==} - -# one of some arguments is one of more -def gn.has_mono( - aa: S.Arg, - +tt: List<&2, S.Arg>, - +nn: String, - +hh: {Laws.has_arg(tt, nn) == True{} : Bool} -) -> {Laws.has_arg(aa <> tt, nn) == True{} : Bool}: - match aa: - case S.Arg{+an, _s, _l, _k, _h, _r, _d, _c}: - gn.or_rt(String.eq(an, nn), Laws.has_arg(tt, nn), hh) - -# pending positionals of some arguments are pending of more -def gn.pin_mono( - xs: List<&2, S.Arg>, - +aa: S.Arg, - +tt: List<&2, S.Arg>, - +hh: {gn.pin(xs, tt) == True{} : Bool} -) -> {gn.pin(xs, aa <> tt) == True{} : Bool}: - match xs: - case Nil{}: - {==} - case Con{xx, +xt}: - match xx: - case S.Arg{+xn, _s, _l, _k, _h, _r, _d, _c}: - gv.and_tt(Laws.has_arg(aa <> tt, xn), gn.pin(xt, aa <> tt), - gn.has_mono(aa, tt, xn, gv.and_l(Laws.has_arg(tt, xn), gn.pin(xt, tt), hh)), - gn.pin_mono(xt, aa, tt, gv.and_r(Laws.has_arg(tt, xn), gn.pin(xt, tt), hh))) - -# a positional kept pending, or not -def gn.pin_put( - keep: Bool, - aa: S.Arg, - +rest: List<&2, S.Arg>, - +tt: List<&2, S.Arg>, - +hr: {gn.pin(rest, aa <> tt) == True{} : Bool} -) -> {gn.pin(S.pos_of.put(keep, aa, rest), aa <> tt) == True{} : Bool}: - match keep: - case True{}: - match aa: - case S.Arg{+nn, +sh, +lo, +kind, +hl, +req, +dd, +cs}: - gv.and_tt(Laws.has_arg(S.Arg{nn, sh, lo, kind, hl, req, dd, cs} <> tt, nn), - gn.pin(rest, S.Arg{nn, sh, lo, kind, hl, req, dd, cs} <> tt), - gn.has_self(S.Arg{nn, sh, lo, kind, hl, req, dd, cs}, tt), hr) - case False{}: - hr - -# the positionals of some arguments are among them -def gn.pin_of(args: List<&2, S.Arg>) -> {gn.pin(S.pos_of(args), args) == True{} : Bool}: - match args: - case Nil{}: - {==} - case Con{+aa, +tt}: - gn.pin_put(S.pos.is(aa), aa, S.pos_of(tt), tt, gn.pin_mono(S.pos_of(tt), aa, tt, gn.pin_of(tt))) - -# whether a found argument is one of `args` -def gn.fok(found: Maybe<&2, S.Arg>, +args: List<&2, S.Arg>) -> Bool: - match found: - case None{}: - True{} - case Some{aa}: - Laws.has_arg(args, gn.name(aa)) - -# a found argument among some is among more -def gn.fok_mono( - mm: Maybe<&2, S.Arg>, - +aa: S.Arg, - +tt: List<&2, S.Arg>, - +hh: {gn.fok(mm, tt) == True{} : Bool} -) -> {gn.fok(mm, aa <> tt) == True{} : Bool}: - match mm: - case None{}: - {==} - case Some{xx}: - gn.has_mono(aa, tt, gn.name(xx), hh) - -# the argument found by a long spelling, when the head matches or not -def gn.fok_long_at( - hit: Bool, - +hh: S.Arg, - +tt: List<&2, S.Arg>, - +rest: Maybe<&2, S.Arg>, - +hr: {gn.fok(rest, tt) == True{} : Bool} -) -> {gn.fok(S.by_long.at(hit, hh, rest), hh <> tt) == True{} : Bool}: - match hit: - case True{}: - gn.has_self(hh, tt) - case False{}: - gn.fok_mono(rest, hh, tt, hr) - -# the argument found by a long spelling is one of them -def gn.fok_long(args: List<&2, S.Arg>, +name: String) -> {gn.fok(S.by_long(args, name), args) == True{} : Bool}: - match args: - case Nil{}: - {==} - case Con{+hh, +tt}: - gn.fok_long_at(S.by_long.hit(hh, name), hh, tt, S.by_long(tt, name), gn.fok_long(tt, name)) - -# the argument found by a short spelling, when the head matches or not -def gn.fok_short_at( - hit: Bool, - +hh: S.Arg, - +tt: List<&2, S.Arg>, - +rest: Maybe<&2, S.Arg>, - +hr: {gn.fok(rest, tt) == True{} : Bool} -) -> {gn.fok(S.by_short.at(hit, hh, rest), hh <> tt) == True{} : Bool}: - match hit: - case True{}: - gn.has_self(hh, tt) - case False{}: - gn.fok_mono(rest, hh, tt, hr) - -# the argument found by a short spelling is one of them -def gn.fok_short(args: List<&2, S.Arg>, +name: String) -> {gn.fok(S.by_short(args, name), args) == True{} : Bool}: - match args: - case Nil{}: - {==} - case Con{+hh, +tt}: - gn.fok_short_at(S.by_short.hit(hh, name), hh, tt, S.by_short(tt, name), gn.fok_short(tt, name)) - -# a binding named after one of the arguments, of a value that is one -def gn.put( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hv: {gv.ok(ws, val) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), ws): - gv.and_tt(gn.pin(pos, args), Bool.and(gn.nok(args, ws, S.Bind{name, val}), gn.all(binds, args, ws)), hp, - gv.and_tt(gn.nok(args, ws, S.Bind{name, val}), gn.all(binds, args, ws), - gv.and_tt(Laws.has_arg(args, name), gv.ok(ws, val), hn, hv), hb)) - -# a value bound when allowed -def gn.choice( - ok: Bool, - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hv: {gv.ok(ws, val) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), ws): - match ok: - case True{}: - gn.put(name, val, args, up, subs, path, binds, pos, raw, seen, ws, hn, hv, hb, hp) - case False{}: - gn.keep(args, binds, pos, ws, hb, hp) - -# an option's value -def gn.opt_val( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hv: {gv.ok(ws, val) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), ws): - gn.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, - raw, seen, ws, hn, hv, hb, hp) - -# a flag binds `true` -def gn.flag( - val: Maybe<&2, String>, - +name: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match val: - case None{}: - gn.put(name, "true", args, up, subs, path, binds, pos, raw, seen, ws, hn, {==}, hb, hp) - case Some{_v}: - gn.keep(args, binds, pos, ws, hb, hp) - -# an option: its value now, or it waits, one of the arguments -def gn.opt( - val: Maybe<&2, String>, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hm: {gv.mok(val, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), ws): - match val: - case None{}: - gv.and_tt(Bool.and(Laws.has_arg(args, name), gn.pin(pos, args)), gn.all(binds, args, ws), - gv.and_tt(Laws.has_arg(args, name), gn.pin(pos, args), hn, hp), hb) - case Some{vv}: - gn.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, ws, hn, hm, hb, hp) - -# the once-check keeps a good walker good -def gn.once( - again: Bool, - st: S.St, - +name: String, - +path: List<&2, String>, - +ws: List<&2, String>, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> gn.G(S.parse.once(again, st, name, path), ws): - match again: - case True{}: - match st: - case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: - gv.and_tt(gn.pin(oo, aa), gn.all(bb, aa, ws), - gv.and_r(gn.min(mode, aa), gn.pin(oo, aa), - gv.and_l(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg)), - gv.and_r(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg)) - case False{}: - hg - -# a found argument, by its kind -def gn.akind( - kk: S.ArgKind, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hm: {gv.mok(val, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match kk: - case S.Flag{}: - gn.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), - name, path, ws, gn.flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen, ws, hn, hb, hp)) - case S.Opt{}: - gn.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), - name, path, ws, gn.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hn, hm, hb, hp)) - case S.Many{}: - gn.opt(val, name, args, up, subs, path, binds, pos, raw, seen, ws, hn, hm, hb, hp) - case S.Pos{}: - gn.keep(args, binds, pos, ws, hb, hp) - case S.Rest{}: - gn.keep(args, binds, pos, ws, hb, hp) - -# a found argument, or an unknown flag -def gn.arg( - found: Maybe<&2, S.Arg>, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hf: {gn.fok(found, args) == True{} : Bool}, - +hm: {gv.mok(val, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gn.keep(args, binds, pos, ws, hb, hp) - case Some{aa}: - match aa: - case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: - gn.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hf, hm, hb, hp) - -# an unknown long option: a request for help, or an unknown flag -def gn.lhelp( - hit: Bool, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match hit: - case True{}: - gn.keep(args, binds, pos, ws, hb, hp) - case False{}: - gn.keep(args, binds, pos, ws, hb, hp) - -# a long option the current command spells, or an unknown one -def gn.found( - found: Maybe<&2, S.Arg>, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hf: {gn.fok(found, args) == True{} : Bool}, - +hm: {gv.mok(val, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gn.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen, ws, hb, hp) - case Some{aa}: - gn.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hf, hm, hb, hp) - -# a long option with a name -def gn.named( - empty: Bool, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hm: {gv.mok(val, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match empty: - case True{}: - gn.keep(args, binds, pos, ws, hb, hp) - case False{}: - gn.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, - gn.fok_long(args, name), hm, hb, hp) - -# a long option after the `=` cut -def gn.cut( - nv: String & Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hq: {gv.pok(nv, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match nv: - case (+name, +val): - gn.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, ws, hq, hb, hp) - -# a long option body within the words -def gn.long( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): - ee = Equal.sym(String, String.from_list(String.to_list(body)), body, gv.round(body)) - hr = {%ee : gv.In(_, ws) - hh : gv.In(String.from_list(String.to_list(body)), ws)} - gn.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen, ws, - gv.cut_go(String.to_list(body), [], ws, hr), hb, hp) - -# one letter, by the walker's mode -def gn.capply_mode( - mode: S.Mode, - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match mode: - case S.Dead{_e}: - gn.keep(args, binds, pos, ws, hb, hp) - case S.Free{}: - gn.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, ws, gn.fok_short(args, SCon{cc, SNil{}}), - gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hp) - case S.Need{_n}: - gn.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, ws, gn.fok_short(args, SCon{cc, SNil{}}), - gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hp) - case S.Help{}: - gn.arg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, ws, gn.fok_short(args, SCon{cc, SNil{}}), - gv.cval(S.by_short(args, SCon{cc, SNil{}}), rest, ws, hh), hb, hp) - -# one letter against a good walker -def gn.capply( - st: S.St, - +cc: Char, - +rest: String, - +orig: String, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool}, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> gn.G(S.cluster.apply(st, cc, rest, orig), ws): - match st: - case S.St{+mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - gn.capply_mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, - gv.and_r(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg), - gv.and_r(gn.min(mode, args), gn.pin(pos, args), - gv.and_l(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg))) - -# one letter, unless the cluster has ended -def gn.letter( - done: Bool, - +cc: Char, - +rest: String, - +orig: String, - st: S.St, - +ws: List<&2, String>, - +hh: {gv.sufs(rest, ws) == True{} : Bool}, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> gn.G(S.cluster.letter(done, cc, rest, orig, st), ws): - match done: - case True{}: - hg - case False{}: - gn.capply(st, cc, rest, orig, ws, hh, hg) - -# every letter of a cluster within the words -def gn.cluster( - body: String, - +orig: String, - +st: S.St, - +done: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> gn.G(S.cluster(body, orig, st, done), ws): - match body: - case SNil{}: - hg - case SCon{+cc, +rest}: - +hr = {gv.in_tail(cc, rest, ws, hh) : {gv.sufs(rest, ws) == True{} : Bool}} - gn.cluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), ws, hr, - gn.letter(done, cc, rest, orig, st, ws, hr, hg)) - -# a short option body within the words: a request for help, or a cluster -def gn.sgo( - hit: Bool, - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_short.go(hit, body, orig, args, up, subs, path, binds, pos, raw, seen), ws): - match hit: - case True{}: - gn.keep(args, binds, pos, ws, hb, hp) - case False{}: - gn.cluster(body, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, raw, seen}, False{}, ws, hh, - gn.keep(args, binds, pos, ws, hb, hp)) - -# a short option body within the words -def gn.short( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(body, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, raw, seen), ws): - gn.sgo(S.short_help(body, args, seen), body, orig, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hp) - -# a positional consumed, or kept when it is a rest -def gn.kpos( - keep: Bool, - aa: S.Arg, - +tt: List<&2, S.Arg>, - +args: List<&2, S.Arg>, - +hh: {gn.pin(aa <> tt, args) == True{} : Bool} -) -> {gn.pin(S.parse.take_pos.keep(keep, aa, tt), args) == True{} : Bool}: - match keep: - case True{}: - hh - case False{}: - match aa: - case S.Arg{+nn, _s, _l, _k, _h, _r, _d, _c}: - gv.and_r(Laws.has_arg(args, nn), gn.pin(tt, args), hh) - -# a positional binds the word, under its own name -def gn.take_pos( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - pos: List<&2, S.Arg>, - +raw: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), ws): - match pos: - case Nil{}: - gn.keep(args, binds, [], ws, hb, {==}) - case Con{ph, +tt}: - match ph: - case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: - gn.choice(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), - S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}, ws, - gv.and_l(Laws.has_arg(args, name), gn.pin(tt, args), hp), gv.ok_in(ws, tok, hh), hb, - gn.kpos(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), - S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt, args, hp)) - -# entering a subcommand: its positionals are its own, and it has no bindings -def gn.enter( - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +ws: List<&2, String> -) -> gn.G(S.parse.enter(ss, args, up, path, binds), ws): - match ss: - case S.Sub{_n, _a, +next, _c}: - gv.and_tt(gn.pin(S.pos_of(next), next), True{}, gn.pin_of(next), {==}) - -# a subcommand entered, or a missing positional -def gn.enter_or( - miss: Maybe<&2, String>, - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), ws): - match miss: - case None{}: - gn.enter(ss, args, up, path, binds, ws) - case Some{_n}: - gn.keep(args, binds, pos, ws, hb, hp) - -# a word: a subcommand, or a positional -def gn.word_go( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gn.take_pos(tok, args, up, subs, path, binds, pos, raw, ws, hh, hb, hp) - case Some{ss}: - gn.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen, ws, hb, hp) - -# a word -def gn.word( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), ws): - gn.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hb, hp) - -# `-` alone is a word; otherwise a cluster -def gn.short_go( - alone: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), ws): - match alone: - case True{}: - gn.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hp) - case False{}: - gn.short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, - gv.in_drop(tok, 1n, ws, hh), hb, hp) - -# a short option or a word -def gn.step_short( - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), ws): - match dash: - case True{}: - gn.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) - case False{}: - gn.word(tok, args, up, subs, path, binds, pos, False{}, seen, ws, hh, hb, hp) - -# a long option, a short one, or a word -def gn.kind_go( - long: Bool, - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), ws): - match long: - case True{}: - gn.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen, ws, - gv.in_drop(tok, 2n, ws, hh), hb, hp) - case False{}: - gn.step_short(dash, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) - -# a word by its shape -def gn.skind( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), ws): - gn.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen, - ws, - hh, hb, hp) - -# `help` starts a help path -def gn.help( - hit: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), ws): - match hit: - case True{}: - gn.keep(args, binds, pos, ws, hb, hp) - case False{}: - gn.skind(tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) - -# `--` switches to positionals -def gn.end( - end: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), ws): - match end: - case True{}: - gn.keep(args, binds, pos, ws, hb, hp) - case False{}: - gn.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen, ws, hh, - hb, hp) - -# after `--` a word is a positional -def gn.raw( - raw: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), ws): - match raw: - case True{}: - gn.take_pos(tok, args, up, subs, path, binds, pos, True{}, ws, hh, hb, hp) - case False{}: - gn.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) - -# a waiting option binds the word under its own name -def gn.need( - flag: Bool, - +name: String, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hn: {Laws.has_arg(args, name) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match flag: - case True{}: - gn.keep(args, binds, pos, ws, hb, hp) - case False{}: - gn.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen, ws, hn, gv.ok_in(ws, tok, hh), hb, hp) - -# a word of a help path -def gn.help_path( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.help_path(found, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match found: - case None{}: - gn.keep(args, binds, pos, ws, hb, hp) - case Some{ss}: - match ss: - case S.Sub{_n, _a, _g, _c}: - gn.keep(args, binds, pos, ws, hb, hp) - -# one word, by the walker's mode -def gn.mode( - mode: S.Mode, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hm: {gn.min(mode, args) == True{} : Bool}, - +hb: {gn.all(binds, args, ws) == True{} : Bool}, - +hp: {gn.pin(pos, args) == True{} : Bool} -) -> gn.G(S.parse.step.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen), ws): - match mode: - case S.Dead{_e}: - gn.keep(args, binds, pos, ws, hb, hp) - case S.Need{name}: - gn.need(S.looks_flag(tok), name, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, hm, hb, hp) - case S.Help{}: - gn.help_path(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, ws, hb, hp) - case S.Free{}: - gn.raw(raw, tok, args, up, subs, path, binds, pos, seen, ws, hh, hb, hp) - -# one word within the words keeps a walker good -def gn.step( - +tok: String, - st: S.St, - +ws: List<&2, String>, - +hh: {gv.sufs(tok, ws) == True{} : Bool}, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> gn.G(S.parse.step(tok, st), ws): - match st: - case S.St{+mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - +hw = {gv.and_l(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg) - : {Bool.and(gn.min(mode, args), gn.pin(pos, args)) == True{} : Bool}} - gn.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen, ws, hh, - gv.and_l(gn.min(mode, args), gn.pin(pos, args), hw), - gv.and_r(Bool.and(gn.min(mode, args), gn.pin(pos, args)), gn.all(binds, args, ws), hg), - gv.and_r(gn.min(mode, args), gn.pin(pos, args), hw)) - -# a good walker, from one list of words to an equal one -def gn.tr( - st: S.St, - +aa: List<&2, String>, - +bb: List<&2, String>, - ee: {aa == bb : List<&2, String>}, - hg: {gn.good(st, bb) == True{} : Bool} -) -> {gn.good(st, aa) == True{} : Bool}: - e2 = Equal.sym(List<&2, String>, aa, bb, ee) - %e2 : {gn.good(st, _) == True{} : Bool} - hg - -# walking the words after `pre` keeps a walker good -def gn.walk( - tt: List<&2, String>, - +pre: List<&2, String>, - +st: S.St, - +hg: {gn.good(st, List.append(&2, String, pre, tt)) == True{} : Bool} -) -> {gn.good(S.parse.walk(tt, st), List.append(&2, String, pre, tt)) == True{} : Bool}: - match tt: - case Nil{}: - hg - case Con{+tok, +rest}: - +ll = List.append(&2, String, pre, tok <> rest) - +mm = List.append(&2, String, List.append(&2, String, pre, [tok]), rest) - +ee = {sc.app(pre, [tok], rest) : {mm == ll : List<&2, String>}} - g1 = gn.step(tok, st, ll, gv.in_mid(pre, tok, rest), hg) - gw = gn.walk(rest, List.append(&2, String, pre, [tok]), S.parse.step(tok, st), - gn.tr(S.parse.step(tok, st), mm, ll, ee, g1)) - gn.tr(S.parse.walk(rest, S.parse.step(tok, st)), ll, mm, Equal.sym(List<&2, String>, mm, ll, ee), gw) - -# a fresh walker is good -def gn.start(spec: Shake.Cli, +ws: List<&2, String>) -> {gn.good(S.parse.start(spec), ws) == True{} : Bool}: - match spec: - case S.Cli{_n, _a, _v, +args, _s}: - gv.and_tt(gn.pin(S.pos_of(args), args), True{}, gn.pin_of(args), {==}) - -# the bindings of a view's command, named and within the words -def gn.vall(vv: Grow.View, +ws: List<&2, String>) -> Bool: - Grow.View{aa, _u, _p, bb} = vv - gn.all(bb, aa, ws) - -# a good walker's view holds its bindings -def gn.vall_of( - st: S.St, - +ws: List<&2, String>, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> {gn.vall(Grow.view(st), ws) == True{} : Bool}: - match st: - case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: - gv.and_r(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg) - -# a good walker's waiting option and positionals are its command's own, -# whatever the words -def gn.wf_of( - st: S.St, - +ws: List<&2, String>, - +hg: {gn.good(st, ws) == True{} : Bool} -) -> {gn.wf(st) == True{} : Bool}: - match st: - case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: - gv.and_l(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hg) - -# the two halves make a good walker -def gn.join( - st: S.St, - +ws: List<&2, String>, - +hw: {gn.wf(st) == True{} : Bool}, - +ha: {gn.vall(Grow.view(st), ws) == True{} : Bool} -) -> {gn.good(st, ws) == True{} : Bool}: - match st: - case S.St{+mode, +aa, _g, _s, _p, +bb, +oo, _r, _n}: - gv.and_tt(Bool.and(gn.min(mode, aa), gn.pin(oo, aa)), gn.all(bb, aa, ws), hw, ha) - -# bindings named after the arguments and within the words are named and given -def gn.all_named( - bs: List<&2, S.Bind>, - +args: List<&2, S.Arg>, - +ws: List<&2, String>, - +hh: {gn.all(bs, args, ws) == True{} : Bool} -) -> {Laws.all_named(bs, ws, args) == True{} : Bool}: - match bs: - case Nil{}: - {==} - case Con{hd, +tt}: - match hd: - case S.Bind{+nn, +vv}: - +hn = {gv.and_l(gn.nok(args, ws, S.Bind{nn, vv}), gn.all(tt, args, ws), hh) - : {Bool.and(Laws.has_arg(args, nn), gv.ok(ws, vv)) == True{} : Bool}} - gv.and_tt(Laws.named_given(ws, args, S.Bind{nn, vv}), Laws.all_named(tt, ws, args), - gv.and_tt(Laws.has_arg(args, nn), Laws.given(ws, args, S.Bind{nn, vv}), - gv.and_l(Laws.has_arg(args, nn), gv.ok(ws, vv), hn), - gv.or3(String.eq(vv, "true"), Laws.piece(ws, vv), Laws.is_default(args, nn, vv), - gv.and_r(Laws.has_arg(args, nn), gv.ok(ws, vv), hn))), - gn.all_named(tt, args, ws, gv.and_r(gn.nok(args, ws, S.Bind{nn, vv}), gn.all(tt, args, ws), hh))) - -# whether an argument's default, when it has one, is a default of `args` -# under a name one of them has -def gn.dcase(dd: Maybe<&2, String>, +an: String, +args: List<&2, S.Arg>) -> Bool: - match dd: - case None{}: - True{} - case Some{dv}: - Bool.and(Laws.has_arg(args, an), Laws.is_default(args, an, dv)) - -# whether every default of some arguments is a default of `args` -def gn.dsub(tt: List<&2, S.Arg>, +args: List<&2, S.Arg>) -> Bool: - match tt: - case Nil{}: - True{} - case Con{aa, rest}: - S.Arg{+an, _s, _l, _k, _h, _r, S.Fallback{_e, dd}, _c} = aa - Bool.and(gn.dcase(dd, an, args), gn.dsub(rest, args)) - -# a default of some arguments is a default of more -def gn.dcase_mono( - dd: Maybe<&2, String>, - +an: String, - aa: S.Arg, - +tt: List<&2, S.Arg>, - +hh: {gn.dcase(dd, an, tt) == True{} : Bool} -) -> {gn.dcase(dd, an, aa <> tt) == True{} : Bool}: - match dd: - case None{}: - {==} - case Some{+dv}: - match aa: - case S.Arg{+bn, +bs, +bl, +bk, +bh, +br, S.Fallback{+ev, +bd}, +bc}: - gv.and_tt(Laws.has_arg(S.Arg{bn, bs, bl, bk, bh, br, S.Fallback{ev, bd}, bc} <> tt, an), - Laws.is_default(S.Arg{bn, bs, bl, bk, bh, br, S.Fallback{ev, bd}, bc} <> tt, an, dv), - gn.or_rt(String.eq(bn, an), Laws.has_arg(tt, an), - gv.and_l(Laws.has_arg(tt, an), Laws.is_default(tt, an, dv), hh)), - gn.or_rt(Bool.and(String.eq(bn, an), Laws.default_is(bd, dv)), Laws.is_default(tt, an, dv), - gv.and_r(Laws.has_arg(tt, an), Laws.is_default(tt, an, dv), hh))) - -# the defaults of some arguments are defaults of more -def gn.dsub_mono( - xs: List<&2, S.Arg>, - +aa: S.Arg, - +tt: List<&2, S.Arg>, - +hh: {gn.dsub(xs, tt) == True{} : Bool} -) -> {gn.dsub(xs, aa <> tt) == True{} : Bool}: - match xs: - case Nil{}: - {==} - case Con{xx, +xt}: - match xx: - case S.Arg{+xn, _s, _l, _k, _h, _r, S.Fallback{_e, +xd}, _c}: - gv.and_tt(gn.dcase(xd, xn, aa <> tt), gn.dsub(xt, aa <> tt), - gn.dcase_mono(xd, xn, aa, tt, gv.and_l(gn.dcase(xd, xn, tt), gn.dsub(xt, tt), hh)), - gn.dsub_mono(xt, aa, tt, gv.and_r(gn.dcase(xd, xn, tt), gn.dsub(xt, tt), hh))) - -# an argument's own default is a default of a list it heads -def gn.dcase_self( - +ev: Maybe<&2, String>, - dd: Maybe<&2, String>, - +an: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hl: String, - +req: Bool, - +cs: List<&2, String>, - +tt: List<&2, S.Arg> -) -> {gn.dcase(dd, an, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs} <> tt) == True{} : Bool}: - match dd: - case None{}: - {==} - case Some{+dv}: - e1 = Equal.sym(Bool, String.eq(an, an), True{}, Eq.string_eq_self(an)) - e2 = Equal.sym(Bool, String.eq(dv, dv), True{}, Eq.string_eq_self(dv)) - %e1 : {Bool.and(Bool.or(_, Laws.has_arg(tt, an)), - Bool.or(Bool.and(_, String.eq(dv, dv)), Laws.is_default(tt, an, dv))) - == True{} : Bool} - %e2 : {Bool.and(Bool.or(True{}, Laws.has_arg(tt, an)), Bool.or(Bool.and(True{}, _), Laws.is_default(tt, an, dv))) - == True{} : Bool} - {==} - -# every argument's default is a default of the list -def gn.dsub_self(args: List<&2, S.Arg>) -> {gn.dsub(args, args) == True{} : Bool}: - match args: - case Nil{}: - {==} - case Con{aa, +tt}: - match aa: - case S.Arg{+an, +sh, +lo, +kind, +hl, +req, S.Fallback{+ev, +dd}, +cs}: - gv.and_tt(gn.dcase(dd, an, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs} <> tt), - gn.dsub(tt, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs} <> tt), - gn.dcase_self(ev, dd, an, sh, lo, kind, hl, req, cs, tt), - gn.dsub_mono(tt, S.Arg{an, sh, lo, kind, hl, req, S.Fallback{ev, dd}, cs}, tt, gn.dsub_self(tt))) - -# a default goes in front when its name is unbound, named and given -def gn.fone( - skip: Bool, - +nn: String, - +vv: String, - +rr: List<&2, S.Bind>, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +hr: {Laws.all_named(rr, ws, args) == True{} : Bool}, - +hd: {Laws.named_given(ws, args, S.Bind{nn, vv}) == True{} : Bool} -) -> {Laws.all_named(S.fill.one(skip, nn, vv, rr), ws, args) == True{} : Bool}: - match skip: - case True{}: - hr - case False{}: - gv.and_tt(Laws.named_given(ws, args, S.Bind{nn, vv}), Laws.all_named(rr, ws, args), hd, hr) - -# `x`, or `y` or something true, is true -def gn.or_rr( - +xx: Bool, - +yy: Bool, - zz: Bool, - +hh: {zz == True{} : Bool} -) -> {Bool.or(xx, Bool.or(yy, zz)) == True{} : Bool}: - ee = Equal.sym(Bool, zz, True{}, hh) - e2 = Equal.sym(Bool, Bool.or(yy, True{}), True{}, gv.or_t(yy)) - %ee : {Bool.or(xx, Bool.or(yy, _)) == True{} : Bool} - %e2 : {Bool.or(xx, _) == True{} : Bool} - gv.or_t(xx) - -# one argument's default, filled when unbound -def gn.fill_arg( - dd: Maybe<&2, String>, - +an: String, - +rr: List<&2, S.Bind>, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +hr: {Laws.all_named(rr, ws, args) == True{} : Bool}, - +hd: {gn.dcase(dd, an, args) == True{} : Bool} -) -> {Laws.all_named(S.fill.arg.go(dd, an, rr), ws, args) == True{} : Bool}: - match dd: - case None{}: - hr - case Some{+dv}: - gn.fone(S.bound(rr, an), an, dv, rr, ws, args, hr, - gv.and_tt(Laws.has_arg(args, an), Laws.given(ws, args, S.Bind{an, dv}), - gv.and_l(Laws.has_arg(args, an), Laws.is_default(args, an, dv), hd), - gn.or_rr(String.eq(dv, "true"), Laws.piece(ws, dv), Laws.is_default(args, an, dv), - gv.and_r(Laws.has_arg(args, an), Laws.is_default(args, an, dv), hd)))) - -# the defaults filled in are named and given -def gn.fill( - tt: List<&2, S.Arg>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +ws: List<&2, String>, - +hb: {Laws.all_named(bs, ws, args) == True{} : Bool}, - +hs: {gn.dsub(tt, args) == True{} : Bool} -) -> {Laws.all_named(S.fill.args(tt, bs), ws, args) == True{} : Bool}: - match tt: - case Nil{}: - hb - case Con{aa, +rest}: - match aa: - case S.Arg{+an, _s, _l, _k, _h, _r, S.Fallback{_e, +dd}, _c}: - gn.fill_arg(dd, an, S.fill.args(rest, bs), ws, args, - gn.fill(rest, args, bs, ws, hb, gv.and_r(gn.dcase(dd, an, args), gn.dsub(rest, args), hs)), - gv.and_l(gn.dcase(dd, an, args), gn.dsub(rest, args), hs)) - -# reversed bindings are named and given when they are -def gn.rev_named( - xs: List<&2, S.Bind>, - +acc: List<&2, S.Bind>, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +hx: {Laws.all_named(xs, ws, args) == True{} : Bool}, - +ha: {Laws.all_named(acc, ws, args) == True{} : Bool} -) -> {Laws.all_named(List.reverse.go(&2, S.Bind, xs, acc), ws, args) == True{} : Bool}: - match xs: - case Nil{}: - ha - case Con{+hd, +tt}: - gn.rev_named(tt, hd <> acc, ws, args, - gv.and_r(Laws.named_given(ws, args, hd), Laws.all_named(tt, ws, args), hx), - gv.and_tt(Laws.named_given(ws, args, hd), Laws.all_named(acc, ws, args), - gv.and_l(Laws.named_given(ws, args, hd), Laws.all_named(tt, ws, args), hx), ha)) - -# the words `ws` leave the walker at the command with these arguments, above -# these levels, at this path, with these bindings -def gn.At( - +spec: Shake.Cli, - +ws: List<&2, String>, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +bs: List<&2, S.Bind> -) -> Type: - {Grow.view(S.parse.walk(ws, S.parse.start(spec))) == Grow.View{args, up, path, bs} : Grow.View} - -# the bindings a command made while it was current are named and within its -# words -def gn.here( - +spec: Shake.Cli, - +pre: List<&2, String>, - +mid: List<&2, String>, - +args: List<&2, S.Arg>, - +bs: List<&2, S.Bind>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - h_pre: gn.At(spec, pre, args, up, path, []), - h_mid: gn.At(spec, List.append(&2, String, pre, mid), args, up, path, bs) -) -> {gn.all(bs, args, mid) == True{} : Bool}: - +s1 = S.parse.walk(pre, S.parse.start(spec)) - +s2 = S.parse.walk(mid, s1) - +v0 = {Grow.View{args, up, path, []} : Grow.View} - +v2 = {Grow.View{args, up, path, bs} : Grow.View} - hw = gn.wf_of(s1, pre, gn.walk(pre, [], S.parse.start(spec), gn.start(spec, pre))) - ha = Equal.trans(Bool, gn.vall(Grow.view(s1), mid), gn.vall(v0, mid), True{}, - Equal.cong(Grow.View, Bool, vv => gn.vall(vv, mid), Grow.view(s1), v0, h_pre), {==}) - +wm = S.parse.walk(List.append(&2, String, pre, mid), S.parse.start(spec)) - e2 = Equal.trans(Grow.View, Grow.view(s2), Grow.view(wm), v2, - Equal.cong(S.St, Grow.View, st => Grow.view(st), s2, wm, - Equal.sym(S.St, wm, s2, Laws.walk_append(pre, mid, S.parse.start(spec)))), - h_mid) - Equal.trans(Bool, gn.vall(v2, mid), gn.vall(Grow.view(s2), mid), True{}, - Equal.cong(Grow.View, Bool, vv => gn.vall(vv, mid), v2, Grow.view(s2), Equal.sym(Grow.View, Grow.view(s2), v2, e2)), - gn.vall_of(s2, mid, gn.walk(mid, [], s1, gn.join(s1, mid, hw, ha)))) - -def Laws.values_here(spec, pre, mid, post, found, qs, args, bs, up, path, ms, h_done, h_pre, h_mid, h_fr, h_len): - +ws = List.append(&2, String, pre, List.append(&2, String, mid, post)) - +f0 = {S.Level{args, S.fill.args(args, bs)} : S.Level} - +rq = List.reverse(&2, S.Level, S.fill.levels(qs)) - ea = Equal.sym(Maybe<&2, S.Matched>, S.at(found, path), Some{S.matched.build(f0 <> rq, ms)}, - fin.at(spec, ws, found, qs, args, bs, up, path, ms, h_done, h_fr, h_len)) - eo = Equal.sym(List<&2, S.Bind>, S.own(S.matched.build(f0 <> rq, ms)), - List.reverse(&2, S.Bind, S.fill.args(args, bs)), Grow.gw.own_build(f0, rq, ms)) - %ea : {Laws.named_at.go(_, mid, args) == Some{True{}} : Maybe<&2, Bool>} - %eo : {Some{Laws.all_named(_, mid, args)} == Some{True{}} : Maybe<&2, Bool>} - Equal.cong(Bool, Maybe<&2, Bool>, bb => Some{bb}, - Laws.all_named(List.reverse(&2, S.Bind, S.fill.args(args, bs)), mid, args), True{}, - gn.rev_named(S.fill.args(args, bs), [], mid, args, - gn.fill(args, args, bs, mid, - gn.all_named(bs, args, mid, gn.here(spec, pre, mid, args, bs, up, path, h_pre, h_mid)), - gn.dsub_self(args)), - {==})) - -# ---- SHAKE-PARSE-1: a word binds at most one value - -# a walker's bindings count at most `kk` values -def nd.T(st: S.St, +kk: Nat) -> Type: - {Laws.nat_le(Laws.valued(Laws.binds_of(st)), kk) == True{} : Bool} - -# a count is at most itself -def nd.le_refl(nn: Nat) -> {Laws.nat_le(nn, nn) == True{} : Bool}: - match nn: - case 0n: - {==} - case 1n+pp: - nd.le_refl(pp) - -# a count is at most one more -def nd.le_s(nn: Nat) -> {Laws.nat_le(nn, 1n+nn) == True{} : Bool}: - match nn: - case 0n: - {==} - case 1n+pp: - nd.le_s(pp) - -# at most is transitive -def nd.le_trans( - aa: Nat, - bb: Nat, - cc: Nat, - h1: {Laws.nat_le(aa, bb) == True{} : Bool}, - h2: {Laws.nat_le(bb, cc) == True{} : Bool} -) -> {Laws.nat_le(aa, cc) == True{} : Bool}: - match aa: - case 0n: - {==} - case 1n+pp: - match bb: - case 0n: - Empty.absurd({Laws.nat_le(1n+pp, cc) == True{} : Bool}, Walk.wk.false_true(h1)) - case 1n+qq: - match cc: - case 0n: - Empty.absurd({False{} == True{} : Bool}, Walk.wk.false_true(h2)) - case 1n+rr: - nd.le_trans(pp, qq, rr, h1, h2) - -# a binding in front counts at most one more -def nd.put_le(flag: Bool, +nn: Nat) -> {Laws.nat_le(Laws.valued.at(flag, nn), 1n+nn) == True{} : Bool}: - match flag: - case True{}: - nd.le_s(nn) - case False{}: - nd.le_refl(1n+nn) - -# the once-check keeps the walker's bindings -def nd.once_keep( - again: Bool, - st: S.St, - +name: String, - +path: List<&2, String> -) -> {Laws.binds_of(S.parse.once(again, st, name, path)) == Laws.binds_of(st) : List<&2, S.Bind>}: - match again: - case True{}: - match st: - case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: - {==} - case False{}: - {==} - -# the once-check keeps a count -def nd.once( - again: Bool, - +st: S.St, - +name: String, - +path: List<&2, String>, - +kk: Nat, - hh: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), kk) == True{} : Bool} -) -> nd.T(S.parse.once(again, st, name, path), kk): - ee = Equal.sym(List<&2, S.Bind>, Laws.binds_of(S.parse.once(again, st, name, path)), Laws.binds_of(st), - nd.once_keep(again, st, name, path)) - %ee : {Laws.nat_le(Laws.valued(_), kk) == True{} : Bool} - hh - -# a binding -def nd.put( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - nd.put_le(String.eq(val, "true"), Laws.valued(binds)) - -# a value bound when allowed -def nd.choice( - ok: Bool, - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match ok: - case True{}: - nd.put(name, val, args, up, subs, path, binds, pos, raw, seen) - case False{}: - nd.le_s(Laws.valued(binds)) - -# an option's value -def nd.opt_val( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - nd.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, raw, - seen) - -# a flag -def nd.flag( - val: Maybe<&2, String>, - +name: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match val: - case None{}: - nd.put(name, "true", args, up, subs, path, binds, pos, raw, seen) - case Some{_v}: - nd.le_s(Laws.valued(binds)) - -# an option -def nd.opt( - val: Maybe<&2, String>, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match val: - case None{}: - nd.le_s(Laws.valued(binds)) - case Some{vv}: - nd.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen) - -# a found argument, by its kind -def nd.akind( - kk: S.ArgKind, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, - seen), 1n+Laws.valued(binds)): - match kk: - case S.Flag{}: - nd.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), - name, path, 1n+Laws.valued(binds), nd.flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen)) - case S.Opt{}: - nd.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), - name, path, 1n+Laws.valued(binds), nd.opt(val, name, args, up, subs, path, binds, pos, raw, seen)) - case S.Many{}: - nd.opt(val, name, args, up, subs, path, binds, pos, raw, seen) - case S.Pos{}: - nd.le_s(Laws.valued(binds)) - case S.Rest{}: - nd.le_s(Laws.valued(binds)) - -# a found argument, or an unknown flag -def nd.arg( - found: Maybe<&2, S.Arg>, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match found: - case None{}: - nd.le_s(Laws.valued(binds)) - case Some{aa}: - match aa: - case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: - nd.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen) - -# an unknown long option: a request for help, or an unknown flag -def nd.lhelp( - hit: Bool, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match hit: - case True{}: - nd.le_s(Laws.valued(binds)) - case False{}: - nd.le_s(Laws.valued(binds)) - -# a long option the current command spells, or an unknown one -def nd.found( - found: Maybe<&2, S.Arg>, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), - 1n+Laws.valued(binds)): - match found: - case None{}: - nd.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen) - case Some{aa}: - nd.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen) - -# a long option with a name -def nd.named( - empty: Bool, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, - seen), 1n+Laws.valued(binds)): - match empty: - case True{}: - nd.le_s(Laws.valued(binds)) - case False{}: - nd.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen) - -# a long option after the `=` cut -def nd.cut( - nv: String & Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match nv: - case (+name, +val): - nd.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen) - -# a long option -def nd.long( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - nd.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen) - -# the bound on a cluster so far: the count it started with while no letter -# has ended it, one more once one has -def nd.inv(st: S.St, +n0: Nat, done: Bool) -> Bool: - match done: - case True{}: - Laws.nat_le(Laws.valued(Laws.binds_of(st)), 1n+n0) - case False{}: - Laws.nat_le(Laws.valued(Laws.binds_of(st)), n0) - -# a walker within the count it started with is within the bound, ended or not -def nd.inv_any( - done: Bool, - +st: S.St, - +n0: Nat, - +hh: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), n0) == True{} : Bool} -) -> {nd.inv(st, n0, done) == True{} : Bool}: - match done: - case True{}: - nd.le_trans(Laws.valued(Laws.binds_of(st)), n0, 1n+n0, hh, nd.le_s(n0)) - case False{}: - hh - -# one more than a count within the start, lifted -def nd.lift( - +st: S.St, - +bs: List<&2, S.Bind>, - +n0: Nat, - ht: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), 1n+Laws.valued(bs)) == True{} : Bool}, - hh: {Laws.nat_le(Laws.valued(bs), n0) == True{} : Bool} -) -> {nd.inv(st, n0, True{}) == True{} : Bool}: - nd.le_trans(Laws.valued(Laws.binds_of(st)), 1n+Laws.valued(bs), 1n+n0, ht, hh) - -# a flag letter: `true` goes on, or `=` ends the cluster refused -def nd.lflag( - eq: Bool, - +name: String, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +n0: Nat, - +hh: {Laws.nat_le(Laws.valued(binds), n0) == True{} : Bool} -) -> {nd.inv(S.parse.once(S.bound(binds, name), S.parse.take_flag(S.cluster.val.flag(eq, rest), name, orig, args, up, - subs, path, binds, pos, raw, seen), name, path), n0, eq) == True{} : Bool}: - match eq: - case True{}: - +st = S.parse.take_flag(Some{rest}, name, orig, args, up, subs, path, binds, pos, raw, seen) - ee = Equal.sym(List<&2, S.Bind>, Laws.binds_of(S.parse.once(S.bound(binds, name), st, name, path)), - Laws.binds_of(st), nd.once_keep(S.bound(binds, name), st, name, path)) - %ee : {Laws.nat_le(Laws.valued(_), 1n+n0) == True{} : Bool} - nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) - case False{}: - +st = S.parse.take_flag(None{}, name, orig, args, up, subs, path, binds, pos, raw, seen) - ee = Equal.sym(List<&2, S.Bind>, Laws.binds_of(S.parse.once(S.bound(binds, name), st, name, path)), - Laws.binds_of(st), nd.once_keep(S.bound(binds, name), st, name, path)) - %ee : {Laws.nat_le(Laws.valued(_), n0) == True{} : Bool} - hh - -# a letter's effect, and whether it ends the cluster -def nd.lpair( - found: Maybe<&2, S.Arg>, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +n0: Nat, - +hh: {Laws.nat_le(Laws.valued(binds), n0) == True{} : Bool} -) -> {nd.inv(S.parse.take_arg(found, S.cluster.val(found, rest), orig, args, up, subs, path, binds, pos, raw, seen), n0, - S.cluster.ends(found, rest)) == True{} : Bool}: - match found: - case None{}: - nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) - case Some{aa}: - match aa: - case S.Arg{+name, +sh, +lo, kind, +hl, +req, +dd, +cs}: - match kind: - case S.Flag{}: - nd.lflag(String.starts_with(rest, "="), name, rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, - hh) - case S.Opt{}: - +vv = S.short_of.val(String.starts_with(rest, "="), rest) - nd.lift(S.parse.once(S.bound(binds, name), S.parse.take_opt(vv, name, args, up, subs, path, binds, pos, - raw, - seen), name, path), binds, n0, - nd.once(S.bound(binds, name), S.parse.take_opt(vv, name, args, up, subs, path, binds, pos, raw, seen), - name, path, 1n+Laws.valued(binds), nd.opt(vv, name, args, up, subs, path, binds, pos, raw, seen)), hh) - case S.Many{}: - +vv = S.short_of.val(String.starts_with(rest, "="), rest) - nd.lift(S.parse.take_opt(vv, name, args, up, subs, path, binds, pos, raw, seen), binds, n0, - nd.opt(vv, name, args, up, subs, path, binds, pos, raw, seen), hh) - case S.Pos{}: - nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) - case S.Rest{}: - nd.le_trans(Laws.valued(binds), n0, 1n+n0, hh, nd.le_s(n0)) - -# a walker within the bound is within one more than the start -def nd.inv_out( - done: Bool, - +st: S.St, - +n0: Nat, - +hh: {nd.inv(st, n0, done) == True{} : Bool} -) -> nd.T(st, 1n+n0): - match done: - case True{}: - hh - case False{}: - nd.le_trans(Laws.valued(Laws.binds_of(st)), n0, 1n+n0, hh, nd.le_s(n0)) - -# one letter, by the walker's mode -def nd.cmode( - mode: S.Mode, - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +n0: Nat, - +hh: {Laws.nat_le(Laws.valued(binds), n0) == True{} : Bool} -) -> {nd.inv(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), n0, - S.cluster.ends(S.by_short(args, SCon{cc, SNil{}}), rest)) == True{} : Bool}: - match mode: - case S.Dead{+ee}: - nd.inv_any(S.cluster.ends(S.by_short(args, SCon{cc, SNil{}}), rest), - S.St{S.Dead{ee}, args, up, subs, path, binds, pos, raw, seen}, n0, hh) - case S.Free{}: - nd.lpair(S.by_short(args, SCon{cc, SNil{}}), rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) - case S.Need{_n}: - nd.lpair(S.by_short(args, SCon{cc, SNil{}}), rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) - case S.Help{}: - nd.lpair(S.by_short(args, SCon{cc, SNil{}}), rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) - -# one letter against a walker within the start -def nd.capply( - st: S.St, - +cc: Char, - +rest: String, - +orig: String, - +n0: Nat, - +hh: {Laws.nat_le(Laws.valued(Laws.binds_of(st)), n0) == True{} : Bool} -) -> {nd.inv(S.cluster.apply(st, cc, rest, orig), n0, S.cluster.done(False{}, cc, rest, st)) == True{} : Bool}: - match st: - case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - nd.cmode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, n0, hh) - -# a letter, and whether the cluster goes on -def nd.letter( - done: Bool, - +cc: Char, - +rest: String, - +orig: String, - +st: S.St, - +n0: Nat, - +hh: {nd.inv(st, n0, done) == True{} : Bool} -) -> {nd.inv(S.cluster.letter(done, cc, rest, orig, st), n0, S.cluster.done(done, cc, rest, st)) == True{} : Bool}: - match done: - case True{}: - hh - case False{}: - nd.capply(st, cc, rest, orig, n0, hh) - -# a whole cluster adds at most one value to what it started with -def nd.cluster( - body: String, - +orig: String, - +st: S.St, - +done: Bool, - +n0: Nat, - +hh: {nd.inv(st, n0, done) == True{} : Bool} -) -> nd.T(S.cluster(body, orig, st, done), 1n+n0): - match body: - case SNil{}: - nd.inv_out(done, st, n0, hh) - case SCon{+cc, +rest}: - nd.cluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), n0, - nd.letter(done, cc, rest, orig, st, n0, hh)) - -# a short option: a request for help, or a cluster -def nd.sgo( - hit: Bool, - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_short.go(hit, body, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match hit: - case True{}: - nd.le_s(Laws.valued(binds)) - case False{}: - nd.cluster(body, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, raw, seen}, False{}, Laws.valued(binds), - nd.le_refl(Laws.valued(binds))) - -# a short option -def nd.short( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - nd.sgo(S.short_help(body, args, seen), body, orig, args, up, subs, path, binds, pos, raw, seen) - -# a positional -def nd.take_pos( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - pos: List<&2, S.Arg>, - +raw: Bool -) -> nd.T(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), 1n+Laws.valued(binds)): - match pos: - case Nil{}: - nd.le_s(Laws.valued(binds)) - case Con{ph, +tt}: - match ph: - case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: - nd.choice(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), - S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}) - -# entering a subcommand starts with no bindings -def nd.enter( - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +path: List<&2, String>, - +binds: List<&2, S.Bind> -) -> nd.T(S.parse.enter(ss, args, up, path, binds), 1n+Laws.valued(binds)): - match ss: - case S.Sub{_n, _a, _g, _c}: - {==} - -# a subcommand entered, or a missing positional -def nd.enter_or( - miss: Maybe<&2, String>, - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match miss: - case None{}: - nd.enter(ss, args, up, path, binds) - case Some{_n}: - nd.le_s(Laws.valued(binds)) - -# a word -def nd.word_go( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match found: - case None{}: - nd.take_pos(tok, args, up, subs, path, binds, pos, raw) - case Some{ss}: - nd.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen) - -# a word -def nd.word( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - nd.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen) - -# `-` alone, or a cluster -def nd.short_go( - alone: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - match alone: - case True{}: - nd.word(tok, args, up, subs, path, binds, pos, False{}, seen) - case False{}: - nd.short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, False{}, seen) - -# a short option or a word -def nd.step_short( - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - match dash: - case True{}: - nd.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen) - case False{}: - nd.word(tok, args, up, subs, path, binds, pos, False{}, seen) - -# a long option, a short one, or a word -def nd.kind_go( - long: Bool, - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - match long: - case True{}: - nd.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen) - case False{}: - nd.step_short(dash, tok, args, up, subs, path, binds, pos, seen) - -# a word by its shape -def nd.skind( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - nd.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen) - -# `help` -def nd.help( - hit: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - match hit: - case True{}: - nd.le_s(Laws.valued(binds)) - case False{}: - nd.skind(tok, args, up, subs, path, binds, pos, seen) - -# `--` -def nd.end( - end: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - match end: - case True{}: - nd.le_s(Laws.valued(binds)) - case False{}: - nd.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen) - -# after `--` -def nd.raw( - raw: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool -) -> nd.T(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), 1n+Laws.valued(binds)): - match raw: - case True{}: - nd.take_pos(tok, args, up, subs, path, binds, pos, True{}) - case False{}: - nd.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen) - -# a waiting option -def nd.need( - flag: Bool, - +name: String, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match flag: - case True{}: - nd.le_s(Laws.valued(binds)) - case False{}: - nd.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen) - -# a word of a help path -def nd.help_path( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.step.help_path(found, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match found: - case None{}: - nd.le_s(Laws.valued(binds)) - case Some{ss}: - match ss: - case S.Sub{_n, _a, _g, _c}: - nd.le_s(Laws.valued(binds)) - -# one word, by the walker's mode -def nd.mode( - mode: S.Mode, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool -) -> nd.T(S.parse.step.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen), 1n+Laws.valued(binds)): - match mode: - case S.Dead{_e}: - nd.le_s(Laws.valued(binds)) - case S.Need{name}: - nd.need(S.looks_flag(tok), name, tok, args, up, subs, path, binds, pos, raw, seen) - case S.Help{}: - nd.help_path(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen) - case S.Free{}: - nd.raw(raw, tok, args, up, subs, path, binds, pos, seen) - -def Laws.one_value_per_word(tok, st): - match st: - case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - nd.mode(mode, tok, args, up, subs, path, binds, pos, raw, seen) - -# ---- SHAKE-PARSE-1: no word is ignored - -# whether a mode has failed, waits for a value, reads a help path -def nu.dead_m(mode: S.Mode) -> Bool: - match mode: - case S.Dead{_e}: - True{} - case S.Free{}: - False{} - case S.Need{_n}: - False{} - case S.Help{}: - False{} - -def nu.need_m(mode: S.Mode) -> Bool: - match mode: - case S.Need{_n}: - True{} - case S.Free{}: - False{} - case S.Dead{_e}: - False{} - case S.Help{}: - False{} - -def nu.help_m(mode: S.Mode) -> Bool: - match mode: - case S.Help{}: - True{} - case S.Free{}: - False{} - case S.Need{_n}: - False{} - case S.Dead{_e}: - False{} - -# a walker's mode facts, its raw switch, its binding and level counts -def nu.dead(st: S.St) -> Bool: - S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st - nu.dead_m(mode) - -def nu.need(st: S.St) -> Bool: - S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st - nu.need_m(mode) - -def nu.help(st: S.St) -> Bool: - S.St{mode, _a, _g, _s, _p, _b, _o, _r, _n} = st - nu.help_m(mode) - -def nu.raw(st: S.St) -> Bool: - S.St{_m, _a, _g, _s, _p, _b, _o, raw, _n} = st - raw - -def nu.blen(st: S.St) -> Nat: - S.St{_m, _a, _g, _s, _p, binds, _o, _r, _n} = st - List.length(&2, S.Bind, binds) - -def nu.ulen(st: S.St) -> Nat: - S.St{_m, _a, up, _s, _p, _b, _o, _r, _n} = st - List.length(&2, S.Level, up) - -# a walker that is not the other one -def nu.N(res: S.St, orig: S.St) -> Type: - {res == orig : S.St} -> Empty - -# one fact that tells two walkers apart -def nu.apart( - +res: S.St, - +orig: S.St, - ff: S.St -> Bool, - hr: {ff(res) == True{} : Bool}, - ho: {ff(orig) == False{} : Bool} -) -> nu.N(res, orig): - ee => Walk.wk.true_false(Equal.trans(Bool, True{}, ff(res), False{}, Equal.sym(Bool, ff(res), True{}, hr), - Equal.trans(Bool, ff(res), ff(orig), False{}, Equal.cong(S.St, Bool, ff, res, orig, ee), ho))) - -# zero is not one more than a count -def nu.z_ty(nn: Nat) -> Type: - match nn: - case 0n: - Unit - case 1n+_p: - Empty - -# the count before this one -def nu.pred(nn: Nat) -> Nat: - match nn: - case 0n: - 0n - case 1n+pp: - pp - -# no count is one more than itself -def nu.succ_ne(nn: Nat, ee: {1n+nn == nn : Nat}) -> Empty: - match nn: - case 0n: - e2 = Equal.sym(Nat, 1n, 0n, ee) - %e2 : nu.z_ty(_) - Unit{} - case 1n+pp: - nu.succ_ne(pp, Equal.cong(Nat, Nat, xx => nu.pred(xx), 1n+(1n+pp), 1n+pp, ee)) - -# more bindings tell two walkers apart -def nu.more( - +res: S.St, - +orig: S.St, - +nn: Nat, - hr: {nu.blen(res) == 1n+nn : Nat}, - ho: {nu.blen(orig) == nn : Nat} -) -> nu.N(res, orig): - ee => nu.succ_ne(nn, Equal.trans(Nat, 1n+nn, nu.blen(res), nn, Equal.sym(Nat, nu.blen(res), 1n+nn, hr), - Equal.trans(Nat, nu.blen(res), nu.blen(orig), nn, Equal.cong(S.St, Nat, ss => nu.blen(ss), res, orig, ee), ho))) - -# more levels tell two walkers apart -def nu.deeper( - +res: S.St, - +orig: S.St, - +nn: Nat, - hr: {nu.ulen(res) == 1n+nn : Nat}, - ho: {nu.ulen(orig) == nn : Nat} -) -> nu.N(res, orig): - ee => nu.succ_ne(nn, Equal.trans(Nat, 1n+nn, nu.ulen(res), nn, Equal.sym(Nat, nu.ulen(res), 1n+nn, hr), - Equal.trans(Nat, nu.ulen(res), nu.ulen(orig), nn, Equal.cong(S.St, Nat, ss => nu.ulen(ss), res, orig, ee), ho))) - -# a binding tells the walker apart -def nu.put( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode -) -> nu.N(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, binds, - pos, raw, seen}): - nu.more(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, binds, - pos, raw, seen}, - List.length(&2, S.Bind, binds), {==}, {==}) - -# a value bound or refused -def nu.choice( - ok: Bool, - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}): - match ok: - case True{}: - nu.put(name, val, args, up, subs, path, binds, pos, raw, seen, m0) - case False{}: - nu.apart(S.parse.take_choice(False{}, name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, - up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - -# an option's value -def nu.opt_val( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, - binds, pos, raw, seen}): - nu.choice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, raw, - seen, - m0, hd) - -# a flag -def nu.flag( - val: Maybe<&2, String>, - +name: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}): - match val: - case None{}: - nu.put(name, "true", args, up, subs, path, binds, pos, raw, seen, m0) - case Some{+vv}: - nu.apart(S.parse.take_flag(Some{vv}, name, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, - up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - -# an option -def nu.opt( - val: Maybe<&2, String>, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, - binds, pos, raw, seen}): - match val: - case None{}: - nu.apart(S.parse.take_opt(None{}, name, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}, - ss => nu.need(ss), {==}, hn) - case Some{vv}: - nu.opt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, m0, hd) - -# a repeat fails -def nu.once_dead( - st: S.St, - +name: String, - +path0: List<&2, String> -) -> {nu.dead(S.parse.once(True{}, st, name, path0)) == True{} : Bool}: - match st: - case S.St{_m, _a, _g, _s, _p, _b, _o, _r, _n}: - {==} - -# the once-check: a repeat fails, else the walker it was given -def nu.once( - again: Bool, - +st: S.St, - +name: String, - +path0: List<&2, String>, - +orig: S.St, - +hdo: {nu.dead(orig) == False{} : Bool}, - hh: nu.N(st, orig) -) -> nu.N(S.parse.once(again, st, name, path0), orig): - match again: - case True{}: - nu.apart(S.parse.once(True{}, st, name, path0), orig, ss => nu.dead(ss), nu.once_dead(st, name, path0), hdo) - case False{}: - hh - -# a found argument, by its kind -def nu.akind( - kk: S.ArgKind, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, - subs, path, binds, pos, raw, seen}): - match kk: - case S.Flag{}: - nu.once(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), - name, path, S.St{m0, args, up, subs, path, binds, pos, raw, seen}, hd, nu.flag(val, name, orig, args, up, - subs, path, binds, pos, raw, seen, m0, hd)) - case S.Opt{}: - nu.once(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), - name, path, S.St{m0, args, up, subs, path, binds, pos, raw, seen}, hd, nu.opt(val, name, args, up, subs, path, - binds, pos, raw, seen, m0, hd, hn)) - case S.Many{}: - nu.opt(val, name, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn) - case S.Pos{}: - nu.apart(S.parse.take_arg.kind(S.Pos{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, - args, up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - case S.Rest{}: - nu.apart(S.parse.take_arg.kind(S.Rest{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, - args, up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - -# a found argument, or an unknown flag -def nu.arg( - found: Maybe<&2, S.Arg>, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}): - match found: - case None{}: - nu.apart(S.parse.take_arg(None{}, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, - subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - case Some{aa}: - match aa: - case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: - nu.akind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn) - -# an unknown long option: a request for help, or an unknown flag -def nu.lhelp( - hit: Bool, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_long.help(hit, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}): - match hit: - case True{}: - nu.apart(S.parse.take_long.help(True{}, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, - subs, path, binds, pos, raw, seen}, ss => nu.help(ss), {==}, hl) - case False{}: - nu.apart(S.parse.take_long.help(False{}, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, - subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - -# a long option the current command spells, or an unknown one -def nu.found( - found: Maybe<&2, S.Arg>, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_long.found(found, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, - args, up, subs, path, binds, pos, raw, seen}): - match found: - case None{}: - nu.lhelp(S.long_help(name, val, seen), orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hl) - case Some{aa}: - nu.arg(Some{aa}, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn) - -# a long option with a name -def nu.named( - empty: Bool, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_long.named(empty, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, - up, subs, path, binds, pos, raw, seen}): - match empty: - case True{}: - nu.apart(S.parse.take_long.named(True{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, - args, up, subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - case False{}: - nu.found(S.by_long(args, name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn, hl) - -# a long option after the `=` cut -def nu.cut( - nv: String & Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_long.cut(nv, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, - binds, pos, raw, seen}): - match nv: - case (+name, +val): - nu.named(String.is_empty(name), name, val, orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn, - hl) - -# a long option -def nu.long( - +body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_long(body, orig, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, - binds, pos, raw, seen}): - nu.cut(S.cut_eq(body), orig, args, up, subs, path, binds, pos, raw, seen, m0, hd, hn, hl) - -# a cluster walker changed from one with `l0` bindings, free: no fewer -# bindings, and failed, waiting, reading a help path, or more bindings -def nu.J(+st: S.St, +l0: Nat) -> Bool: - Bool.and(Laws.nat_le(l0, nu.blen(st)), - Bool.or(Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))), Laws.nat_le(1n+l0, nu.blen(st)))) - -# a walker that is not free and has no fewer bindings has changed -def nu.j_stop( - +st: S.St, - +l0: Nat, - hb: {Laws.nat_le(l0, nu.blen(st)) == True{} : Bool}, - hs: {Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))) == True{} : Bool} -) -> {nu.J(st, l0) == True{} : Bool}: - ee = Equal.sym(Bool, Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))), True{}, hs) - %ee : {Bool.and(Laws.nat_le(l0, nu.blen(st)), Bool.or(_, Laws.nat_le(1n+l0, nu.blen(st)))) == True{} : Bool} - gv.and_tt(Laws.nat_le(l0, nu.blen(st)), True{}, hb, {==}) - -# a binding changes a cluster walker -def nu.jput( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: - gv.and_tt(Laws.nat_le(l0, 1n+List.length(&2, S.Bind, binds)), Laws.nat_le(l0, List.length(&2, S.Bind, binds)), - nd.le_trans(l0, List.length(&2, S.Bind, binds), 1n+List.length(&2, S.Bind, binds), hl0, - nd.le_s(List.length(&2, S.Bind, binds))), hl0) - -# bound or refused -def nu.jchoice( - ok: Bool, - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: - match ok: - case True{}: - nu.jput(name, val, args, up, subs, path, binds, pos, raw, seen, l0, hl0) - case False{}: - nu.j_stop(S.parse.take_choice(False{}, name, val, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) - -# an option's value -def nu.jopt_val( - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.take_opt_val(name, val, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: - nu.jchoice(S.allowed(S.parse.choices(S.by_name(args, name)), val), name, val, args, up, subs, path, binds, pos, raw, - seen, l0, hl0) - -# a flag -def nu.jflag( - val: Maybe<&2, String>, - +name: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: - match val: - case None{}: - nu.jput(name, "true", args, up, subs, path, binds, pos, raw, seen, l0, hl0) - case Some{+vv}: - nu.j_stop(S.parse.take_flag(Some{vv}, name, orig, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) - -# an option -def nu.jopt( - val: Maybe<&2, String>, - +name: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: - match val: - case None{}: - nu.j_stop(S.parse.take_opt(None{}, name, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) - case Some{vv}: - nu.jopt_val(name, vv, args, up, subs, path, binds, pos, raw, seen, l0, hl0) - -# the once-check keeps a changed cluster walker changed -def nu.jonce( - again: Bool, - +st: S.St, - +name: String, - +path0: List<&2, String>, - +l0: Nat, - +hh: {nu.J(st, l0) == True{} : Bool} -) -> {nu.J(S.parse.once(again, st, name, path0), l0) == True{} : Bool}: - match again: - case True{}: - match st: - case S.St{+mm, +aa, +gg, +ss, +pp, +bb, +oo, +rr, +nn}: - nu.j_stop(S.parse.once(True{}, S.St{mm, aa, gg, ss, pp, bb, oo, rr, nn}, name, path0), l0, - gv.and_l(Laws.nat_le(l0, List.length(&2, S.Bind, bb)), - Bool.or(Bool.or(nu.dead_m(mm), Bool.or(nu.need_m(mm), nu.help_m(mm))), - Laws.nat_le(1n+l0, List.length(&2, S.Bind, bb))), hh), {==}) - case False{}: - hh - -# a found argument, by its kind -def nu.jakind( - kk: S.ArgKind, - +name: String, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.take_arg.kind(kk, name, val, orig, args, up, subs, path, binds, pos, raw, seen), - l0) == True{} : Bool}: - match kk: - case S.Flag{}: - nu.jonce(S.bound(binds, name), S.parse.take_flag(val, name, orig, args, up, subs, path, binds, pos, raw, seen), - name, path, l0, nu.jflag(val, name, orig, args, up, subs, path, binds, pos, raw, seen, l0, hl0)) - case S.Opt{}: - nu.jonce(S.bound(binds, name), S.parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen), - name, path, l0, nu.jopt(val, name, args, up, subs, path, binds, pos, raw, seen, l0, hl0)) - case S.Many{}: - nu.jopt(val, name, args, up, subs, path, binds, pos, raw, seen, l0, hl0) - case S.Pos{}: - nu.j_stop(S.parse.take_arg.kind(S.Pos{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), l0, hl0, - {==}) - case S.Rest{}: - nu.j_stop(S.parse.take_arg.kind(S.Rest{}, name, val, orig, args, up, subs, path, binds, pos, raw, seen), l0, - hl0, {==}) - -# a found argument, or an unknown flag -def nu.jarg( - found: Maybe<&2, S.Arg>, - +val: Maybe<&2, String>, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.parse.take_arg(found, val, orig, args, up, subs, path, binds, pos, raw, seen), l0) == True{} : Bool}: - match found: - case None{}: - nu.j_stop(S.parse.take_arg(None{}, val, orig, args, up, subs, path, binds, pos, raw, seen), l0, hl0, {==}) - case Some{aa}: - match aa: - case S.Arg{+name, _s, _l, kind, _h, _r, _d, _c}: - nu.jakind(kind, name, val, orig, args, up, subs, path, binds, pos, raw, seen, l0, hl0) - -# one letter, by the walker's mode -def nu.jmode( - mode: S.Mode, - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +l0: Nat, - +hl0: {Laws.nat_le(l0, List.length(&2, S.Bind, binds)) == True{} : Bool} -) -> {nu.J(S.cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen), - l0) == True{} : Bool}: - match mode: - case S.Dead{+ee}: - nu.j_stop(S.St{S.Dead{ee}, args, up, subs, path, binds, pos, raw, seen}, l0, hl0, {==}) - case S.Free{}: - nu.jarg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, l0, hl0) - case S.Need{_n}: - nu.jarg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, l0, hl0) - case S.Help{}: - nu.jarg(S.by_short(args, SCon{cc, SNil{}}), S.cluster.val(S.by_short(args, SCon{cc, SNil{}}), rest), orig, args, - up, subs, path, binds, pos, raw, seen, l0, hl0) - -# one letter against a walker with no fewer bindings than the start -def nu.japply( - st: S.St, - +cc: Char, - +rest: String, - +orig: String, - +l0: Nat, - +hb: {Laws.nat_le(l0, nu.blen(st)) == True{} : Bool} -) -> {nu.J(S.cluster.apply(st, cc, rest, orig), l0) == True{} : Bool}: - match st: - case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - nu.jmode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen, l0, hb) - -# a changed walker has no fewer bindings -def nu.j_le( - +st: S.St, - +l0: Nat, - +hh: {nu.J(st, l0) == True{} : Bool} -) -> {Laws.nat_le(l0, nu.blen(st)) == True{} : Bool}: - gv.and_l(Laws.nat_le(l0, nu.blen(st)), - Bool.or(Bool.or(nu.dead(st), Bool.or(nu.need(st), nu.help(st))), Laws.nat_le(1n+l0, nu.blen(st))), hh) - -# a letter keeps a changed walker changed -def nu.jletter( - done: Bool, - +cc: Char, - +rest: String, - +orig: String, - +st: S.St, - +l0: Nat, - +hh: {nu.J(st, l0) == True{} : Bool} -) -> {nu.J(S.cluster.letter(done, cc, rest, orig, st), l0) == True{} : Bool}: - match done: - case True{}: - hh - case False{}: - nu.japply(st, cc, rest, orig, l0, nu.j_le(st, l0, hh)) - -# the rest of a cluster keeps a changed walker changed -def nu.jcluster( - body: String, - +orig: String, - +st: S.St, - +done: Bool, - +l0: Nat, - +hh: {nu.J(st, l0) == True{} : Bool} -) -> {nu.J(S.cluster(body, orig, st, done), l0) == True{} : Bool}: - match body: - case SNil{}: - hh - case SCon{+cc, +rest}: - nu.jcluster(rest, orig, S.cluster.letter(done, cc, rest, orig, st), S.cluster.done(done, cc, rest, st), l0, - nu.jletter(done, cc, rest, orig, st, l0, hh)) - -# a count is never above itself by one -def nu.le_sn(nn: Nat) -> {Laws.nat_le(1n+nn, nn) == False{} : Bool}: - match nn: - case 0n: - {==} - case 1n+pp: - nu.le_sn(pp) - -# a cluster of at least one letter changes the walker -def nu.cluster1( - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.cluster(SCon{cc, rest}, orig, S.St{S.Free{}, args, up, subs, path, binds, pos, False{}, seen}, - False{}), S.St{m0, args, up, subs, path, binds, pos, False{}, seen}): - +l0 = List.length(&2, S.Bind, binds) - +s0 = {S.St{S.Free{}, args, up, subs, path, binds, pos, False{}, seen} : S.St} - +fin = S.cluster(SCon{cc, rest}, orig, s0, False{}) - hj = nu.jcluster(rest, orig, S.cluster.letter(False{}, cc, rest, orig, s0), S.cluster.done(False{}, cc, rest, s0), l0, - nu.japply(s0, cc, rest, orig, l0, nd.le_refl(l0))) - e1 = Equal.sym(Bool, nu.dead_m(m0), False{}, hd) - e2 = Equal.sym(Bool, nu.need_m(m0), False{}, hn) - e3 = Equal.sym(Bool, nu.help_m(m0), False{}, hl) - e4 = Equal.sym(Bool, Laws.nat_le(l0, l0), True{}, nd.le_refl(l0)) - e5 = Equal.sym(Bool, Laws.nat_le(1n+l0, l0), False{}, nu.le_sn(l0)) - jo = {%e1 : {Bool.and(Laws.nat_le(l0, l0), Bool.or(Bool.or(_, Bool.or(nu.need_m(m0), nu.help_m(m0))), - Laws.nat_le(1n+l0, l0))) == False{} : Bool} - %e2 : {Bool.and(Laws.nat_le(l0, l0), Bool.or(Bool.or(False{}, Bool.or(_, nu.help_m(m0))), - Laws.nat_le(1n+l0, l0))) == False{} : Bool} - %e3 : {Bool.and(Laws.nat_le(l0, l0), Bool.or(Bool.or(False{}, Bool.or(False{}, _)), - Laws.nat_le(1n+l0, l0))) == False{} : Bool} - %e4 : {Bool.and(_, Laws.nat_le(1n+l0, l0)) == False{} : Bool} - %e5 : {_ == False{} : Bool} - {==} : {nu.J(S.St{m0, args, up, subs, path, binds, pos, False{}, seen}, l0) == False{} : Bool}} - nu.apart(fin, S.St{m0, args, up, subs, path, binds, pos, False{}, seen}, ss => nu.J(ss, l0), hj, jo) - -# a nonempty short option body: a request for help, or a cluster -def nu.sgo( - hit: Bool, - +cc: Char, - +rest: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_short.go(hit, SCon{cc, rest}, orig, args, up, subs, path, binds, pos, False{}, seen), - S.St{m0, args, up, subs, path, binds, pos, False{}, seen}): - match hit: - case True{}: - nu.apart(S.parse.take_short.go(True{}, SCon{cc, rest}, orig, args, up, subs, path, binds, pos, False{}, seen), - S.St{m0, args, up, subs, path, binds, pos, False{}, seen}, ss => nu.help(ss), {==}, hl) - case False{}: - nu.cluster1(cc, rest, orig, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) - -# a nonempty cluster body is a char in front of the rest -def nu.cl0( - body: String, - +orig: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool}, - +he: {String.is_empty(body) == False{} : Bool} -) -> nu.N(S.parse.take_short(body, orig, args, up, subs, path, binds, pos, False{}, seen), - S.St{m0, args, up, subs, path, binds, pos, False{}, seen}): - match body: - case SNil{}: - Empty.absurd(nu.N(S.parse.take_short(SNil{}, orig, args, up, subs, path, binds, pos, False{}, seen), - S.St{m0, args, up, subs, path, binds, pos, False{}, seen}), Walk.wk.true_false(he)) - case SCon{+cc, +rest}: - nu.sgo(S.short_help(SCon{cc, rest}, args, seen), cc, rest, orig, args, up, subs, path, binds, pos, seen, m0, hd, - hn, hl) - -# a value bound or refused, against any walker with as many bindings that has -# not failed -def nu.choice_any( - ok: Bool, - +name: String, - +val: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +orig: S.St, - +hb: {nu.blen(orig) == List.length(&2, S.Bind, binds) : Nat}, - +hd: {nu.dead(orig) == False{} : Bool} -) -> nu.N(S.parse.take_choice(ok, name, val, args, up, subs, path, binds, pos, raw, seen), orig): - match ok: - case True{}: - nu.more(S.parse.put(name, val, args, up, subs, path, binds, pos, raw, seen), orig, List.length(&2, S.Bind, binds), - {==}, hb) - case False{}: - nu.apart(S.parse.take_choice(False{}, name, val, args, up, subs, path, binds, pos, raw, seen), orig, - ss => nu.dead(ss), {==}, hd) - -# a positional binds the word, or none is left -def nu.take_pos( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.take_pos(tok, args, up, subs, path, binds, pos, raw), S.St{m0, args, up, subs, path, binds, pos, - raw, seen}): - match pos: - case Nil{}: - nu.apart(S.parse.take_pos(tok, args, up, subs, path, binds, [], raw), S.St{m0, args, up, subs, path, binds, [], - raw, seen}, - ss => nu.dead(ss), {==}, hd) - case Con{ph, +tt}: - match ph: - case S.Arg{+name, +sh, +lo, +kind, +hl, +req, +dd, +cs}: - nu.choice_any(S.allowed(cs, tok), name, tok, args, up, subs, path, binds, - S.parse.take_pos.keep(S.rest.is(S.Arg{name, sh, lo, kind, hl, req, dd, cs}), - S.Arg{name, sh, lo, kind, hl, req, dd, cs}, tt), raw, True{}, - S.St{m0, args, up, subs, path, binds, S.Arg{name, sh, lo, kind, hl, req, dd, cs} <> tt, raw, - seen}, {==}, hd) - -# entering a subcommand adds a level -def nu.enter( - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode -) -> nu.N(S.parse.enter(ss, args, up, path, binds), S.St{m0, args, up, subs, path, binds, pos, raw, seen}): - match ss: - case S.Sub{+nn, +na, +nx, +nc}: - nu.deeper(S.parse.enter(S.Sub{nn, na, nx, nc}, args, up, path, binds), S.St{m0, args, up, subs, path, binds, - pos, raw, seen}, - List.length(&2, S.Level, up), {==}, {==}) - -# a subcommand entered, or a missing positional -def nu.enter_or( - miss: Maybe<&2, String>, - ss: S.Sub, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.enter_or(miss, ss, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, - binds, pos, raw, seen}): - match miss: - case None{}: - nu.enter(ss, args, up, subs, path, binds, pos, raw, seen, m0) - case Some{+nn}: - nu.apart(S.parse.enter_or(Some{nn}, ss, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - -# a word -def nu.word_go( - found: Maybe<&2, S.Sub>, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.word.go(found, tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, - binds, pos, raw, seen}): - match found: - case None{}: - nu.take_pos(tok, args, up, subs, path, binds, pos, raw, seen, m0, hd) - case Some{ss}: - nu.enter_or(S.parse.first_req(pos), ss, args, up, subs, path, binds, pos, raw, seen, m0, hd) - -# a word -def nu.word( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.word(tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, path, binds, pos, - raw, seen}): - nu.word_go(S.find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw, seen, m0, hd) - -# `-` alone, or a cluster -def nu.short_go( - alone: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool}, - +ha: {String.is_empty(String.drop(tok, 1n)) == alone : Bool} -) -> nu.N(S.parse.step.short.go(alone, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, - binds, pos, False{}, seen}): - match alone: - case True{}: - nu.word(tok, args, up, subs, path, binds, pos, False{}, seen, m0, hd) - case False{}: - nu.cl0(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl, ha) - -# a short option or a word -def nu.step_short( - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, - pos, False{}, seen}): - match dash: - case True{}: - nu.short_go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl, - {==}) - case False{}: - nu.word(tok, args, up, subs, path, binds, pos, False{}, seen, m0, hd) - -# a long option, a short one, or a word -def nu.kind_go( - long: Bool, - dash: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.kind.go(long, dash, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, - path, binds, pos, False{}, seen}): - match long: - case True{}: - nu.long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos, False{}, seen, m0, hd, hn, hl) - case False{}: - nu.step_short(dash, tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) - -# a word by its shape -def nu.skind( - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.kind(tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, pos, - False{}, seen}): - nu.kind_go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok, args, up, subs, path, binds, pos, seen, - m0, - hd, hn, hl) - -# `help` starts a help path -def nu.help_w( - hit: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.help(hit, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, - pos, False{}, seen}): - match hit: - case True{}: - nu.apart(S.parse.step.help(True{}, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, - binds, pos, False{}, seen}, ss => nu.help(ss), {==}, hl) - case False{}: - nu.skind(tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) - -# `--` switches to positionals -def nu.end( - end: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.end(end, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, - pos, False{}, seen}): - match end: - case True{}: - nu.apart(S.parse.step.end(True{}, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, - binds, pos, False{}, seen}, ss => nu.raw(ss), {==}, {==}) - case False{}: - nu.help_w(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args, up, subs, path, binds, pos, seen, m0, hd, - hn, - hl) - -# after `--` a word is a positional -def nu.rawf( - raw: Bool, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool}, - +hn: {nu.need_m(m0) == False{} : Bool}, - +hl: {nu.help_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen), S.St{m0, args, up, subs, path, binds, - pos, raw, seen}): - match raw: - case True{}: - nu.take_pos(tok, args, up, subs, path, binds, pos, True{}, seen, m0, hd) - case False{}: - nu.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos, seen, m0, hd, hn, hl) - -# a waiting option binds the word, or refuses it -def nu.need_w( - flag: Bool, - +name: String, - +tok: String, - +args: List<&2, S.Arg>, - +up: List<&2, S.Level>, - +subs: List<&2, S.Sub>, - +path: List<&2, String>, - +binds: List<&2, S.Bind>, - +pos: List<&2, S.Arg>, - +raw: Bool, - +seen: Bool, - +m0: S.Mode, - +hd: {nu.dead_m(m0) == False{} : Bool} -) -> nu.N(S.parse.step.need(flag, name, tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, subs, - path, binds, pos, raw, seen}): - match flag: - case True{}: - nu.apart(S.parse.step.need(True{}, name, tok, args, up, subs, path, binds, pos, raw, seen), S.St{m0, args, up, - subs, path, binds, pos, raw, seen}, ss => nu.dead(ss), {==}, hd) - case False{}: - nu.opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen, m0, hd) - -def Laws.word_used(tok, st, h_live, h_same): - match st: - case S.St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen}: - match mode: - case S.Dead{+ee}: - Walk.wk.false_true(h_live) - case S.Help{}: - Walk.wk.false_true(h_live) - case S.Need{+nn}: - nu.need_w(S.looks_flag(tok), nn, tok, args, up, subs, path, binds, pos, raw, seen, S.Need{nn}, {==}, h_same) - case S.Free{}: - nu.rawf(raw, tok, args, up, subs, path, binds, pos, seen, S.Free{}, {==}, {==}, {==}, h_same) - -def Laws.spec_err_where(err): - match err: - case K.RestNotLast{+path, +name}: - ("rest positional '" ++ name ++ "' is not the last positional", {==}) - case K.SameName{+path, +name}: - ("two arguments are named '" ++ name ++ "'", {==}) - case K.SameShort{+path, +short}: - ("two arguments are spelled '-" ++ short ++ "'", {==}) - case K.SameLong{+path, +long}: - ("two arguments are spelled '--" ++ long ++ "'", {==}) - case K.SameSub{+path, +name}: - ("two subcommands are named '" ++ name ++ "'", {==}) - case K.HelpSub{+path}: - ("a subcommand is named 'help', which `parse` keeps for help", {==}) - case K.DefaultNotChoice{+path, +name, +value}: - ("the default '" ++ value ++ "' of '" ++ name ++ "' is not one of its choices", {==}) - case K.RequiredAfterOptional{+path, +name}: - ("required positional '" ++ name ++ "' follows an optional one", {==}) - -def Laws.env_sets(_nn, _sh, _lo, _kind, _hp, _req, _ev, _dd, _cs, _var): - {==} - -def Laws.env_of_none(_vars): - {==} - -def Laws.env_of_nil(_var): - {==} - -def Laws.env_of_hit(var, nn, vv, rest, h_eq, h_full): - e1 = Equal.sym(Bool, String.eq(nn, var), True{}, h_eq) - %e1 : {S.env.full(S.env.get.at(_, vv, S.env.get(rest, var))) == Some{vv} : Maybe<&2, String>} - e2 = Equal.sym(Bool, String.is_empty(vv), False{}, h_full) - %e2 : {Bool.pick(Maybe<&2, String>, _, None{}, Some{vv}) == Some{vv} : Maybe<&2, String>} - {==} - -def Laws.env_of_empty(var, nn, vv, rest, h_eq, h_empty): - e1 = Equal.sym(Bool, String.eq(nn, var), True{}, h_eq) - %e1 : {S.env.full(S.env.get.at(_, vv, S.env.get(rest, var))) == None{} : Maybe<&2, String>} - e2 = Equal.sym(Bool, String.is_empty(vv), True{}, h_empty) - %e2 : {Bool.pick(Maybe<&2, String>, _, None{}, Some{vv}) == None{} : Maybe<&2, String>} - {==} - -def Laws.env_of_skip(var, nn, vv, rest, h_eq): - e1 = Equal.sym(Bool, String.eq(nn, var), False{}, h_eq) - %e1 : {S.env.full(S.env.get.at(_, vv, S.env.get(rest, var))) == S.env.of(Some{var}, rest) - : Maybe<&2, String>} - {==} - -# that a kind is not a flag -def fb.not_flag(kk: S.ArgKind) -> Type: - {S.env.flag.is(kk) == False{} : Bool} - -# what an env value gives an argument other than a flag is that value -def fb.kind( - kk: S.ArgKind, - +vv: String, - hh: fb.not_flag(kk) -) -> {S.env.kind(kk, vv) == Some{vv} : Maybe<&2, String>}: - match kk: - case S.Flag{}: - Empty.absurd({S.env.kind(S.Flag{}, vv) == Some{vv} : Maybe<&2, String>}, Walk.wk.true_false(hh)) - case S.Opt{}: - {==} - case S.Many{}: - {==} - case S.Pos{}: - {==} - case S.Rest{}: - {==} - -def Laws.fallback_env(nn, sh, lo, kind, hp, req, ev, dd, cs, vars, vv, h_kind, h_env): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) - %e1 : {S.Arg{nn, sh, lo, kind, hp, req, S.resolve.with(_, kind, ev, dd), cs} - == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{vv}}, cs} : S.Arg} - e2 = Equal.sym(Maybe<&2, String>, S.env.kind(kind, vv), Some{vv}, fb.kind(kind, vv, h_kind)) - %e2 : {S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, _}, cs} - == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, Some{vv}}, cs} : S.Arg} - {==} - -def Laws.fallback_flag_on(nn, sh, lo, hp, req, ev, dd, cs, vars, vv, h_env, h_on): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) - %e1 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.resolve.with(_, S.Flag{}, ev, dd), cs} - == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Some{"true"}}, cs} : S.Arg} - e2 = Equal.sym(Bool, S.allowed.ok(Laws.off_words(), String.to_lower(vv)), False{}, h_on) - %e2 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Bool.pick(Maybe<&2, String>, _, None{}, Some{"true"})}, cs} - == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Some{"true"}}, cs} : S.Arg} - {==} - -def Laws.fallback_flag_off(nn, sh, lo, hp, req, ev, dd, cs, vars, vv, h_env, h_off): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) - %e1 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.resolve.with(_, S.Flag{}, ev, dd), cs} - == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, None{}}, cs} : S.Arg} - e2 = Equal.sym(Bool, S.allowed.ok(Laws.off_words(), String.to_lower(vv)), True{}, h_off) - %e2 : {S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, Bool.pick(Maybe<&2, String>, _, None{}, Some{"true"})}, cs} - == S.Arg{nn, sh, lo, S.Flag{}, hp, req, S.Fallback{ev, None{}}, cs} : S.Arg} - {==} - -def Laws.fallback_default(nn, sh, lo, kind, hp, req, ev, dd, cs, vars, h_env): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), None{}, h_env) - %e1 : {S.Arg{nn, sh, lo, kind, hp, req, S.resolve.with(_, kind, ev, dd), cs} - == S.Arg{nn, sh, lo, kind, hp, req, S.Fallback{ev, dd}, cs} : S.Arg} - {==} - -def Laws.resolve_spec(_nn, _ab, _ver, _args, _subs, _vars): - {==} - -def Laws.resolve_args_con(_aa, _tt, _vars): - {==} - -def Laws.resolve_subs_con(_nn, _ab, _args, _kids, _tt, _vars): - {==} - -def Laws.env_parse(_spec, _ws, _vars): - {==} - -# an argument resolved under no pairs is unchanged -def rs.arg_nil(aa: S.Arg) -> {S.resolve.arg(aa, []) == aa : S.Arg}: - match aa: - case S.Arg{+n, +s, +l, +kk, +h, +r, fb, +cs}: - match fb: - case S.Fallback{ev, +dd}: - match ev: - case None{}: - {==} - case Some{+var}: - {==} - -# arguments resolved under no pairs are unchanged -def rs.args_nil(args: List<&2, S.Arg>) -> {S.resolve.args(args, []) == args : List<&2, S.Arg>}: - match args: - case Nil{}: - {==} - case Con{+hh, +tt}: - e1 = Equal.sym(S.Arg, S.resolve.arg(hh, []), hh, rs.arg_nil(hh)) - %e1 : {_ <> S.resolve.args(tt, []) == hh <> tt : List<&2, S.Arg>} - e2 = Equal.sym(List<&2, S.Arg>, S.resolve.args(tt, []), tt, rs.args_nil(tt)) - %e2 : {hh <> _ == hh <> tt : List<&2, S.Arg>} - {==} - -# subcommands resolved under no pairs are unchanged -def rs.subs_nil(subs: List<&2, S.Sub>) -> {S.resolve.subs(subs, []) == subs : List<&2, S.Sub>}: - match subs: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Sub{+n, +a, +args, +kids}: - e1 = Equal.sym(List<&2, S.Arg>, S.resolve.args(args, []), args, rs.args_nil(args)) - %e1 : {S.Sub{n, a, _, S.resolve.subs(kids, [])} <> S.resolve.subs(tt, []) - == S.Sub{n, a, args, kids} <> tt : List<&2, S.Sub>} - e2 = Equal.sym(List<&2, S.Sub>, S.resolve.subs(kids, []), kids, rs.subs_nil(kids)) - %e2 : {S.Sub{n, a, args, _} <> S.resolve.subs(tt, []) == S.Sub{n, a, args, kids} <> tt : List<&2, S.Sub>} - e3 = Equal.sym(List<&2, S.Sub>, S.resolve.subs(tt, []), tt, rs.subs_nil(tt)) - %e3 : {S.Sub{n, a, args, kids} <> _ == S.Sub{n, a, args, kids} <> tt : List<&2, S.Sub>} - {==} - -# a spec resolved under no pairs is unchanged -def rs.spec_nil(spec: S.Cli) -> {S.resolve(spec, []) == spec : S.Cli}: - match spec: - case S.Cli{+n, +a, +v, +args, +subs}: - e1 = Equal.sym(List<&2, S.Arg>, S.resolve.args(args, []), args, rs.args_nil(args)) - %e1 : {S.Cli{n, a, v, _, S.resolve.subs(subs, [])} == S.Cli{n, a, v, args, subs} : S.Cli} - e2 = Equal.sym(List<&2, S.Sub>, S.resolve.subs(subs, []), subs, rs.subs_nil(subs)) - %e2 : {S.Cli{n, a, v, args, _} == S.Cli{n, a, v, args, subs} : S.Cli} - {==} - -def Laws.bad_args_nil(args, bs): - match args: - case Nil{}: - {==} - case Con{hh, tt}: - match hh: - case S.Arg{_n, _s, _l, _k, _h, _r, fb, _c}: - match fb: - case S.Fallback{ev, _d}: - match ev: - case None{}: - Laws.bad_args_nil(tt, bs) - case Some{_var}: - Laws.bad_args_nil(tt, bs) - -# no command on the path has a bad argument under no pairs -def ck.go_nil( - mm: S.Matched, - +args: List<&2, S.Arg>, - +subs: List<&2, S.Sub> -) -> {S.env.bad.go(mm, args, subs, []) == None{} : Maybe<&2, S.Var>}: - match mm: - case S.Leaf{+bs}: - Laws.bad_args_nil(args, bs) - case S.Node{+bs, +name, next}: - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.args(args, [], bs), None{}, Laws.bad_args_nil(args, bs)) - %e1 : {S.env.first(_, - S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), [])) - == None{} : Maybe<&2, S.Var>} - ck.go_nil(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name))) - -# the check under no pairs leaves every result as it is -def ck.nil( - spec: S.Cli, - got: Result<&2, &2, S.ParseErr, S.Matched> -) -> {S.env.check(spec, [], got) == got : Result<&2, &2, S.ParseErr, S.Matched>}: - match spec: - case S.Cli{_n, _a, _v, +args, +subs}: - match got: - case Fail{_e}: - {==} - case Done{+mm}: - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.go(mm, args, subs, []), None{}, ck.go_nil(mm, args, subs)) - %e1 : {S.env.check.done(_, mm) == Done{mm} : Result<&2, &2, S.ParseErr, S.Matched>} - {==} - -def Laws.env_none(spec, ws): - e1 = Equal.sym(S.Cli, S.resolve(spec, []), spec, rs.spec_nil(spec)) - %e1 : {S.env.check(spec, [], S.parse(_, ws)) == S.parse(spec, ws) : Result<&2, &2, S.ParseErr, S.Matched>} - ck.nil(spec, S.parse(spec, ws)) - -def Laws.env_check_fail(_spec, _vars, _ee): - {==} - -def Laws.env_check_good(_nn, _ab, _ver, args, subs, vars, mm, h_none): - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.go(mm, args, subs, vars), None{}, h_none) - %e1 : {S.env.check.done(_, mm) == Done{mm} : Result<&2, &2, S.ParseErr, S.Matched>} - {==} - -def Laws.env_check_bad(_nn, _ab, _ver, args, subs, vars, mm, name, vv, h_bad): - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.go(mm, args, subs, vars), Some{(name, vv)}, h_bad) - %e1 : {S.env.check.done(_, mm) == Fail{S.BadValue{S.path_of(mm), name, vv}} - : Result<&2, &2, S.ParseErr, S.Matched>} - {==} - -def Laws.bad_go_leaf(_bs, _args, _subs, _vars): - {==} - -def Laws.bad_go_root(bs, name, next, args, subs, vars, pp, h_here): - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.args(args, vars, bs), Some{pp}, h_here) - %e1 : {S.env.first(_, - S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars)) - == Some{pp} : Maybe<&2, S.Var>} - {==} - -def Laws.bad_go_down(bs, name, next, args, subs, vars, h_none): - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.args(args, vars, bs), None{}, h_none) - %e1 : {S.env.first(_, - S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars)) - == S.env.bad.go(next, S.env.sub.args(S.find_sub(subs, name)), S.env.sub.kids(S.find_sub(subs, name)), vars) - : Maybe<&2, S.Var>} - {==} - -def Laws.bad_args_here(aa, tt, vars, bs, pp, h_here): - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.arg(aa, vars, bs), Some{pp}, h_here) - %e1 : {S.env.first(_, S.env.bad.args(tt, vars, bs)) == Some{pp} : Maybe<&2, S.Var>} - {==} - -def Laws.bad_args_next(aa, tt, vars, bs, h_none): - e1 = Equal.sym(Maybe<&2, S.Var>, S.env.bad.arg(aa, vars, bs), None{}, h_none) - %e1 : {S.env.first(_, S.env.bad.args(tt, vars, bs)) == S.env.bad.args(tt, vars, bs) : Maybe<&2, S.Var>} - {==} - -# an `and` with a false right side is false -def ba.and_false(bb: Bool) -> {Bool.and(bb, False{}) == False{} : Bool}: - match bb: - case True{}: - {==} - case False{}: - {==} - -# a flag is never reported, whatever its env value -def ba.flag( - found: Maybe<&2, String>, - +nn: String, - +cs: List<&2, String>, - +bs: List<&2, S.Bind> -) -> {S.env.bad.val(found, S.Flag{}, nn, cs, bs) == None{} : Maybe<&2, S.Var>}: - match found: - case None{}: - {==} - case Some{_vv}: - {==} - -def Laws.bad_arg_some(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, vv, h_env, h_kind, h_out, h_bound): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) - %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == Some{(nn, vv)} : Maybe<&2, S.Var>} - e2 = Equal.sym(Bool, S.env.flag.is(kind), False{}, h_kind) - %e2 : {Bool.pick(Maybe<&2, S.Var>, - Bool.and(Bool.not(_), Bool.and(Bool.not(S.allowed(cs, vv)), - List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv))), - Some{(nn, vv)}, None{}) == Some{(nn, vv)} : Maybe<&2, S.Var>} - e3 = Equal.sym(Bool, S.allowed(cs, vv), False{}, h_out) - %e3 : {Bool.pick(Maybe<&2, S.Var>, - Bool.and(Bool.not(_), List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv)), - Some{(nn, vv)}, None{}) == Some{(nn, vv)} : Maybe<&2, S.Var>} - e4 = Equal.sym(Bool, List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv), True{}, h_bound) - %e4 : {Bool.pick(Maybe<&2, S.Var>, _, Some{(nn, vv)}, None{}) == Some{(nn, vv)} : Maybe<&2, S.Var>} - {==} - -def Laws.bad_arg_unset(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, h_env): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), None{}, h_env) - %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == None{} : Maybe<&2, S.Var>} - {==} - -def Laws.bad_arg_flag(nn, _sh, _lo, _hp, _req, ev, _dd, cs, vars, bs): - ba.flag(S.env.of(ev, vars), nn, cs, bs) - -def Laws.bad_arg_allowed(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, vv, h_env, h_in): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) - %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == None{} : Maybe<&2, S.Var>} - e2 = Equal.sym(Bool, S.allowed(cs, vv), True{}, h_in) - %e2 : {Bool.pick(Maybe<&2, S.Var>, - Bool.and(Bool.not(S.env.flag.is(kind)), Bool.and(Bool.not(_), - List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv))), - Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} - e3 = Equal.sym(Bool, Bool.and(Bool.not(S.env.flag.is(kind)), False{}), False{}, - ba.and_false(Bool.not(S.env.flag.is(kind)))) - %e3 : {Bool.pick(Maybe<&2, S.Var>, _, Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} - {==} - -def Laws.bad_arg_unbound(nn, _sh, _lo, kind, _hp, _req, ev, _dd, cs, vars, bs, vv, h_env, h_free): - e1 = Equal.sym(Maybe<&2, String>, S.env.of(ev, vars), Some{vv}, h_env) - %e1 : {S.env.bad.val(_, kind, nn, cs, bs) == None{} : Maybe<&2, S.Var>} - e2 = Equal.sym(Bool, List.contains(~String, ~String.eq, S.get_all.bind(bs, nn), vv), False{}, h_free) - %e2 : {Bool.pick(Maybe<&2, S.Var>, - Bool.and(Bool.not(S.env.flag.is(kind)), Bool.and(Bool.not(S.allowed(cs, vv)), _)), - Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} - e3 = Equal.sym(Bool, Bool.and(Bool.not(S.allowed(cs, vv)), False{}), False{}, - ba.and_false(Bool.not(S.allowed(cs, vv)))) - %e3 : {Bool.pick(Maybe<&2, S.Var>, - Bool.and(Bool.not(S.env.flag.is(kind)), _), - Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} - e4 = Equal.sym(Bool, Bool.and(Bool.not(S.env.flag.is(kind)), False{}), False{}, - ba.and_false(Bool.not(S.env.flag.is(kind)))) - %e4 : {Bool.pick(Maybe<&2, S.Var>, _, Some{(nn, vv)}, None{}) == None{} : Maybe<&2, S.Var>} - {==} - -def Laws.help_env_shown(_nn, _sh, _lo, _kind, _hp, _req, _var, _dd, _cs): - {==} - -def Laws.help_env_none(_nn, _sh, _lo, _kind, _hp, _req, _dd, _cs): - {==} - -def Laws.env_names_root(_nn, _ab, _ver, _args, _subs): - {==} - -def Laws.env_names_forest(_nn, _ab, _args, _kids, _tt): - {==} - -def Laws.env_names_some(_nn, _sh, _lo, _kind, _hp, _req, _var, _dd, _cs, _tt): - {==} - -def Laws.env_names_none(_nn, _sh, _lo, _kind, _hp, _req, _dd, _cs, _tt): - {==} - -def Laws.kept_set(_nn, _vv, _rest): - {==} - -def Laws.kept_unset(_nn, _ee, _rest): - {==} - -def Laws.sug_other(spec, err, h_err): - match err: - case S.UnknownFlag{at, word}: - Empty.absurd({S.suggestion(spec, S.UnknownFlag{at, word}) == None{} : Maybe<&2, String>}, - Walk.wk.true_false(h_err)) - case S.Missing{_at, _name}: - {==} - case S.NoValue{_at, _name}: - {==} - case S.BadValue{_at, _name, _value}: - {==} - case S.NeedHelp{_path}: - {==} - case S.Unexpected{_at, _arg}: - {==} - case S.Repeated{_at, _name}: - {==} - case S.NeedVersion{}: - {==} - -def Laws.sug_short(spec, at, word, h_word): - e1 = Equal.sym(Bool, String.starts_with(word, "--"), False{}, h_word) - %e1 : {S.sug.word.go(_, spec, at, word) == None{} : Maybe<&2, String>} - {==} - -# the arguments reached down a path are those of the page `help` reaches -def sa.go( - path: List<&2, String>, - how: S.How -) -> {S.sug.args.go(path, how) == Laws.how_args(Laws.reach(path, how)) : List<&2, S.Arg>}: - match path: - case Nil{}: - match how: - case S.How{_b, _a, _v, _g, _s, _u}: - {==} - case Con{+hh, tt}: - sa.go(tt, S.help.step(how, hh)) - -# the arguments of the command at a path are those of its help page -def sa.reach( - spec: S.Cli, - path: List<&2, String> -) -> {S.sug.args(spec, path) == Laws.how_args(Laws.reach(path, Laws.root(spec))) : List<&2, S.Arg>}: - match spec: - case S.Cli{+n, a, v, args, subs}: - sa.go(path, S.How{n, a, v, args, subs, n}) - -def Laws.sug_long(spec, at, body, name, val, h_cut): - e1 = Equal.sym(Bool, String.starts_with(body, ""), True{}, Laws.prefix_empty(body)) - %e1 : {S.sug.word.go(_, spec, at, "--" ++ body) - == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(Laws.reach(at, Laws.root(spec)))), name)) - : Maybe<&2, String>} - e2 = Equal.sym(String, String.drop(body, 0n), body, Laws.drop_zero(body)) - %e2 : {S.sug.found(S.sug.best(S.sug.cands(S.sug.args(spec, at)), S.sug.typed(S.cut_eq(_)))) - == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(Laws.reach(at, Laws.root(spec)))), name)) - : Maybe<&2, String>} - e3 = Equal.sym(String & Maybe<&2, String>, S.cut_eq(body), (name, val), h_cut) - %e3 : {S.sug.found(S.sug.best(S.sug.cands(S.sug.args(spec, at)), S.sug.typed(_))) - == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(Laws.reach(at, Laws.root(spec)))), name)) - : Maybe<&2, String>} - e4 = Equal.sym(List<&2, S.Arg>, S.sug.args(spec, at), Laws.how_args(Laws.reach(at, Laws.root(spec))), - sa.reach(spec, at)) - %e4 : {S.sug.found(S.sug.best(S.sug.cands(_), name)) - == S.sug.found(S.sug.best(S.sug.cands(Laws.how_args(Laws.reach(at, Laws.root(spec)))), name)) - : Maybe<&2, String>} - {==} - -def Laws.sug_found_some(_cc): - {==} - -def Laws.cands_some(_nn, _sh, _ll, _kind, _hp, _req, _fb, _cs, _tt): - {==} - -def Laws.cands_none(_nn, _sh, _kind, _hp, _req, _fb, _cs, _tt): - {==} - -def Laws.sug_close(_typed, _cc): - {==} - -def Laws.best_nil(_typed): - {==} - -def Laws.best_far(cc, tt, typed, h_far): - e1 = Equal.sym(Bool, S.sug.close(typed, cc), False{}, h_far) - %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == S.sug.best(tt, typed) : Maybe<&2, String>} - {==} - -def Laws.best_only(cc, tt, typed, h_close, h_rest): - e1 = Equal.sym(Bool, S.sug.close(typed, cc), True{}, h_close) - %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == Some{cc} : Maybe<&2, String>} - e2 = Equal.sym(Maybe<&2, String>, S.sug.best(tt, typed), None{}, h_rest) - %e2 : {S.sug.keep(True{}, typed, cc, _) == Some{cc} : Maybe<&2, String>} - {==} - -def Laws.best_here(cc, tt, typed, ww, h_close, h_rest, h_le): - e1 = Equal.sym(Bool, S.sug.close(typed, cc), True{}, h_close) - %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == Some{cc} : Maybe<&2, String>} - e2 = Equal.sym(Maybe<&2, String>, S.sug.best(tt, typed), Some{ww}, h_rest) - %e2 : {S.sug.keep(True{}, typed, cc, _) == Some{cc} : Maybe<&2, String>} - e3 = Equal.sym(Bool, Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)), True{}, h_le) - %e3 : {Bool.pick(Maybe<&2, String>, _, Some{cc}, Some{ww}) == Some{cc} : Maybe<&2, String>} - {==} - -def Laws.best_there(cc, tt, typed, ww, h_close, h_rest, h_le): - e1 = Equal.sym(Bool, S.sug.close(typed, cc), True{}, h_close) - %e1 : {S.sug.keep(_, typed, cc, S.sug.best(tt, typed)) == Some{ww} : Maybe<&2, String>} - e2 = Equal.sym(Maybe<&2, String>, S.sug.best(tt, typed), Some{ww}, h_rest) - %e2 : {S.sug.keep(True{}, typed, cc, _) == Some{ww} : Maybe<&2, String>} - e3 = Equal.sym(Bool, Nat.is_le(S.osa(typed, cc), S.osa(typed, ww)), False{}, h_le) - %e3 : {Bool.pick(Maybe<&2, String>, _, Some{cc}, Some{ww}) == Some{ww} : Maybe<&2, String>} - {==} - -# that `qq` contains `ss` -def ct.has(qq: String, ss: String) -> Type: - {String.contains(qq, ss) == True{} : Bool} - -# appending is associative -def ct.assoc(aa: String, +bb: String, +cc: String) -> {(aa ++ bb) ++ cc == aa ++ (bb ++ cc) : String}: - match aa: - case SNil{}: - {==} - case SCon{+hh, tt}: - Equal.cong(String, String, ys => SCon{hh, ys}, (tt ++ bb) ++ cc, tt ++ (bb ++ cc), ct.assoc(tt, bb, cc)) - -# a string starts with itself, whatever follows -def ct.starts(ss: String, +post: String) -> {String.starts_with(ss ++ post, ss) == True{} : Bool}: - match ss: - case SNil{}: - Laws.prefix_empty(post) - case SCon{+hh, tt}: - e1 = Equal.sym(Bool, Char.is_eq(hh, hh), True{}, Eq.char_eq_self(hh)) - %e1 : {String.starts_with.go.if(tt, tt ++ post, _) == True{} : Bool} - ct.starts(tt, post) - -# every string contains the empty one -def ct.nil(post: String) -> ct.has(post, ""): - match post: - case SNil{}: - {==} - case SCon{_h, _t}: - {==} - -# a string contains itself, whatever follows -def ct.front(ss: String, +post: String) -> ct.has(ss ++ post, ss): - match ss: - case SNil{}: - ct.nil(post) - case SCon{+hh, +tt}: - e1 = Equal.sym(Bool, String.starts_with(SCon{hh, tt ++ post}, SCon{hh, tt}), True{}, - ct.starts(SCon{hh, tt}, post)) - %e1 : {String.contains.if(tt ++ post, SCon{hh, tt}, _) == True{} : Bool} - {==} - -# a contained string stays contained, here or further on -def ct.if( - tt: String, - +ss: String, - here: Bool, - hh: ct.has(tt, ss) -) -> {String.contains.if(tt, ss, here) == True{} : Bool}: - match here: - case False{}: - hh - case True{}: - {==} - -# what a string contains, it contains after anything -def ct.after(pre: String, +qq: String, +ss: String, hh: ct.has(qq, ss)) -> ct.has(pre ++ qq, ss): - match pre: - case SNil{}: - hh - case SCon{+ch, +tt}: - ct.if(tt ++ qq, ss, String.starts_with(SCon{ch, tt ++ qq}, ss), ct.after(tt, qq, ss, hh)) - -# a string contained in the part after `pp` is contained in the whole -def ct.step( - +pp: String, - +qq: String, - +rr: String, - +ss: String, - hh: ct.has(qq ++ rr, ss) -) -> ct.has((pp ++ qq) ++ rr, ss): - e1 = Equal.sym(String, (pp ++ qq) ++ rr, pp ++ (qq ++ rr), ct.assoc(pp, qq, rr)) - %e1 : {String.contains(_, ss) == True{} : Bool} - ct.after(pp, qq ++ rr, ss, hh) - -# a string is contained where it leads -def ct.lead(+ss: String, +qq: String, +rr: String) -> ct.has((ss ++ qq) ++ rr, ss): - e1 = Equal.sym(String, (ss ++ qq) ++ rr, ss ++ (qq ++ rr), ct.assoc(ss, qq, rr)) - %e1 : {String.contains(_, ss) == True{} : Bool} - ct.front(ss, qq ++ rr) - -# the type that is empty exactly where a Maybe is Some -def mb.emp(mm: Maybe<&2, String>) -> Type: - match mm: - case None{}: - Unit - case Some{_s}: - Empty - -# that None is Some -def mb.none_some(ss: String) -> Type: - {None{} == Some{ss} : Maybe<&2, String>} - -# None is not Some -def mb.clash(+ss: String, eq: mb.none_some(ss)) -> Empty: - %eq : mb.emp(_) - Unit{} - -# an error with no suggestion proves anything about its text -def tp.none( - +spec: S.Cli, - +err: S.ParseErr, - +ss: String, - eq: mb.none_some(ss) -) -> ct.has(S.err_text(spec, err), ss): - Empty.absurd(ct.has(S.err_text(spec, err), ss), mb.clash(ss, eq)) - -def Laws.err_text_tip(spec, err, ss, h_sug): - match err: - case S.UnknownFlag{+at, +word}: - e1 = Equal.sym(Maybe<&2, String>, S.sug.word(spec, at, word), Some{ss}, h_sug) - %e1 : {String.contains(S.err.wrap(spec, at, "error: unexpected argument '" ++ word ++ "' found" ++ S.sug.tip(_)), - ss) == True{} : Bool} - ct.step("error: unexpected argument '", word ++ "' found" ++ "\n\n tip: a similar argument exists: '" ++ ss ++ "'", - "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, - ct.step(word, "' found" ++ "\n\n tip: a similar argument exists: '" ++ ss ++ "'", - "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, - ct.step("' found", "\n\n tip: a similar argument exists: '" ++ ss ++ "'", - "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, - ct.step("\n\n tip: a similar argument exists: '", ss ++ "'", - "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at), ss, - ct.lead(ss, "'", "\n\n" ++ S.usage.at(spec, at) ++ "\n\n" ++ S.err.hint(at)))))) - case S.Missing{+at, +name}: - tp.none(spec, S.Missing{at, name}, ss, h_sug) - case S.NoValue{+at, +name}: - tp.none(spec, S.NoValue{at, name}, ss, h_sug) - case S.BadValue{+at, +name, +value}: - tp.none(spec, S.BadValue{at, name, value}, ss, h_sug) - case S.NeedHelp{+path}: - tp.none(spec, S.NeedHelp{path}, ss, h_sug) - case S.Unexpected{+at, +arg}: - tp.none(spec, S.Unexpected{at, arg}, ss, h_sug) - case S.Repeated{+at, +name}: - tp.none(spec, S.Repeated{at, name}, ss, h_sug) - case S.NeedVersion{}: - tp.none(spec, S.NeedVersion{}, ss, h_sug) - -# a positional's Arguments line, then the lines after it -def ha.next( - +aa: S.Arg, - +tt: List<&2, S.Arg>, - +ww: Nat, - ee: {S.help.arg_lines(tt, ww) == Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, tt), ww) : String} -) -> {S.help.arg_line(aa, ww) ++ "\n" ++ S.help.arg_lines(tt, ww) - == S.help.arg_line(aa, ww) ++ "\n" ++ Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, tt), ww) : String}: - Equal.cong(String, String, xs => S.help.arg_line(aa, ww) ++ "\n" ++ xs, S.help.arg_lines(tt, ww), - Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, tt), ww), ee) - -# the Arguments lines are the lines of the positionals, in order -def ha.lines( - +args: List<&2, S.Arg>, - +ww: Nat -) -> {S.help.arg_lines(args, ww) == Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, args), ww) : String}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+n, +s, +l, kind, +h, +r, +d, +c}: - match kind: - case S.Flag{}: - ha.lines(tt, ww) - case S.Opt{}: - ha.lines(tt, ww) - case S.Many{}: - ha.lines(tt, ww) - case S.Pos{}: - ha.next(S.Arg{n, s, l, S.Pos{}, h, r, d, c}, tt, ww, ha.lines(tt, ww)) - case S.Rest{}: - ha.next(S.Arg{n, s, l, S.Rest{}, h, r, d, c}, tt, ww, ha.lines(tt, ww)) - -def Laws.args_listed(args): - Equal.cong(String, String, xs => S.help.args.go(xs), S.help.arg_lines(args, S.help.arg_width(S.pos_of(args), 0n)), - Laws.arg_lines(List.filter(~S.Arg, ~S.pos.is, args), S.help.arg_width(S.pos_of(args), 0n)), - ha.lines(args, S.help.arg_width(S.pos_of(args), 0n))) - -# a usage mark's brackets, as the law's mark writes them -def ha.wrap(req: Bool, +nn: String) -> {S.help.pos.wrap(req, nn) == Laws.mark.req(req, nn) : String}: - match req: - case True{}: - {==} - case False{}: - {==} - -# a usage mark's dots, as the law's mark writes them -def ha.dots(kind: S.ArgKind) -> {S.help.pos.dots(S.rest.is.kind(kind)) == Laws.mark.rest(kind) : String}: - match kind: - case S.Flag{}: - {==} - case S.Opt{}: - {==} - case S.Pos{}: - {==} - case S.Rest{}: - {==} - case S.Many{}: - {==} - -# a positional's usage hint is the law's mark -def ha.mark( - +nn: String, - +sh: Maybe<&2, String>, - +lo: Maybe<&2, String>, - +kind: S.ArgKind, - +hp: String, - +req: Bool, - +fb: S.Fallback, - +cs: List<&2, String> -) -> {S.help.pos.one(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) == Laws.mark(S.Arg{nn, sh, lo, kind, hp, req, fb, cs}) - : String}: - +up = String.to_upper(nn) - Equal.trans(String, S.help.pos.wrap(req, up) ++ S.help.pos.dots(S.rest.is.kind(kind)), - Laws.mark.req(req, up) ++ S.help.pos.dots(S.rest.is.kind(kind)), Laws.mark.req(req, up) ++ Laws.mark.rest(kind), - Equal.cong(String, String, xs => xs ++ S.help.pos.dots(S.rest.is.kind(kind)), S.help.pos.wrap(req, up), - Laws.mark.req(req, up), ha.wrap(req, up)), - Equal.cong(String, String, xs => Laws.mark.req(req, up) ++ xs, S.help.pos.dots(S.rest.is.kind(kind)), - Laws.mark.rest(kind), ha.dots(kind))) - -def Laws.arg_line_mark(nn, sh, lo, kind, hp, req, fb, cs, ww): - +aa = {S.Arg{nn, sh, lo, kind, hp, req, fb, cs} : S.Arg} - Equal.cong(String, String, mm => " " ++ S.help.pad(String.drop(mm, 1n), ww) ++ " " ++ hp ++ S.help.extra(aa), - S.help.pos.one(aa), Laws.mark(aa), ha.mark(nn, sh, lo, kind, hp, req, fb, cs)) - -# the find kept here is the later of the one above and this one -def ef.here( - found: Maybe<&2, S.Arg>, - +best: Maybe<&2, S.Arg> -) -> {S.err.find.here(found, best) == Laws.keep_later(best, found) : Maybe<&2, S.Arg>}: - match found: - case None{}: - {==} - case Some{_a}: - {==} - -# keeping the later find is associative -def ef.assoc( - +found: Maybe<&2, S.Arg>, - +best: Maybe<&2, S.Arg>, - later: Maybe<&2, S.Arg> -) -> {Laws.keep_later(S.err.find.here(found, best), later) - == Laws.keep_later(best, Laws.keep_later(found, later)) : Maybe<&2, S.Arg>}: - match later: - case None{}: - ef.here(found, best) - case Some{_a}: - {==} - -# the search down a path keeps the deepest page's find, else `best` -def ef.go( - path: List<&2, String>, - how: S.How, - +name: String, - +best: Maybe<&2, S.Arg> -) -> {S.err.find.go(path, how, name, best) == Laws.keep_later(best, Laws.deepest(Laws.pages(path, how), name)) - : Maybe<&2, S.Arg>}: - match path: - case Nil{}: - match how: - case S.How{_b, _a, _v, +args, _s, _u}: - ef.here(S.by_name(args, name), best) - case Con{+hh, +tt}: - match how: - case S.How{+bin, +about, +version, +args, +subs, +usage}: - +hw = {S.How{bin, about, version, args, subs, usage} : S.How} - +here = S.err.find.here(S.by_name(args, name), best) - +deeper = Laws.deepest(Laws.pages(tt, S.help.step(hw, hh)), name) - Equal.trans(Maybe<&2, S.Arg>, S.err.find.go(tt, S.help.step(hw, hh), name, here), - Laws.keep_later(here, deeper), Laws.keep_later(best, Laws.keep_later(S.by_name(args, name), deeper)), - ef.go(tt, S.help.step(hw, hh), name, here), - ef.assoc(S.by_name(args, name), best, deeper)) - -# keeping the later of nothing and a find is the find -def ef.none(found: Maybe<&2, S.Arg>) -> {Laws.keep_later(None{}, found) == found : Maybe<&2, S.Arg>}: - match found: - case None{}: - {==} - case Some{_a}: - {==} - -def Laws.err_find(spec, at, name): - match spec: - case S.Cli{+bin, +about, +version, +args, +subs}: - +hw = {S.How{bin, about, version, args, subs, bin} : S.How} - Equal.trans(Maybe<&2, S.Arg>, S.err.find.go(at, hw, name, None{}), - Laws.keep_later(None{}, Laws.deepest(Laws.pages(at, hw), name)), Laws.deepest(Laws.pages(at, hw), name), - ef.go(at, hw, name, None{}), ef.none(Laws.deepest(Laws.pages(at, hw), name))) - -# a string with nothing after it, under a char -def ef.nil_under(+cc: Char, xs: String) -> {SCon{cc, xs ++ ""} == SCon{cc, xs} : String}: - Equal.cong(String, String, ys => SCon{cc, ys}, xs ++ "", xs, hs.nil(xs)) - -def Laws.err_form(nn, _sh, _lo, kind, _hp, req, _fb, _cs): - match kind: - case S.Flag{}: - {==} - case S.Opt{}: - {==} - case S.Many{}: - {==} - case S.Pos{}: - match req: - case True{}: - ef.nil_under('<', String.to_upper(nn) ++ ">") - case False{}: - ef.nil_under('[', String.to_upper(nn) ++ "]") - case S.Rest{}: - match req: - case True{}: - {==} - case False{}: - {==} - -# a found argument is named as the law names it -def en.named(found: Maybe<&2, S.Arg>, +name: String) -> {S.err.named(found, name) == Laws.named(found, name) : String}: - match found: - case None{}: - {==} - case Some{aa}: - match aa: - case S.Arg{+nn, +sh, +lo, +kind, +hp, +req, +fb, +cs}: - Laws.err_form(nn, sh, lo, kind, hp, req, fb, cs) - -# an error's argument, named as the law names it -def en.name( - +spec: Shake.Cli, - +at: List<&2, String>, - +name: String -) -> {S.err.name(spec, at, name) == Laws.named(Laws.deepest(Laws.pages(at, Laws.root(spec)), name), name) : String}: - +dd = Laws.deepest(Laws.pages(at, Laws.root(spec)), name) - Equal.trans(String, S.err.named(S.err.find(spec, at, name), name), S.err.named(dd, name), Laws.named(dd, name), - Equal.cong(Maybe<&2, S.Arg>, String, ff => S.err.named(ff, name), S.err.find(spec, at, name), dd, - Laws.err_find(spec, at, name)), - en.named(dd, name)) - -# a found argument's choices line is the law's -def en.values(found: Maybe<&2, S.Arg>) -> {S.err.values(found) == Laws.values_line(found) : String}: - match found: - case None{}: - {==} - case Some{aa}: - match aa: - case S.Arg{_n, _s, _l, _k, _h, _r, _d, cs}: - match cs: - case Nil{}: - {==} - case Con{_c, _t}: - {==} - -def Laws.err_missing_named(spec, at, name): - Equal.cong(String, String, xx => S.err.wrap(spec, at, "error: the following required argument was not provided:\n " ++ xx), - S.err.name(spec, at, name), Laws.named(Laws.deepest(Laws.pages(at, Laws.root(spec)), name), name), - en.name(spec, at, name)) - -def Laws.err_novalue_named(spec, at, name): - Equal.cong(String, String, xx => S.err.wrap(spec, at, "error: a value is required for '" ++ xx ++ "' but none was supplied"), - S.err.name(spec, at, name), Laws.named(Laws.deepest(Laws.pages(at, Laws.root(spec)), name), name), - en.name(spec, at, name)) - -def Laws.err_repeated_named(spec, at, name): - Equal.cong(String, String, xx => S.err.wrap(spec, at, "error: the argument '" ++ xx ++ "' cannot be used multiple times"), - S.err.name(spec, at, name), Laws.named(Laws.deepest(Laws.pages(at, Laws.root(spec)), name), name), - en.name(spec, at, name)) - -def Laws.err_bad_named(spec, at, name, value): - +dd = Laws.deepest(Laws.pages(at, Laws.root(spec)), name) - +ff = S.err.find(spec, at, name) - Equal.trans(String, - S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(ff, name) ++ "'" ++ S.err.values(ff)), - S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(dd, name) ++ "'" ++ S.err.values(dd)), - S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ Laws.named(dd, name) ++ "'" ++ Laws.values_line(dd)), - Equal.cong(Maybe<&2, S.Arg>, String, - gg => S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(gg, name) ++ "'" ++ - S.err.values(gg)), ff, dd, Laws.err_find(spec, at, name)), - Equal.trans(String, - S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ S.err.named(dd, name) ++ "'" ++ S.err.values(dd)), - S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ Laws.named(dd, name) ++ "'" ++ S.err.values(dd)), - S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ Laws.named(dd, name) ++ "'" ++ Laws.values_line(dd)), - Equal.cong(String, String, xx => S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ xx ++ "'" ++ - S.err.values(dd)), S.err.named(dd, name), Laws.named(dd, name), en.named(dd, name)), - Equal.cong(String, String, xx => S.err.wrap(spec, at, "error: invalid value '" ++ value ++ "' for '" ++ - Laws.named(dd, name) ++ "'" ++ xx), S.err.values(dd), Laws.values_line(dd), en.values(dd)))) - -# the help row is the law's -def bi.help( - long: Maybe<&2, S.Arg>, - short: Maybe<&2, S.Arg> -) -> {S.help.help_row(long, short) == Laws.help_rows.of(long, short) : List<&2, S.Arg>}: - match long: - case None{}: - match short: - case None{}: - {==} - case Some{_a}: - {==} - case Some{_a}: - {==} - -# the version row of a page with a version is the law's -def bi.vrow( - +vv: String, - long: Maybe<&2, S.Arg>, - short: Maybe<&2, S.Arg> -) -> {S.help.version_row(Some{vv}, long, short) == Laws.version_rows.of(long, short) : List<&2, S.Arg>}: - match long: - case None{}: - match short: - case None{}: - {==} - case Some{_a}: - {==} - case Some{_a}: - {==} - -# the version row is the law's -def bi.version( - +args: List<&2, S.Arg>, - version: Maybe<&2, String> -) -> {S.help.version_row(version, S.by_long(args, "version"), S.by_short(args, "V")) == Laws.version_rows(args, version) - : List<&2, S.Arg>}: - match version: - case None{}: - {==} - case Some{+vv}: - bi.vrow(vv, S.by_long(args, "version"), S.by_short(args, "V")) - -def Laws.builtins_listed(args, version): - +hr = S.help.help_row(S.by_long(args, "help"), S.by_short(args, "h")) - +vr = S.help.version_row(version, S.by_long(args, "version"), S.by_short(args, "V")) - Equal.trans(List<&2, S.Arg>, List.append(&2, S.Arg, hr, vr), List.append(&2, S.Arg, Laws.help_rows(args), vr), - List.append(&2, S.Arg, Laws.help_rows(args), Laws.version_rows(args, version)), - Equal.cong(List<&2, S.Arg>, List<&2, S.Arg>, xs => List.append(&2, S.Arg, xs, vr), hr, Laws.help_rows(args), - bi.help(S.by_long(args, "help"), S.by_short(args, "h"))), - Equal.cong(List<&2, S.Arg>, List<&2, S.Arg>, xs => List.append(&2, S.Arg, Laws.help_rows(args), xs), vr, - Laws.version_rows(args, version), bi.version(args, version))) - -# the code's version check is the law's -def va.asks( - spec: Shake.Cli, - +at: List<&2, String>, - +word: String -) -> {S.version.asks(spec, at, word) == Laws.asks_version(spec, at, word) : Bool}: - match spec: - case S.Cli{_n, _a, _v, _g, _s}: - {==} - -# the parse is the walk's answer, read for the version -def va.res( - +spec: Shake.Cli, - +ws: List<&2, String>, - +rr: Result<&2, &2, S.ParseErr, S.Matched>, - h_walk: {S.parse.finish(S.parse.walk(ws, S.parse.start(spec))) == rr : Result<&2, &2, S.ParseErr, S.Matched>} -) -> {Shake.parse(spec, ws) == S.version.res(spec, rr) : Result<&2, &2, S.ParseErr, S.Matched>}: - Equal.cong(Result<&2, &2, S.ParseErr, S.Matched>, Result<&2, &2, S.ParseErr, S.Matched>, - xx => S.version.res(spec, xx), S.parse.finish(S.parse.walk(ws, S.parse.start(spec))), rr, h_walk) - -# a refused word read for the version, its check answered -def va.pick( - +spec: Shake.Cli, - +at: List<&2, String>, - +word: String, - +bb: Bool, - h_asks: {Laws.asks_version(spec, at, word) == bb : Bool} -) -> {S.version.res(spec, Fail{S.UnknownFlag{at, word}}) - == Fail{Bool.pick(S.ParseErr, bb, S.NeedVersion{}, S.UnknownFlag{at, word})} - : Result<&2, &2, S.ParseErr, S.Matched>}: - ee = Equal.trans(Bool, S.version.asks(spec, at, word), Laws.asks_version(spec, at, word), bb, va.asks(spec, at, word), - h_asks) - Equal.cong(Bool, Result<&2, &2, S.ParseErr, S.Matched>, - xx => Fail{Bool.pick(S.ParseErr, xx, S.NeedVersion{}, S.UnknownFlag{at, word})}, S.version.asks(spec, at, word), bb, - ee) - -def Laws.version_asked(spec, ws, at, word, h_walk, h_asks): - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, ws), - S.version.res(spec, Fail{S.UnknownFlag{at, word}}), Fail{S.NeedVersion{}}, - va.res(spec, ws, Fail{S.UnknownFlag{at, word}}, h_walk), va.pick(spec, at, word, True{}, h_asks)) - -def Laws.version_unasked(spec, ws, at, word, h_walk, h_asks): - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, ws), - S.version.res(spec, Fail{S.UnknownFlag{at, word}}), Fail{S.UnknownFlag{at, word}}, - va.res(spec, ws, Fail{S.UnknownFlag{at, word}}, h_walk), va.pick(spec, at, word, False{}, h_asks)) - -# an answer that refuses no unknown option is left as it is -def va.other( - +spec: Shake.Cli, - rr: Result<&2, &2, S.ParseErr, S.Matched>, - h_rr: {Laws.unknown.res(rr) == False{} : Bool} -) -> {S.version.res(spec, rr) == rr : Result<&2, &2, S.ParseErr, S.Matched>}: - match rr: - case Done{_m}: - {==} - case Fail{ee}: - match ee: - case S.UnknownFlag{at, word}: - Empty.absurd({S.version.res(spec, Fail{S.UnknownFlag{at, word}}) == Fail{S.UnknownFlag{at, word}} - : Result<&2, &2, S.ParseErr, S.Matched>}, Walk.wk.true_false(h_rr)) - case S.Missing{_at, _name}: - {==} - case S.NoValue{_at, _name}: - {==} - case S.BadValue{_at, _name, _value}: - {==} - case S.NeedHelp{_path}: - {==} - case S.Unexpected{_at, _arg}: - {==} - case S.Repeated{_at, _name}: - {==} - case S.NeedVersion{}: - {==} - -def Laws.version_other(spec, ws, rr, h_walk, h_rr): - Equal.trans(Result<&2, &2, S.ParseErr, S.Matched>, Shake.parse(spec, ws), S.version.res(spec, rr), rr, - va.res(spec, ws, rr, h_walk), va.other(spec, rr, h_rr)) - -# a lookup's answer, read as absent or not, under one answer of the check -def rl.at( - bb: Bool, - +x1: S.Arg, - +x2: S.Arg, - +r1: Maybe<&2, S.Arg>, - +r2: Maybe<&2, S.Arg>, - e: {Maybe.is_none(&2, S.Arg, r1) == Maybe.is_none(&2, S.Arg, r2) : Bool} -) -> {Maybe.is_none(&2, S.Arg, S.by_long.at(bb, x1, r1)) == Maybe.is_none(&2, S.Arg, S.by_long.at(bb, x2, r2)) - : Bool}: - match bb: - case True{}: - {==} - case False{}: - e - -# resolved arguments spell long what they spelled -def rl.none( - args: List<&2, S.Arg>, - +vars: List<&2, Shake.Var>, - +name: String -) -> {Maybe.is_none(&2, S.Arg, S.by_long(S.resolve.args(args, vars), name)) - == Maybe.is_none(&2, S.Arg, S.by_long(args, name)) : Bool}: - match args: - case Nil{}: - {==} - case Con{hh, +tt}: - match hh: - case S.Arg{+n, +s, +l, +k, +h, +r, fb, +cs}: - match fb: - case S.Fallback{+ev, +dd}: - +orig = {S.Arg{n, s, l, k, h, r, S.Fallback{ev, dd}, cs} : S.Arg} - rl.at(S.by_long.hit(orig, name), S.resolve.arg(orig, vars), orig, - S.by_long(S.resolve.args(tt, vars), name), S.by_long(tt, name), rl.none(tt, vars, name)) - -def Laws.resolve_asks(spec, vars, at, word): - match spec: - case S.Cli{+nn, +ab, +vv, +gg, +ss}: - Equal.cong(Bool, Bool, xx => Bool.and(Bool.or(String.eq(word, "--version"), Bool.and(String.eq(word, "-V"), xx)), - Bool.and(List.is_empty(&2, String, at), Maybe.is_some(&2, String, vv))), - Maybe.is_none(&2, S.Arg, S.by_long(S.resolve.args(gg, vars), "version")), - Maybe.is_none(&2, S.Arg, S.by_long(gg, "version")), rl.none(gg, vars, "version")) - -def Laws.version_of_some(_name, _about, _ver, _args, _subs): - {==} - -def Laws.version_of_none(_name, _about, _args, _subs, err): - match err: - case S.NeedVersion{}: - {==} - case S.UnknownFlag{_at, _word}: - {==} - case S.Missing{_at, _name}: - {==} - case S.NoValue{_at, _name}: - {==} - case S.BadValue{_at, _name, _value}: - {==} - case S.NeedHelp{_path}: - {==} - case S.Unexpected{_at, _arg}: - {==} - case S.Repeated{_at, _name}: - {==} - -def Laws.version_of_other(_spec, err, h_err): - match err: - case S.NeedVersion{}: - Empty.absurd({Shake.version_of(_spec, S.NeedVersion{}) == None{} : Maybe<&2, String>}, Walk.wk.true_false(h_err)) - case S.UnknownFlag{_at, _word}: - {==} - case S.Missing{_at, _name}: - {==} - case S.NoValue{_at, _name}: - {==} - case S.BadValue{_at, _name, _value}: - {==} - case S.NeedHelp{_path}: - {==} - case S.Unexpected{_at, _arg}: - {==} - case S.Repeated{_at, _name}: - {==} - -def Laws.reads_nil(_spec): - {==} - -def Laws.reads_con(_spec, _rr, _rs): - {==} - -# the code's walk down a path is the law's -def rd.down( - here: Maybe<&2, S.Sub>, - +name: String -) -> {K.reads.down(here, name) == Laws.sub_named(here, name) : Maybe<&2, S.Sub>}: - match here: - case None{}: - {==} - case Some{ss}: - match ss: - case S.Sub{_n, _a, _g, _s}: - {==} - -# the command a path names, as the code finds its arguments -def rd.cmd( - path: List<&2, String>, - +here: Maybe<&2, S.Sub> -) -> {K.reads.cmd(path, here) == K.reads.args(Laws.named_cmd(path, here)) : Maybe<&2, List<&2, S.Arg>>}: - match path: - case Nil{}: - {==} - case Con{+hh, +tt}: - Equal.trans(Maybe<&2, List<&2, S.Arg>>, K.reads.cmd(tt, K.reads.down(here, hh)), - K.reads.cmd(tt, Laws.sub_named(here, hh)), K.reads.args(Laws.named_cmd(tt, Laws.sub_named(here, hh))), - Equal.cong(Maybe<&2, S.Sub>, Maybe<&2, List<&2, S.Arg>>, xx => K.reads.cmd(tt, xx), K.reads.down(here, hh), - Laws.sub_named(here, hh), rd.down(here, hh)), - rd.cmd(tt, Laws.sub_named(here, hh))) - -# a read, its command found as the law finds it -def rd.base( - spec: Shake.Cli, - +path: List<&2, String>, - +name: String, - +want: K.Want, - +found: Maybe<&2, S.Sub>, - h_cmd: {Laws.named_cmd(path, Some{Laws.root_sub(spec)}) == found : Maybe<&2, S.Sub>} -) -> {K.read(spec, K.Read{path, name, want}) == K.reads.at(K.reads.args(found), want, path, name) - : List<&2, K.ReadErr>}: - match spec: - case S.Cli{+bin, +about, _v, +args, +subs}: - +here = {Some{S.Sub{bin, about, args, subs}} : Maybe<&2, S.Sub>} - Equal.cong(Maybe<&2, List<&2, S.Arg>>, List<&2, K.ReadErr>, xx => K.reads.at(xx, want, path, name), - K.reads.cmd(path, here), K.reads.args(found), - Equal.trans(Maybe<&2, List<&2, S.Arg>>, K.reads.cmd(path, here), K.reads.args(Laws.named_cmd(path, here)), - K.reads.args(found), rd.cmd(path, here), - Equal.cong(Maybe<&2, S.Sub>, Maybe<&2, List<&2, S.Arg>>, xx => K.reads.args(xx), - Laws.named_cmd(path, here), found, h_cmd))) - -def Laws.read_no_command(spec, path, name, want, h_cmd): - rd.base(spec, path, name, want, None{}, h_cmd) - -# a read of the argument a command's arguments find under its name -def rd.arg( - +spec: Shake.Cli, - +path: List<&2, String>, - +name: String, - +want: K.Want, - +sn: String, - +sa: String, - +sargs: List<&2, S.Arg>, - +ssubs: List<&2, S.Sub>, - +fa: Maybe<&2, S.Arg>, - h_cmd: {Laws.named_cmd(path, Some{Laws.root_sub(spec)}) == Some{S.Sub{sn, sa, sargs, ssubs}} : Maybe<&2, S.Sub>}, - h_arg: {S.by_name(sargs, name) == fa : Maybe<&2, S.Arg>} -) -> {K.read(spec, K.Read{path, name, want}) == K.reads.arg(fa, want, path, name) : List<&2, K.ReadErr>}: - Equal.trans(List<&2, K.ReadErr>, K.read(spec, K.Read{path, name, want}), - K.reads.arg(S.by_name(sargs, name), want, path, name), K.reads.arg(fa, want, path, name), - rd.base(spec, path, name, want, Some{S.Sub{sn, sa, sargs, ssubs}}, h_cmd), - Equal.cong(Maybe<&2, S.Arg>, List<&2, K.ReadErr>, xx => K.reads.arg(xx, want, path, name), S.by_name(sargs, name), - fa, h_arg)) - -def Laws.read_no_arg(spec, path, name, want, sn, sa, sargs, ssubs, h_cmd, h_arg): - rd.arg(spec, path, name, want, sn, sa, sargs, ssubs, None{}, h_cmd, h_arg) - -# the code's flag test is the law's -def rd.flag(kind: S.ArgKind) -> {K.reads.flag(kind) == Laws.is_flag(kind) : Bool}: - match kind: - case S.Flag{}: - {==} - case S.Opt{}: - {==} - case S.Pos{}: - {==} - case S.Rest{}: - {==} - case S.Many{}: - {==} - -def Laws.read_flag(spec, path, name, sn, sa, sargs, ssubs, nn, sh, lo, kind, hp, rq, fb, cs, h_cmd, h_arg): - Equal.trans(List<&2, K.ReadErr>, K.read(spec, K.Read{path, name, K.WantFlag{}}), - K.reads.one(Bool.not(K.reads.flag(kind)), K.NotFlag{path, name}), - K.reads.one(Bool.not(Laws.is_flag(kind)), K.NotFlag{path, name}), - rd.arg(spec, path, name, K.WantFlag{}, sn, sa, sargs, ssubs, Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}}, h_cmd, - h_arg), - Equal.cong(Bool, List<&2, K.ReadErr>, xx => K.reads.one(Bool.not(xx), K.NotFlag{path, name}), K.reads.flag(kind), - Laws.is_flag(kind), rd.flag(kind))) - -def Laws.read_value(spec, path, name, sn, sa, sargs, ssubs, nn, sh, lo, kind, hp, rq, fb, cs, h_cmd, h_arg): - Equal.trans(List<&2, K.ReadErr>, K.read(spec, K.Read{path, name, K.WantValue{}}), - K.reads.one(K.reads.flag(kind), K.IsFlag{path, name}), - K.reads.one(Laws.is_flag(kind), K.IsFlag{path, name}), - rd.arg(spec, path, name, K.WantValue{}, sn, sa, sargs, ssubs, Some{S.Arg{nn, sh, lo, kind, hp, rq, fb, cs}}, h_cmd, - h_arg), - Equal.cong(Bool, List<&2, K.ReadErr>, xx => K.reads.one(xx, K.IsFlag{path, name}), K.reads.flag(kind), - Laws.is_flag(kind), rd.flag(kind))) diff --git a/src/args.bend b/src/args.bend index a96a857..12d79dc 100644 --- a/src/args.bend +++ b/src/args.bend @@ -3,6 +3,30 @@ # list that starts with the program as invoked (bend 2.0.32 and later); the # rest of a program reads the line more than once, so `words` drops that head # and copies the rest. +# +# Trusted, not proved (IO no law reaches; each is one bind, checked by +# reading): +# - A program compiled by bend 2.0.38 hands `IO.args` the program as invoked +# (the process's `argv[0]`), then the process's words in order and +# unchanged, less the runtime's: a `+RTS` opens a bracket of runtime +# options and a `-RTS` closes it (one left open runs to the end), and +# neither word nor any word inside reaches `IO.args`; at the first `--RTS` +# it stops looking, drops it and passes every later word through. Every +# other word reaches it, `--`, `--help`, `--threads`, `--gpu`, +# `--gpu-build` and `--bend-help` included. Run as `bend file.bend [args]`, +# `IO.args` is the file as given, then the arguments, after `bend` took a +# first `--`. This is the C `main` bend emits and bend2/effs/args.c, read +# from 2.0.38 and confirmed by compiling a program that prints its +# `IO.args`; 2.0.29, 2.0.32 and 2.0.38 each changed it, so every bend bump +# rechecks it. +# - `argv` hands on exactly the list `IO.args` answers, through `words` +# (laws/ArgvDropsProgram.bend). +# - `env_vars(spec)` answers, for each name it asks for +# (laws/EnvVarsAskedInOrder.bend) and in that order, the name and the +# value `IO.get_env` answers, unchanged and empty included, and no pair +# for a name `IO.get_env` fails for. bend 2.0.38 answers `getenv`'s value +# for a set variable and fails for an unset one (`io_get_env_run` in +# bend2/effs/get_env.c); every bend bump rechecks it. import Base import ./cli.bend as C @@ -23,7 +47,7 @@ def words(xs: List<&1, String>) -> List<&2, String>: copy(t) # the process's arguments, without the program name -def argv() -> IO(List<&2, String>): # noqa: L001 IO: reads argv (SHAKE-TRUST-4) +def argv() -> IO(List<&2, String>): # noqa: L001 IO: reads argv (trusted above) IO.bind(List<&1, String>, List<&2, String>, IO.args(), xs => IO.pure(List<&2, String>, words(xs))) @@ -40,7 +64,7 @@ def kept( rest # the names that are set, each with its value, in order -def vars(names: List<&2, String>) -> IO(List<&2, C.Var>): # noqa: L001 IO: reads env vars (SHAKE-TRUST-5) +def vars(names: List<&2, String>) -> IO(List<&2, C.Var>): # noqa: L001 IO: reads env vars (trusted above) match names: case Nil{}: IO.pure(List<&2, C.Var>, []) @@ -50,5 +74,5 @@ def vars(names: List<&2, String>) -> IO(List<&2, C.Var>): # noqa: L001 IO: read rest => IO.pure(List<&2, C.Var>, kept(hh, got, rest)))) # the env vars a spec names that are set, each with its value -def env_vars(spec: C.Cli) -> IO(List<&2, C.Var>): # noqa: L001 IO: reads env vars (SHAKE-TRUST-5) +def env_vars(spec: C.Cli) -> IO(List<&2, C.Var>): # noqa: L001 IO: reads env vars (trusted above) vars(C.env.names(spec)) diff --git a/src/check.bend b/src/check.bend index 5851eaa..e27a207 100644 --- a/src/check.bend +++ b/src/check.bend @@ -1,6 +1,6 @@ # shake/check: the ways a Cli can contradict itself, internal (main.bend # exports `check`). `parse` does not call it: a program checks its spec once, -# and every guarantee of SPEC.md's parse rows holds for a spec `check` passes. +# and every parse guarantee (LAWS.bend) holds for a spec `check` passes. import Base import ./cli.bend as C From 9246ef256913646736656198d2dcb77500650346 Mon Sep 17 00:00:00 2001 From: Noah Gardner Date: Sat, 31 Oct 2026 23:09:20 -0400 Subject: [PATCH 2/3] docs: keep SPEC.md as docs/SPEC.md The requirement table the law files replaced stays as a record: the RFCs name guarantees by its IDs. LAWS.bend points to it. Co-Authored-By: Claude Opus 5.5 --- LAWS.bend | 4 +- docs/SPEC.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 docs/SPEC.md diff --git a/LAWS.bend b/LAWS.bend index fb93dc0..97897ff 100644 --- a/LAWS.bend +++ b/LAWS.bend @@ -40,8 +40,8 @@ # names, or whose first pair's value is empty, gives no env value. # # The reasoning behind each guarantee is in docs/rfc/shake-spec.md and -# docs/rfc/shake-env-and-suggestions.md, which name them by the requirement -# IDs they had before laws/. +# docs/rfc/shake-env-and-suggestions.md. Those name the guarantees by the +# requirement IDs of docs/SPEC.md, the table these files replaced. import Base import ./laws/AllowedAny.bend as AllowedAny import ./laws/ArgLineMark.bend as ArgLineMark diff --git a/docs/SPEC.md b/docs/SPEC.md new file mode 100644 index 0000000..134160a --- /dev/null +++ b/docs/SPEC.md @@ -0,0 +1,126 @@ +# shake specification + +This is the list of every behavior shake guarantees, each under a stable requirement ID. Every requirement is about the interface in `main.bend`: its types, builders, `parse`, `parse_env`, `check`, `check_reads`, the readers, `help`, `err_text`, `help_path`, `err_path`, `version_of`, `suggestion`, `argv` and `env_vars`. Every module under `src/` is internal and carries no promise. + +Every requirement has one of two levels. A **Proved** requirement holds for every input, and is backed by a quantified law (a `for` or `exs` binder) in `src/LAWS.bend` that passes the proof gate. A **Trusted** requirement is an assumption shake cannot check from inside its own gate, and it is listed in the trust boundary below. A Proved requirement whose laws have not all landed has status **pending**: we intend to prove it, and until then it is not guaranteed. The proof gate is this check: the first line `bend src/PROOF.bend` prints is exactly `ALL PROOFS CHECK`. Tests and fixtures are never evidence for a requirement. + +A spec is **well-formed** when `check` reports nothing for it (SHAKE-SPEC-1). The parse requirements hold for well-formed specs; a spec that contradicts itself, such as two options spelled `-n`, is its author's bug and not an input a user can give. Every error a row names but `NeedHelp` and `NeedVersion` also carries `at`, the command path selected where the parse failed (SHAKE-ERR-2); rows write it only where it matters. A **plain word** is `-`, or a word that does not start with `-`. The **current command** is the command whose arguments `parse` matches words against: the root, then each subcommand it selects. The **selected path** is the names of the subcommands selected, in order. A successful parse answers the root command's **Matched**, as clap answers `ArgMatches`: the bindings that command made while it was current and, when a subcommand was selected under it, that subcommand's name and its own Matched, and so on down the selected path. + +The words `parse` receives are what the program passes it. In a compiled program they come from `argv`, which drops the program name `IO.args` starts with, after the runtime has taken its own words: those between `+RTS` and `-RTS`, and a `--RTS` (SHAKE-TRUST-2). A `--` the user types reaches `parse`. + +An argument's **env var** is the name `env` gives it; an argument built without `env` has none. `parse_env` also receives **vars**, a list of name and value pairs, which in a compiled program come from `env_vars` (SHAKE-TRUST-5). An argument's **env value** is the value of the first pair in vars named by its env var, when that value is not empty; an env var no pair names, or whose first pair's value is empty, gives no env value. + +The reasoning behind each requirement, the verdict of each against the code at `b93357a`, and the decisions that shaped them are in [docs/rfc/shake-spec.md](rfc/shake-spec.md). The RFC also records the audit behind them and the rollout that proved every row. The env and suggestion rows (SHAKE-PARSE-11, PARSE-12, HELP-3, ERR-3, ERR-4, ARGS-2, TRUST-5) are designed in [docs/rfc/shake-env-and-suggestions.md](rfc/shake-env-and-suggestions.md). + +## Format + +A requirement table is any table whose header row is exactly `| ID | Requirement | Level | Status | Law |`. An ID is uppercase segments joined by hyphens, at least two (`[A-Z][A-Z0-9]*(-[A-Z0-9]+)+`), unique within the requirement tables, and never reused once released. Level is `Proved` or `Trusted`. Status is `proved` or `pending` for a Proved row and empty for a Trusted row. A Law cell holds ` ` entries, paths relative to this file, separated by `; `. A proved row names one or more laws, and together they prove it. A pending row may name laws that each prove part of it; the row stays pending until its requirement is proved in full, and "Left to prove" says what is missing. + +A law proves a requirement when a comment line `# `, alone on its line, sits in the unbroken comment block directly above its `law` line. A law may carry several tags, one per line: + +``` +# LAW: a request for help has no error text +# SHAKE-ERR-1 +law err_text_help: +``` + +A tag may name a proved or a pending requirement, never a Trusted one or an ID no requirement table lists. bolt's `trace` rule checks all of this over the whole tree, and its `closed` rule rejects a law with no binder; both are errors in `bolt.bend`. + +## Requirements + +### How a word is read (SHAKE-TOK) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-TOK-1 | A word that starts with `--`, is not exactly `--`, and has at least one char before its first `=` is a long option: the chars between `--` and the first `=` (or the end) are its spelling, and when there is an `=` everything after it, verbatim and possibly empty, is its value. A word `--=...` is refused as `UnknownFlag{word}`. | Proved | proved | src/LAWS.bend long_no_name; src/LAWS.bend long_binds; src/LAWS.bend cut_first_eq; src/LAWS.bend cut_no_eq | +| SHAKE-TOK-2 | A word that starts with `-`, is not `-` and does not start with `--` is a cluster of short options, read letter by letter: a letter that spells a flag binds it and the next letter goes on; a letter that spells an option takes the rest of the word as its value, after one `=` when the rest starts with it (verbatim, and possibly empty), or the next word when nothing is left; a letter no argument of the current command spells refuses the whole word as `UnknownFlag{word}`, except that a word `-h` may be a request for help (SHAKE-PARSE-8) and a word `-V` a request for the version (SHAKE-VER-1). | Proved | proved | src/LAWS.bend short_step; src/LAWS.bend letter_flag; src/LAWS.bend letter_value; src/LAWS.bend letter_value_eq; src/LAWS.bend letter_value_next; src/LAWS.bend letter_unknown | +| SHAKE-TOK-3 | A plain word is never read as an option. | Proved | proved | src/LAWS.bend plain_word_step; src/LAWS.bend dash_word_step; src/LAWS.bend value_binds; src/LAWS.bend value_refused; src/LAWS.bend raw_next; src/LAWS.bend help_walk | +| SHAKE-TOK-4 | When a word exactly `--` reaches `parse` where an option could stand, it binds nothing and every later word is read as a plain word bound to a positional, whatever its shape. | Proved | proved | src/LAWS.bend dd_step; src/LAWS.bend raw_next; src/LAWS.bend raw_binds; src/LAWS.bend raw_no_pos; src/LAWS.bend raw_refused | +| SHAKE-TOK-5 | A flag given a value (`--verbose=x`, or `-v=x` in a cluster) is refused as `Unexpected{word}`. | Proved | proved | src/LAWS.bend flag_long_valued; src/LAWS.bend letter_flag_eq | +| SHAKE-TOK-6 | An option given no value in its own word takes the next word as its value, unless that word starts with `-` and is not `-`, or there is none; then the parse fails with `NoValue{name}`. | Proved | proved | src/LAWS.bend value_flag_shaped; src/LAWS.bend value_absent; src/LAWS.bend value_binds | +| SHAKE-TOK-7 | A long or short spelling that no argument of the current command has is refused as `UnknownFlag{word}`, except a word `--help` or `-h` that SHAKE-PARSE-8 reads as a request for help, and a word `--version` or `-V` that SHAKE-VER-1 reads as a request for the version. Arguments of a parent command are not matched after a subcommand is selected. | Proved | proved | src/LAWS.bend unknown_long; src/LAWS.bend unknown_short; src/LAWS.bend enter_step; src/LAWS.bend letter_unknown | + +### What a parse binds (SHAKE-PARSE) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-PARSE-1 | For every well-formed spec and word list, when `parse` succeeds, every binding in the Matched of each command on the selected path is `true` for a flag given while that command was current, a value-carrying piece of the words given while that command was current (a plain word bound to a positional, the value part of an option word, or the word after an option), bound verbatim under the name of the argument it was given for, or the default of an argument of that command that the words did not bind. One word adds at most one binding other than `true` to the current command, and a word read while the parse can still succeed is never ignored: it binds a value, sets a flag, enters a subcommand, waits for an option's value, switches to positionals, starts a help path, or fails. | Proved | proved | src/LAWS.bend values_given; src/LAWS.bend values_here; src/LAWS.bend one_value_per_word; src/LAWS.bend word_used | +| SHAKE-PARSE-2 | Plain words that select no subcommand bind the current command's positionals in spec order, one word each; a plain word with no positional left is refused as `Unexpected{word}`. | Proved | proved | src/LAWS.bend no_pos_left; src/LAWS.bend pos_next; src/LAWS.bend pos_binds; src/LAWS.bend start_state; src/LAWS.bend enter_step; src/LAWS.bend pos_of_app; src/LAWS.bend pos_of_pos; src/LAWS.bend pos_of_opt; src/LAWS.bend help_word_next; src/LAWS.bend help_word_binds | +| SHAKE-PARSE-3 | A rest positional binds every remaining plain word that selects no subcommand, in order, each under the rest's name. | Proved | proved | src/LAWS.bend rest_next; src/LAWS.bend rest_binds; src/LAWS.bend rest_raw_next; src/LAWS.bend rest_raw_binds | +| SHAKE-PARSE-4 | Before `--`, a plain word that names a subcommand of the current command selects it: the selected path gains that name, its arguments become current, and the bindings made so far stay with the command that made them; the subcommand's own bindings start empty. When a pending positional of the current command is required, has no default and is not a rest, the parse fails instead with `Missing{name}` of the first such one; a rest positional does not block a subcommand, and SHAKE-PARSE-7 checks it when the words end. | Proved | proved | src/LAWS.bend enter_step; src/LAWS.bend enter_missing; src/LAWS.bend none_blocking; src/LAWS.bend first_blocking | +| SHAKE-PARSE-5 | A value given to an argument with a nonempty choices list binds only when it is in the list, and otherwise fails with `BadValue{name, value}`. An argument with no choices accepts every value. | Proved | proved | src/LAWS.bend choice_refused; src/LAWS.bend value_refused; src/LAWS.bend long_refused; src/LAWS.bend raw_refused; src/LAWS.bend letter_value_bad; src/LAWS.bend pos_next; src/LAWS.bend pos_binds; src/LAWS.bend raw_binds; src/LAWS.bend value_binds; src/LAWS.bend long_binds; src/LAWS.bend letter_value; src/LAWS.bend letter_value_eq | +| SHAKE-PARSE-6 | After the last word, each argument of every command on the selected path that the words left unbound in that command and that has a default is bound to its default in that command's Matched. A bound argument's value is never replaced, and no argument without a default gains a binding. | Proved | proved | src/LAWS.bend default_filled; src/LAWS.bend bound_kept; src/LAWS.bend no_default; src/LAWS.bend no_argument | +| SHAKE-PARSE-7 | A successful parse binds every required argument of every command on the selected path, in that command's Matched. Otherwise the parse fails with `Missing{name}`. | Proved | proved | src/LAWS.bend required_present; src/LAWS.bend required_missing | +| SHAKE-PARSE-8 | Before `--` and before any positional of the current command is bound, a plain word `help`, a word `--help` when no argument of the current command has the long spelling `help`, or a word `-h` when no argument of the current command has the short spelling `h` or the long spelling `help`, makes the parse fail with `NeedHelp{path}`, where `path` is the selected path followed by the remaining words, as long as each names a subcommand under the one before; the first remaining word that does not makes the parse fail with `Unexpected{word}`. A command that declares its own long `help` gets `--help` as that argument (SHAKE-TOK-1), and `-h` is read as any other short option word (SHAKE-TOK-2); `help` still asks for help. | Proved | proved | src/LAWS.bend help_unknown; src/LAWS.bend help_step; src/LAWS.bend help_walk; src/LAWS.bend help_path; src/LAWS.bend dash_help_step; src/LAWS.bend dash_help_path; src/LAWS.bend short_help_step; src/LAWS.bend short_help_path | +| SHAKE-PARSE-9 | Once a word makes the parse fail, the words after it do not change what `parse` answers. | Proved | proved | src/LAWS.bend fail_stays | +| SHAKE-PARSE-10 | A flag or an option built with `opt` that is given a second time while the same command is current, under any of its spellings, fails the parse with `Repeated{at, name}`; an argument of a parent command with the same name is a different argument (SHAKE-TOK-7). An option built with `many` binds every value given, in the order given, and `get_all` of its command's Matched reads them all. | Proved | proved | src/LAWS.bend repeated_long_flag; src/LAWS.bend repeated_long_opt; src/LAWS.bend letter_flag_again; src/LAWS.bend letter_opt_again; src/LAWS.bend long_binds; src/LAWS.bend letter_value; src/LAWS.bend letter_value_eq | +| SHAKE-PARSE-11 | `parse_env(spec, words, vars)` reads `words` exactly as `parse` reads them against `spec` with every argument's default, in every command, replaced by its **fallback**, and answers what that parse answers, except as SHAKE-PARSE-12 says. An argument's fallback is its env value when it has one and is not a flag; for a flag with an env value, `true` when that value, lowercased, is none of `0`, `false`, `no`, `off`, `n` and `f`, and no fallback otherwise; and for an argument with no env value, its default. So a value the words bind wins over the env value and the env value over the default (SHAKE-PARSE-6), an argument with a fallback is filled by it and needs nothing from the words to satisfy `required` (SHAKE-PARSE-7), a required positional with a fallback does not block a subcommand (SHAKE-PARSE-4), and `parse_env(spec, words, [])` is `parse(spec, words)`. | Proved | proved | src/LAWS.bend env_sets; src/LAWS.bend env_of_none; src/LAWS.bend env_of_nil; src/LAWS.bend env_of_hit; src/LAWS.bend env_of_empty; src/LAWS.bend env_of_skip; src/LAWS.bend fallback_env; src/LAWS.bend fallback_flag_on; src/LAWS.bend fallback_flag_off; src/LAWS.bend fallback_default; src/LAWS.bend resolve_spec; src/LAWS.bend resolve_args_con; src/LAWS.bend resolve_subs_con; src/LAWS.bend env_parse; src/LAWS.bend env_none | +| SHAKE-PARSE-12 | When the parse SHAKE-PARSE-11 describes succeeds and an argument other than a flag, of a command on the selected path, has an env value outside its nonempty choices that the command's Matched binds to it, `parse_env` fails instead with `BadValue{at, name, value}` of the first such argument, the root command first and each command's arguments in spec order, where `at` is the selected path. A word's value outside the choices never binds (SHAKE-PARSE-5), so only an argument filled from its env value can be bound to it. When that parse fails, `parse_env` fails with the same error. | Proved | proved | src/LAWS.bend env_check_fail; src/LAWS.bend env_check_good; src/LAWS.bend env_check_bad; src/LAWS.bend bad_go_leaf; src/LAWS.bend bad_go_root; src/LAWS.bend bad_go_down; src/LAWS.bend bad_args_here; src/LAWS.bend bad_args_next; src/LAWS.bend bad_args_nil; src/LAWS.bend bad_arg_some; src/LAWS.bend bad_arg_unset; src/LAWS.bend bad_arg_flag; src/LAWS.bend bad_arg_allowed; src/LAWS.bend bad_arg_unbound | + +### Requests for the version (SHAKE-VER) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-VER-1 | When the spec has a version, `parse` fails with `NeedVersion{}` wherever it would otherwise fail with `UnknownFlag{[], "--version"}`, and wherever it would otherwise fail with `UnknownFlag{[], "-V"}` when no argument of the root command has the long spelling `version`: that is, when the first word to fail the parse is `--version` or `-V` read while the root command is current (SHAKE-PARSE-9, TOK-7). Every other result of the parse is what it would be without this row, and `parse_env` reads the version as `parse` does (SHAKE-PARSE-11). | Proved | proved | src/LAWS.bend version_asked; src/LAWS.bend version_unasked; src/LAWS.bend version_other; src/LAWS.bend resolve_asks | +| SHAKE-VER-2 | `version_of(spec, err)` is `Some{name ++ " " ++ version}`, the root command's name and the spec's version, when `err` is `NeedVersion{}` and the spec has a version, and `None` otherwise. | Proved | proved | src/LAWS.bend version_of_some; src/LAWS.bend version_of_none; src/LAWS.bend version_of_other | + +### Checking a spec (SHAKE-SPEC) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-SPEC-1 | `check(spec)` reports exactly one error for each of: a rest positional that is not its command's last positional; an argument of a command with the name, short spelling or long spelling of an earlier argument of that command; a subcommand with the name of an earlier subcommand of the same command; a subcommand named `help`; a default outside its argument's nonempty choices; a required positional after an optional one. It reports nothing else. | Proved | proved | src/LAWS.bend check_listed | + +### Checking reads (SHAKE-READ) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-READ-1 | `check_reads(spec, reads)` reports exactly one error for each read in `reads` that is wrong, in the order of `reads`, and nothing for a read that is right: `NoCommand{path}` when a name of its path is not a subcommand of the command before it, the root first; otherwise `NoArg{path, name}` when the command its path names has no argument named `name`; otherwise, for a flag read (`read_flag`), `NotFlag{path, name}` when that argument is not a flag, and for a value read (`read_value`), `IsFlag{path, name}` when it is one. It reports nothing else. | Proved | proved | src/LAWS.bend reads_nil; src/LAWS.bend reads_con; src/LAWS.bend read_no_command; src/LAWS.bend read_no_arg; src/LAWS.bend read_flag; src/LAWS.bend read_value | + +### Reading a result (SHAKE-GET) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-GET-1 | For every Matched `m` and name: `get(m, name)` is `Some` of the value of the last binding of that name that `m`'s command made, or `None` when it made none; `get_all(m, name)` is the values of every binding of that name that `m`'s command made, in binding order; `on(m, name)` is true exactly when `get(m, name)` is `Some{"true"}`. A command's Matched reads only its own bindings: the subcommand selected under it changes none of these. | Proved | proved | src/LAWS.bend get_all_append; src/LAWS.bend get_all_hit; src/LAWS.bend get_all_miss; src/LAWS.bend get_last; src/LAWS.bend get_none; src/LAWS.bend on_some; src/LAWS.bend on_none; src/LAWS.bend get_all_node; src/LAWS.bend get_node | +| SHAKE-GET-2 | For every Matched `m`: `sub_name(m)` is `Some` of the name of the subcommand selected under `m`'s command, or `None` when none was; `sub_of(m, name)` is that subcommand's Matched when its name is `name`, and `None` otherwise; `path_of(m)` is the names of the subcommands selected below `m`'s command, in order, so the whole selected path for the Matched `parse` answers; `at(m, [])` is `Some{m}`, and `at(m, name <> rest)` is `at` of `sub_of(m, name)` and `rest`, or `None` when `sub_of` is `None`. | Proved | proved | src/LAWS.bend sub_name_leaf; src/LAWS.bend sub_name_node; src/LAWS.bend sub_of_hit; src/LAWS.bend sub_of_miss; src/LAWS.bend sub_of_leaf; src/LAWS.bend path_of_leaf; src/LAWS.bend path_of_node; src/LAWS.bend at_nil; src/LAWS.bend at_cons | + +### Help pages (SHAKE-HELP) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-HELP-1 | `help(spec, path)` renders the page of the command reached by following each name of `path` from the root through the subcommands, skipping a name that is not a subcommand where it stands. | Proved | proved | src/LAWS.bend help_page; src/LAWS.bend reach_child; src/LAWS.bend reach_skip | +| SHAKE-HELP-2 | A page lists each subcommand of its command exactly once, in spec order, followed by `help`. When its command has positionals, an Arguments block after the subcommands lists each exactly once, in spec order, by the mark the usage line gives it without its leading space, then its help text and the extras an option line has (SHAKE-HELP-3); a command with no positional has no Arguments block. The Options block lists each flag and option exactly once, in spec order, followed by the built-in rows of SHAKE-HELP-4. Its usage line names each positional exactly once, in spec order, as `` when required and `[NAME]` otherwise, with `...` after a rest positional. | Proved | proved | src/LAWS.bend cmds_listed; src/LAWS.bend opts_listed; src/LAWS.bend usage_listed; src/LAWS.bend args_listed; src/LAWS.bend arg_line_mark; src/LAWS.bend builtins_listed | +| SHAKE-HELP-3 | The Options or Arguments line of an argument that has an env var `NAME` shows `[env: NAME]` after its help text, before its `[default: ...]` and `[possible values: ...]`, as clap's does; the line of one with no env var is what it would be without this row. | Proved | proved | src/LAWS.bend help_env_shown; src/LAWS.bend help_env_none; src/LAWS.bend arg_line_mark | +| SHAKE-HELP-4 | The Options block of a page ends with a row `Print help` spelled as the requests for help SHAKE-PARSE-8 answers on its command: `-h, --help` when the command has neither the short spelling `h` nor the long spelling `help`, `--help` when it has `h` but not `help`, and no row when it has `help`. On the root page of a spec with a version a row `Print version` follows, spelled as the requests SHAKE-VER-1 answers: `-V, --version` when the root has neither the short spelling `V` nor the long spelling `version`, `--version` when it has `V` but not `version`, and no row when it has `version`. These rows are laid out as the other rows of the block, and make the usage line show `[OPTIONS]`. | Proved | proved | src/LAWS.bend builtins_listed | + +### Errors (SHAKE-ERR) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-ERR-1 | `err_text(spec, err)` is empty exactly when `help_path(err)` is `Some` or `err` is `NeedVersion{}`, that is, when the parse failed with a request for help or for the version. | Proved | proved | src/LAWS.bend err_text_help; src/LAWS.bend err_text_iff; src/LAWS.bend err_text_version | +| SHAKE-ERR-2 | For every error but a request for help or for the version, `err_path(err)` is the path of subcommands the parse had selected at the word that failed it, and `err_text(spec, err)` shows the usage line of the command at that path, as `help(spec, err_path(err))` does. `err_path(NeedVersion{})` is `[]`. | Proved | proved | src/LAWS.bend err_path_at; src/LAWS.bend err_text_usage; src/LAWS.bend help_page; src/LAWS.bend unknown_long; src/LAWS.bend unknown_short; src/LAWS.bend no_pos_left; src/LAWS.bend choice_refused; src/LAWS.bend flag_long_valued; src/LAWS.bend value_flag_shaped; src/LAWS.bend value_absent; src/LAWS.bend help_unknown; src/LAWS.bend long_no_name; src/LAWS.bend repeated_long_flag; src/LAWS.bend repeated_long_opt; src/LAWS.bend value_refused; src/LAWS.bend long_refused; src/LAWS.bend raw_no_pos; src/LAWS.bend raw_refused; src/LAWS.bend enter_missing; src/LAWS.bend required_missing; src/LAWS.bend letter_flag_eq; src/LAWS.bend letter_unknown; src/LAWS.bend letter_flag_again; src/LAWS.bend letter_opt_again; src/LAWS.bend letter_value_bad | +| SHAKE-ERR-3 | `suggestion(spec, err)` is `None` for every error but an `UnknownFlag{at, word}` whose word starts with `--` (SHAKE-TOK-1, TOK-7). For such a word, let `typed` be its chars after `--` up to its first `=`, and the **candidates** the long spellings of the flags and options of the command at `at`, reached as `help(spec, at)` reaches it (SHAKE-HELP-1), in spec order; a parent command's arguments, subcommands and `help` are not candidates. A candidate `c` is **close** when `0 < d` and `3 * d <= max(length(typed), length(c))`, where `d` is the optimal string alignment distance from `typed` to `c`: the fewest single-char insertions, deletions, substitutions and swaps of two adjacent chars that turn one into the other, editing no char twice. The suggestion is `Some{"--" ++ c}` for the close candidate with the least `d`, the first in spec order among equals, and `None` when no candidate is close. A short option word never gets one. | Proved | proved | src/LAWS.bend sug_other; src/LAWS.bend sug_short; src/LAWS.bend sug_long; src/LAWS.bend sug_found_some; src/LAWS.bend cands_some; src/LAWS.bend cands_none; src/LAWS.bend sug_close; src/LAWS.bend best_nil; src/LAWS.bend best_far; src/LAWS.bend best_only; src/LAWS.bend best_here; src/LAWS.bend best_there | +| SHAKE-ERR-4 | When `suggestion(spec, err)` is `Some{s}`, `err_text(spec, err)` contains `s`. | Proved | proved | src/LAWS.bend err_text_tip | +| SHAKE-ERR-5 | `err_text` names the argument of a `Missing{at, name}`, `NoValue{at, name}`, `BadValue{at, name, value}` or `Repeated{at, name}` error by the **display form** of the argument named `name` of the deepest command on the path `at` that has one, the root first and each name of `at` reached as `help` reaches it (SHAKE-HELP-1): for a flag, `--` and its long spelling, else `-` and its short spelling; for an option, the same followed by ` `, and by `...` for one built with `many`; for a positional, `` when it is required and `[NAME]` otherwise, followed by `...` for a rest; `NAME` being the argument's name in upper case. A flag or option with neither spelling (an empty spelling counts as none), and a name no command on the path has, is named by `name` itself. The message of a `BadValue` whose argument has nonempty choices `c1`, ..., `cn` ends with the line ` [possible values: c1, ..., cn]`. | Proved | proved | src/LAWS.bend err_find; src/LAWS.bend err_form; src/LAWS.bend err_missing_named; src/LAWS.bend err_novalue_named; src/LAWS.bend err_bad_named; src/LAWS.bend err_repeated_named | + +### The argument list (SHAKE-ARGS) + +| ID | Requirement | Level | Status | Law | +| :---- | :---- | :---- | :---- | :---- | +| SHAKE-ARGS-1 | The copy `argv` makes of the words `IO.args` gives drops the first, the program as invoked, and keeps all the others, in order and unchanged: for every list, reading the copy back gives the list after its first word, and nothing for an empty list. | Proved | proved | src/LAWS.bend copy_keeps; src/LAWS.bend words_keeps | +| SHAKE-ARGS-2 | The names `env_vars(spec)` asks `IO.get_env` for are the env vars of the arguments that have one, the root command's arguments first and then each subcommand's, depth first, each command's arguments in spec order. | Proved | proved | src/LAWS.bend env_names_root; src/LAWS.bend env_names_forest; src/LAWS.bend env_names_some; src/LAWS.bend env_names_none | + +## Left to prove + +No row is pending. The RFCs' Rollouts and [docs/rfc/shake-walker-proofs.md](rfc/shake-walker-proofs.md) say in which phase each row's laws landed. Every walker law holds wherever the words before the word it is about leave the walker in the state its premise names. + +## Trust boundary + +These assumptions sit outside the proofs. They are the complete list of Trusted requirements, and a passing proof gate says nothing about them. + +| ID | Assumption | Why it is trusted | +| :---- | :---- | :---- | +| SHAKE-TRUST-1 | The Bend checker is sound: a proof it accepts proves its law. | It cannot be checked from inside Bend; this is EZ-TRUST-1. shake pins bend 2.0.38 through the flake. | +| SHAKE-TRUST-2 | A program compiled by bend 2.0.38 hands `IO.args` the program as invoked (the process's `argv[0]`) followed by the process's words after it, in order and unchanged, except for the runtime's words. A word `+RTS` opens a bracket of runtime options and a word `-RTS` closes it (a bracket left open runs to the end); neither word, nor any word inside a bracket, reaches `IO.args`. At the first word `--RTS`, inside a bracket or not, it stops examining words, drops that `--RTS` and passes every later word through unchanged. It reads the bracketed words, after the words of the env var `BENDRTS`, as runtime options: `--threads N` and `--gpu X` set the runtime, and it ends the process before `main` on `--help`, on `--gpu-build`, on a bad value and on any other word it reads there. Every other word reaches `IO.args`: `--`, `--help`, `--threads`, `--gpu`, `--gpu-build` and `--bend-help` included. Run as `bend file.bend [args]`, `IO.args` is the file as given, then the arguments, after the `bend` command has taken a first `--`. | It is the C `main` bend emits (`main` in the C that `bend file.bend -o file.c` writes, and `IO.args` in bend2/effs/args.c), read from bend 2.0.38's output and confirmed by compiling a program that prints its `IO.args` and the demo; 2.0.29 stopped taking `--help`, 2.0.32 put the program first and 2.0.38 moved the runtime's options inside `+RTS` … `-RTS`, so a plain `--` now reaches the program. It changes when bend does, so every bend bump rechecks it. | +| SHAKE-TRUST-3 | The proof-gate runner fails the build unless the first line of `bend src/PROOF.bend` is `ALL PROOFS CHECK`. | It is the flake's `proofs` check, run by `nix flake check` in CI, which runs every PROOF.bend on the flake's bend and compares the first line; `ez prove` does the same once ez runs on bend 2.0.38. | +| SHAKE-TRUST-4 | `argv` hands on exactly the list `IO.args` answers, through the copy SHAKE-ARGS-1 is about. | It is IO, which no law can reach: `argv` in `src/args.bend` is one `IO.bind` of `IO.args` into `words`, short enough to check by reading, and marked `# noqa: L001` for that reason. | +| SHAKE-TRUST-5 | `env_vars(spec)` answers, for each name it asks for (SHAKE-ARGS-2) and in that order, the pair of the name and the value `IO.get_env` answers for it, unchanged and empty included, and no pair for a name `IO.get_env` fails for. A program compiled by bend 2.0.38 answers `getenv`'s value for a variable that is set and fails for one that is not. | It is IO, which no law can reach: `env_vars` in `src/args.bend` is one `IO.bind` of `IO.get_env` per name, short enough to check by reading and marked `# noqa: L001`. What `IO.get_env` answers is `io_get_env_run` in bend2/effs/get_env.c, read from bend 2.0.38's source; every bend bump rechecks it, as for SHAKE-TRUST-2. | From 9652f3cc60e7390e252ee1d8d812190924ed3a23 Mon Sep 17 00:00:00 2001 From: Noah Gardner Date: Sat, 31 Oct 2026 23:33:28 -0400 Subject: [PATCH 3/3] build: bolt v1.22.0; drop the noqa comments it finds silence nothing bolt v1.22.0 reads law and proof files by what LAWS.bend and PROOF.bend import, which this layout needs. The rules changed since the v1.19.0 pin, so some noqa comments no longer silence anything (S005); they are removed. Co-Authored-By: Claude Opus 5.5 --- examples/demo/main.bend | 16 +++++++------- ez.lock.toml | 6 +++--- ez.toml | 6 +++--- src/eq.bend | 46 ++++++++++++++++++++--------------------- src/grow.bend | 6 +++--- src/walk.bend | 4 ++-- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/examples/demo/main.bend b/examples/demo/main.bend index da8137a..6254b53 100644 --- a/examples/demo/main.bend +++ b/examples/demo/main.bend @@ -3,7 +3,7 @@ import Base import ../../main.bend as Shake # the program spec -def spec() -> Shake.Cli: # noqa: L001 the example program, not the library +def spec() -> Shake.Cli: Shake.app("demo", "A tiny command-line program.", Some{"0.1.0"}, [Shake.flag("verbose", Some{"v"}, Some{"verbose"}, "More output")], [ @@ -44,12 +44,12 @@ def greet.tags(tags: List<&2, String>) -> IO(Unit): IO.print("tags: " ++ String.join(hh <> tt, " ")) # the value bound to `name`, or "" when nothing bound it -def text(+mm: Shake.Matched, +name: String) -> String: # noqa: L001 the example program, not the library +def text(+mm: Shake.Matched, +name: String) -> String: Maybe.default(&2, String, Shake.get(mm, name), "") # a successful greet: `who` when given, else `--name`, which has a default; # verbose when the root's `-v` or greet's own is set -def greet(+root: Shake.Matched, +mm: Shake.Matched) -> IO(Unit): # noqa: L001 IO +def greet(+root: Shake.Matched, +mm: Shake.Matched) -> IO(Unit): do IO: +who : String <- IO.pure(String, Maybe.default(&2, String, Shake.get(mm, "who"), text(mm, "name"))) @@ -58,7 +58,7 @@ def greet(+root: Shake.Matched, +mm: Shake.Matched) -> IO(Unit): # noqa: L001 I greet.v(Bool.or(Shake.on(root, "verbose"), Shake.on(mm, "verbose"))) # a successful add -def add(+mm: Shake.Matched) -> IO(Unit): # noqa: L001 IO +def add(+mm: Shake.Matched) -> IO(Unit): IO.print(text(mm, "a") ++ " + " ++ text(mm, "b") ++ " x" ++ text(mm, "times") ++ " base " ++ text(mm, "base")) @@ -92,7 +92,7 @@ def run.sub(+root: Shake.Matched, sel: Maybe<&2, String>) -> IO(Unit): run.add(Shake.sub_of(root, "add"))) # the root's Matched: dispatch on its subcommand -def run(+mm: Shake.Matched) -> IO(Unit): # noqa: L001 IO +def run(+mm: Shake.Matched) -> IO(Unit): run.sub(mm, Shake.sub_name(mm)) # a hard error @@ -118,11 +118,11 @@ def fail.at(+app: Shake.Cli, ask: Maybe<&2, List<&2, String>>, +ee: Shake.ParseE fail.ver(app, Shake.version_of(app, ee), ee) # a failed parse: help text, the version, or an error -def fail(+app: Shake.Cli, +ee: Shake.ParseErr) -> IO(Unit): # noqa: L001 IO +def fail(+app: Shake.Cli, +ee: Shake.ParseErr) -> IO(Unit): fail.at(app, Shake.help_path(ee), ee) # the parse result -def go( # noqa: L001 IO +def go( +app: Shake.Cli, rr: Result<&2, &2, Shake.ParseErr, Shake.Matched> ) -> IO(Unit): @@ -132,7 +132,7 @@ def go( # noqa: L001 IO case Done{m}: run(m) -def main() -> IO(Unit): # noqa: L001 IO +def main() -> IO(Unit): do IO: +app : Shake.Cli <- IO.pure(Shake.Cli, spec()) av : List<&2, String> <- Shake.argv() diff --git a/ez.lock.toml b/ez.lock.toml index 04856b9..0245ee3 100644 --- a/ez.lock.toml +++ b/ez.lock.toml @@ -4,8 +4,8 @@ hub = "https://hub.bend-lang.com" [tools] [tools.bolt] git = "https://github.com/Emerging-Patterns/bolt" -rev = "7f5bb328187efa21598b4fed51cb1e154cf36330" -tag = "v1.19.0" +rev = "78fad9564b1a5b49f1e39bbd46a7fec45e615a4c" +tag = "v1.22.0" root = "." -narHash = "sha256-9d+ikveuzegJVH8nbEYSAxy8Pt0dYDyW4ISZml+MsNM=" +narHash = "sha256-OHkE75Urs5jTTU8Nygs2+enKYKOXeHOBDZ5t0ZwJVA4=" entry = "main.bend" diff --git a/ez.toml b/ez.toml index f65aa2e..ac0654b 100644 --- a/ez.toml +++ b/ez.toml @@ -4,8 +4,8 @@ entry = "main.bend" [tools] [tools.bolt] git = "https://github.com/Emerging-Patterns/bolt" -rev = "7f5bb328187efa21598b4fed51cb1e154cf36330" -tag = "v1.19.0" +rev = "78fad9564b1a5b49f1e39bbd46a7fec45e615a4c" +tag = "v1.22.0" root = "." -narHash = "sha256-9d+ikveuzegJVH8nbEYSAxy8Pt0dYDyW4ISZml+MsNM=" +narHash = "sha256-OHkE75Urs5jTTU8Nygs2+enKYKOXeHOBDZ5t0ZwJVA4=" entry = "main.bend" diff --git a/src/eq.bend b/src/eq.bend index f8421a4..a5f8549 100644 --- a/src/eq.bend +++ b/src/eq.bend @@ -10,7 +10,7 @@ law bool_cmp_self: {Bool.cmp(bb, bb) == EQ{} : Cmp} # the two bits, each compared with itself -def bool_cmp_self(bb): # noqa: L001 a proof of this file's law +def bool_cmp_self(bb): match bb: case False{}: {==} @@ -24,7 +24,7 @@ law word_cmp_self: {Word.cmp(nn, ww, ww) == EQ{} : Cmp} # by induction on the width: a word of no bits and a bit carried over one -def word_cmp_self(nn, ww): # noqa: L001 a proof of this file's law +def word_cmp_self(nn, ww): match nn: case 0n: match ww: @@ -45,7 +45,7 @@ law u32_cmp_self: {U32.cmp(uu, uu) == EQ{} : Cmp} # a number is a word of thirty-two bits, so the word's law answers for it -def u32_cmp_self(uu): # noqa: L001 a proof of this file's law +def u32_cmp_self(uu): match uu: case U32{+ww}: word_cmp_self(32n, ww) @@ -56,7 +56,7 @@ law char_cmp_self: {Char.cmp(cc, cc) == ((cc, cc), EQ{}) : (Char & Char) & Cmp} # a char is a code point, and the pair it is compared inside rides along -def char_cmp_self(cc): # noqa: L001 a proof of this file's law +def char_cmp_self(cc): match cc: case Chr{+xx}: Equal.cong(Cmp, (Char & Char) & Cmp, rr => ((Chr{xx}, Chr{xx}), rr), @@ -68,7 +68,7 @@ law char_eq_self: {Char.is_eq(cc, cc) == True{} : Bool} # a char is a code point, and equality is the comparison's verdict -def char_eq_self(cc): # noqa: L001 a proof of this file's law +def char_eq_self(cc): match cc: case Chr{+xx}: Equal.cong(Cmp, Bool, rr => Cmp.is_eq(rr), U32.cmp(xx, xx), EQ{}, u32_cmp_self(xx)) @@ -80,7 +80,7 @@ law string_cmp_self: # by induction on the string: the head's law and the tail's, composed the # way String.cmp composes them -def string_cmp_self(ss): # noqa: L001 a proof of this file's law +def string_cmp_self(ss): match ss: case SNil{}: {==} @@ -108,7 +108,7 @@ law string_order_self: # by induction on the string: the head's code point compares equal to itself, # so the walk goes on to the tail still equal -def string_order_self(ss): # noqa: L001 a proof of this file's law +def string_order_self(ss): match ss: case SNil{}: {==} @@ -122,7 +122,7 @@ def string_order_self(ss): # noqa: L001 a proof of this file's law string_order_self(tt)) # equality is the order's verdict read as a bit, so the order's law answers -def string_eq_self(ss): # noqa: L001 a proof of this file's law +def string_eq_self(ss): Equal.cong(Cmp, Bool, cc => Cmp.is_eq(cc), String.order.go(ss, ss, EQ{}), EQ{}, string_order_self(ss)) @@ -133,7 +133,7 @@ law string_order_lt: {String.order.go(aa, bb, LT{}) == LT{} : Cmp} # the walk only goes on while the order is equal -def string_order_lt(aa, bb): # noqa: L001 a proof of this file's law +def string_order_lt(aa, bb): match aa: case SNil{}: match bb: @@ -159,7 +159,7 @@ law string_order_gt: {String.order.go(aa, bb, GT{}) == GT{} : Cmp} # the walk only goes on while the order is equal -def string_order_gt(aa, bb): # noqa: L001 a proof of this file's law +def string_order_gt(aa, bb): match aa: case SNil{}: match bb: @@ -245,7 +245,7 @@ law cmp_is_eq: {c == EQ{} : Cmp} # the two other answers read as False -def cmp_is_eq(c, e): # noqa: L001 a proof of this file's law +def cmp_is_eq(c, e): match c: case LT{}: bit.absurd(&2, {LT{} == EQ{} : Cmp}, e) @@ -262,7 +262,7 @@ law bool_eq: {a == b : Bool} # the two bits that differ compare LT or GT -def bool_eq(a, b, e): # noqa: L001 a proof of this file's law +def bool_eq(a, b, e): match a b: case False{} False{}: {==} @@ -282,7 +282,7 @@ law fin_tail: {t == EQ{} : Cmp} # an answer of LT or GT from the tails is the answer, never EQ -def fin_tail(_ab, _bb, t, e): # noqa: L001 a proof of this file's law +def fin_tail(_ab, _bb, t, e): match t: case LT{}: bit.gone(&2, {LT{} == EQ{} : Cmp}, cmp.lt(e)) @@ -300,7 +300,7 @@ law fin_head: {Bool.cmp(ab, bb) == EQ{} : Cmp} # and an EQ from the tails leaves the heads to answer -def fin_head(ab, bb, t, e): # noqa: L001 a proof of this file's law +def fin_head(ab, bb, t, e): match t: case LT{}: bit.gone(&2, {Bool.cmp(ab, bb) == EQ{} : Cmp}, cmp.lt(e)) @@ -318,7 +318,7 @@ law word_eq: {a == b : Word(n)} # by induction on the width: the heads by the bit law, then the tails -def word_eq(n, a, b, e): # noqa: L001 a proof of this file's law +def word_eq(n, a, b, e): match n: case 0n: match a b: @@ -341,7 +341,7 @@ law u32_eq: {Chr{x} == Chr{y} : Char} # a code point is a word of thirty-two bits -def u32_eq(x, y, e): # noqa: L001 a proof of this file's law +def u32_eq(x, y, e): match x y: case U32{+wx} U32{+wy}: Equal.cong(Word(32n), Char, w => Chr{U32{w}}, wx, wy, @@ -355,7 +355,7 @@ law char_eq_true: {a == b : Char} # a char is a code point -def char_eq_true(a, b, e): # noqa: L001 a proof of this file's law +def char_eq_true(a, b, e): match a b: case Chr{x} Chr{y}: u32_eq(x, y, e) @@ -370,7 +370,7 @@ law eq_fin_rec: == Cmp.is_eq(Pair.snd(String & String, Cmp, r)) : Bool} # the comparison, taken apart and put back together -def eq_fin_rec(_h1, _h2, r): # noqa: L001 a proof of this file's law +def eq_fin_rec(_h1, _h2, r): match r: case (ts, c): match ts: @@ -440,7 +440,7 @@ def cmp_tail.cmp(x, y, t1, t2, c, _ec, e): bit.absurd(&2, {Cmp.is_eq(Pair.snd(String & String, Cmp, String.cmp(t1, t2))) == True{} : Bool}, e) # by induction on both strings, as string_eq_true goes over String.eq -def cmp_true(a, b, e): # noqa: L001 a proof of this file's law +def cmp_true(a, b, e): match a b: case SNil{} SNil{}: {==} @@ -484,7 +484,7 @@ law order_start: {c == EQ{} : Cmp} # every arm the walk takes on a start other than EQ answers that start -def order_start(a, b, c, e): # noqa: L001 a proof of this file's law +def order_start(a, b, c, e): match a b: case SNil{} SNil{}: cmp_is_eq(c, e) @@ -525,7 +525,7 @@ law eq_head: # the walk goes on to the tails from the heads' comparison, which must have # been EQ for the walk to end equal -def eq_head(h1, h2, t1, t2, e): # noqa: L001 a proof of this file's law +def eq_head(h1, h2, t1, t2, e): match h1 h2: case Chr{+x} Chr{+y}: u32_eq(x, y, Equal.cong(Cmp, Bool, k => Cmp.is_eq(k), U32.cmp(x, y), EQ{}, @@ -541,7 +541,7 @@ law eq_tail: {String.eq(t1, t2) == True{} : Bool} # and the walk over the tails started from that EQ -def eq_tail(h1, h2, t1, t2, e): # noqa: L001 a proof of this file's law +def eq_tail(h1, h2, t1, t2, e): match h1 h2: case Chr{+x} Chr{+y}: Equal.trans(Bool, String.eq(t1, t2), @@ -559,7 +559,7 @@ law string_eq_true: # by induction on both strings: two of different lengths never read as # equal, and two with a first char each have one head and equal tails -def string_eq_true(a, b, e): # noqa: L001 a proof of this file's law +def string_eq_true(a, b, e): match a b: case SNil{} SNil{}: {==} diff --git a/src/grow.bend b/src/grow.bend index 9dcb351..7072c0b 100644 --- a/src/grow.bend +++ b/src/grow.bend @@ -11,7 +11,7 @@ import ./cli.bend as Shake import ./eq.bend as Eq # the bindings a walker holds for its current command -def binds_of(st: Shake.St) -> List<&2, Shake.Bind>: # noqa: L001 proof machinery +def binds_of(st: Shake.St) -> List<&2, Shake.Bind>: Shake.St{_m, _a, _g, _s, _p, bb, _o, _r, _n} = st bb # `big` is `small` with some bindings put in front @@ -635,7 +635,7 @@ def gw.Stuck(big: Shake.St) -> Type: {gw.live(big) == False{} : Bool} # the three things a word does to a walker -type Moved<-A: Type, -B: Type, -C: Type> is Type: # noqa: L001 proof machinery: what a step did, for the walker proofs +type Moved<-A: Type, -B: Type, -C: Type> is Type: Stays{proof: A} Enters{proof: B} Stops{proof: C} @@ -1228,7 +1228,7 @@ def gw.stuck_walk(more, st, hh): gw.stuck_walk(tt, Shake.parse.step(ww, st), gw.stuck_step(ww, st, hh)) # a walk either grows the walker or leaves it stuck -type Went<-A: Type, -B: Type> is Type: # noqa: L001 proof machinery: what a walk did, for the walker proofs +type Went<-A: Type, -B: Type> is Type: Grew{proof: A} Stopped{proof: B} diff --git a/src/walk.bend b/src/walk.bend index 10527c6..9e37f76 100644 --- a/src/walk.bend +++ b/src/walk.bend @@ -17,7 +17,7 @@ def all_plain(ws: List<&2, String>) -> Bool: case Nil{}: True{} case Con{+ww, rest}: - Bool.and(plain_word(ww), all_plain(rest)) # noqa: U002 a law premise, never run; the proofs read Bool.and + Bool.and(plain_word(ww), all_plain(rest)) # Unit for False, Empty for True: a motive that tells them apart def wk.false_ty(bb: Bool) -> Type: @@ -228,7 +228,7 @@ def wk.unfound(+subs: List<&2, Shake.Sub>, ws: List<&2, String>) -> Bool: True{} case Con{+ww, rest}: Bool.and(Maybe.is_none(&2, Shake.Sub, Shake.find_sub(subs, ww)), - wk.unfound(subs, rest)) # noqa: U002 a law premise, never run + wk.unfound(subs, rest)) # a word that is not `--`, not `help`, not a flag and no subcommand here # reaches the positional it binds