diff --git a/LAWS.bend b/LAWS.bend new file mode 100644 index 0000000..97897ff --- /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. 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 +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/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/SPEC.md b/docs/SPEC.md similarity index 98% rename from SPEC.md rename to docs/SPEC.md index 2748b77..134160a 100644 --- a/SPEC.md +++ b/docs/SPEC.md @@ -10,7 +10,7 @@ The words `parse` receives are what the program passes it. In a compiled program 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). +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 @@ -111,7 +111,7 @@ A tag may name a proved or a pending requirement, never a Trusted one or an ID n ## 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. +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 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/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 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