diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aab0d90..c63bee1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,3 +12,8 @@ on: jobs: check: uses: Emerging-Patterns/actions/.github/workflows/nix-flake-check.yml@5b0aa4f0eaee72662e06479ecebcfd15e9762fe7 + with: + # the shared default still names macos-14, which GitHub retires on + # 2026-11-02 and fails in brownouts before then (no runner is assigned); + # macos-15 is arm64 too, so aarch64-darwin is still checked + runners: '["ubuntu-latest", "ubuntu-24.04-arm", "macos-15"]' diff --git a/AGENTS.md b/AGENTS.md index ee5c69a..dfa5944 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,8 @@ writing Bend. Then this. If Bend can state it as a quantified law (a `for` or `exs` binder, IO values included), it goes in a law file under `laws/` and its proof under `proofs/`. A law with no -binder is a unit test the checker runs, not a guarantee: `closed` reports +binder is a unit test the checker runs, not a guarantee: `closed` (opt-in, +on in this repo's bolt.bend) reports it, and it does not go in at all (docs/rfc/bolt-spec.md). `tests/*.bend` and `#|` exist only for claims Bend cannot prove: @@ -174,7 +175,7 @@ Design specs and plans are not kept in this repo; they live under it) stay right for expensive work that does not recurse. The same goes for any expensive expression in a branch: a scan of the whole token list inside a per-token pick runs for every token (bind's notes were 20 s that way, - 20 ms as one pass). `bolt`'s `strict` rule catches the Bool.and/or shape. + 20 ms as one pass). `bolt`'s `strict` rule (pedantic) catches the Bool.and/or shape. - A destructure or a `match` needs a variable, never a call: `Out{a, b} = f(x)` is "a match cannot scrutinize a computed value"; bind the call first, or take it apart in a helper that receives it as a parameter. diff --git a/LAWS.bend b/LAWS.bend index 688cad8..94fb8d5 100644 --- a/LAWS.bend +++ b/LAWS.bend @@ -76,19 +76,18 @@ import ./laws/ForeignReportsMissingBackend.bend as ForeignReportsMissingBackend import ./laws/FromrevExempt.bend as FromrevExempt import ./laws/FromrevReportsReversedStringBuild.bend as FromrevReportsReversedStringBuild import ./laws/FuelReportsLiteralBudgets.bend as FuelReportsLiteralBudgets -import ./laws/HoistReportsTablesInLoops.bend as HoistReportsTablesInLoops import ./laws/HoleReportsEachTodo.bend as HoleReportsEachTodo import ./laws/IndexReportsGetsInRecursion.bend as IndexReportsGetsInRecursion import ./laws/InertConcatCont.bend as InertConcatCont import ./laws/InertFromrev.bend as InertFromrev import ./laws/InertTranscong.bend as InertTranscong +import ./laws/ImportReachIsClosed.bend as ImportReachIsClosed import ./laws/LawNamesEveryItem.bend as LawNamesEveryItem import ./laws/ParamReportsShortNames.bend as ParamReportsShortNames import ./laws/PickReportsSelfCalls.bend as PickReportsSelfCalls import ./laws/PickReportsBoundSearches.bend as PickReportsBoundSearches import ./laws/PutReportsMapPutCalls.bend as PutReportsMapPutCalls import ./laws/RewalkReportsRepeatedWalks.bend as RewalkReportsRepeatedWalks -import ./laws/RingReportsDropAppendRotation.bend as RingReportsDropAppendRotation import ./laws/RoleClosed.bend as RoleClosed import ./laws/RoleClosedExempt.bend as RoleClosedExempt import ./laws/RoleConcat.bend as RoleConcat @@ -97,7 +96,6 @@ import ./laws/RoleDocExempt.bend as RoleDocExempt import ./laws/RoleEager.bend as RoleEager import ./laws/RoleFromrev.bend as RoleFromrev import ./laws/RoleFromrevExempt.bend as RoleFromrevExempt -import ./laws/RoleHoist.bend as RoleHoist import ./laws/RoleHole.bend as RoleHole import ./laws/RoleHoleExempt.bend as RoleHoleExempt import ./laws/RoleIndex.bend as RoleIndex @@ -110,27 +108,18 @@ import ./laws/RolePick.bend as RolePick import ./laws/RoleProofNamed.bend as RoleProofNamed import ./laws/RoleReached.bend as RoleReached import ./laws/RoleRewalk.bend as RoleRewalk -import ./laws/RoleRing.bend as RoleRing -import ./laws/RoleScan.bend as RoleScan -import ./laws/RoleScanExempt.bend as RoleScanExempt import ./laws/RoleStrict.bend as RoleStrict -import ./laws/RoleTable.bend as RoleTable import ./laws/RoleTail.bend as RoleTail import ./laws/RoleThunk.bend as RoleThunk -import ./laws/RoleUnit.bend as RoleUnit -import ./laws/ScanReportsCarriedArgumentWalks.bend as ScanReportsCarriedArgumentWalks import ./laws/ScopeConcatCont.bend as ScopeConcatCont import ./laws/ScopeExemptionsFollowPaths.bend as ScopeExemptionsFollowPaths import ./laws/SpaceReportsBlanksAndWidth.bend as SpaceReportsBlanksAndWidth import ./laws/StrictReportsEagerSelfCalls.bend as StrictReportsEagerSelfCalls import ./laws/StringsReportLongLiteralArms.bend as StringsReportLongLiteralArms -import ./laws/TableReportsFixedTableAccess.bend as TableReportsFixedTableAccess import ./laws/TailReportsNonTailCalls.bend as TailReportsNonTailCalls import ./laws/ThunkReportsIgnoredSelfCallLambdas.bend as ThunkReportsIgnoredSelfCallLambdas import ./laws/TranscongReportsTransCongChains.bend as TranscongReportsTransCongChains import ./laws/TwiceReportsRepeatedListLiteral.bend as TwiceReportsRepeatedListLiteral -import ./laws/UnitReportsMultiplyByOne.bend as UnitReportsMultiplyByOne -import ./laws/UnsafeReportsReachableForeignDefs.bend as UnsafeReportsReachableForeignDefs import ./laws/UnusedReportsDeadBinders.bend as UnusedReportsDeadBinders import ./laws/WrapReportsBrokenHeaderShape.bend as WrapReportsBrokenHeaderShape import ./laws/LspAnswersEachRequestOnce.bend as LspAnswersEachRequestOnce @@ -140,6 +129,7 @@ import ./laws/LspFramesCountBytes.bend as LspFramesCountBytes import ./laws/LspNavigationUsesBinder.bend as LspNavigationUsesBinder import ./laws/LspPositionsUseNegotiatedEncoding.bend as LspPositionsUseNegotiatedEncoding import ./laws/LspPublishesOnDocumentEvents.bend as LspPublishesOnDocumentEvents +import ./laws/LspKeepsRoleMarks.bend as LspKeepsRoleMarks import ./laws/LspUnitsMatchCharsBelowAstral.bend as LspUnitsMatchCharsBelowAstral import ./laws/ArrowTerm.bend as ArrowTerm import ./laws/AttributeDef.bend as AttributeDef diff --git a/PROOF.bend b/PROOF.bend index 90d84bb..5a1364c 100644 --- a/PROOF.bend +++ b/PROOF.bend @@ -61,19 +61,18 @@ import ./proofs/ForeignReportsMissingBackend.bend as PForeignReportsMissingBacke import ./proofs/FromrevExempt.bend as PFromrevExempt import ./proofs/FromrevReportsReversedStringBuild.bend as PFromrevReportsReversedStringBuild import ./proofs/FuelReportsLiteralBudgets.bend as PFuelReportsLiteralBudgets -import ./proofs/HoistReportsTablesInLoops.bend as PHoistReportsTablesInLoops import ./proofs/HoleReportsEachTodo.bend as PHoleReportsEachTodo import ./proofs/IndexReportsGetsInRecursion.bend as PIndexReportsGetsInRecursion import ./proofs/InertConcatCont.bend as PInertConcatCont import ./proofs/InertFromrev.bend as PInertFromrev import ./proofs/InertTranscong.bend as PInertTranscong +import ./proofs/ImportReachIsClosed.bend as PImportReachIsClosed import ./proofs/LawNamesEveryItem.bend as PLawNamesEveryItem import ./proofs/ParamReportsShortNames.bend as PParamReportsShortNames import ./proofs/PickReportsSelfCalls.bend as PPickReportsSelfCalls import ./proofs/PickReportsBoundSearches.bend as PPickReportsBoundSearches import ./proofs/PutReportsMapPutCalls.bend as PPutReportsMapPutCalls import ./proofs/RewalkReportsRepeatedWalks.bend as PRewalkReportsRepeatedWalks -import ./proofs/RingReportsDropAppendRotation.bend as PRingReportsDropAppendRotation import ./proofs/RoleClosed.bend as PRoleClosed import ./proofs/RoleClosedExempt.bend as PRoleClosedExempt import ./proofs/RoleConcat.bend as PRoleConcat @@ -82,7 +81,6 @@ import ./proofs/RoleDocExempt.bend as PRoleDocExempt import ./proofs/RoleEager.bend as PRoleEager import ./proofs/RoleFromrev.bend as PRoleFromrev import ./proofs/RoleFromrevExempt.bend as PRoleFromrevExempt -import ./proofs/RoleHoist.bend as PRoleHoist import ./proofs/RoleHole.bend as PRoleHole import ./proofs/RoleHoleExempt.bend as PRoleHoleExempt import ./proofs/RoleIndex.bend as PRoleIndex @@ -95,27 +93,18 @@ import ./proofs/RolePick.bend as PRolePick import ./proofs/RoleProofNamed.bend as PRoleProofNamed import ./proofs/RoleReached.bend as PRoleReached import ./proofs/RoleRewalk.bend as PRoleRewalk -import ./proofs/RoleRing.bend as PRoleRing -import ./proofs/RoleScan.bend as PRoleScan -import ./proofs/RoleScanExempt.bend as PRoleScanExempt import ./proofs/RoleStrict.bend as PRoleStrict -import ./proofs/RoleTable.bend as PRoleTable import ./proofs/RoleTail.bend as PRoleTail import ./proofs/RoleThunk.bend as PRoleThunk -import ./proofs/RoleUnit.bend as PRoleUnit -import ./proofs/ScanReportsCarriedArgumentWalks.bend as PScanReportsCarriedArgumentWalks import ./proofs/ScopeConcatCont.bend as PScopeConcatCont import ./proofs/ScopeExemptionsFollowPaths.bend as PScopeExemptionsFollowPaths import ./proofs/SpaceReportsBlanksAndWidth.bend as PSpaceReportsBlanksAndWidth import ./proofs/StrictReportsEagerSelfCalls.bend as PStrictReportsEagerSelfCalls import ./proofs/StringsReportLongLiteralArms.bend as PStringsReportLongLiteralArms -import ./proofs/TableReportsFixedTableAccess.bend as PTableReportsFixedTableAccess import ./proofs/TailReportsNonTailCalls.bend as PTailReportsNonTailCalls import ./proofs/ThunkReportsIgnoredSelfCallLambdas.bend as PThunkReportsIgnoredSelfCallLambdas import ./proofs/TranscongReportsTransCongChains.bend as PTranscongReportsTransCongChains import ./proofs/TwiceReportsRepeatedListLiteral.bend as PTwiceReportsRepeatedListLiteral -import ./proofs/UnitReportsMultiplyByOne.bend as PUnitReportsMultiplyByOne -import ./proofs/UnsafeReportsReachableForeignDefs.bend as PUnsafeReportsReachableForeignDefs import ./proofs/UnusedReportsDeadBinders.bend as PUnusedReportsDeadBinders import ./proofs/WrapReportsBrokenHeaderShape.bend as PWrapReportsBrokenHeaderShape import ./proofs/LspAnswersEachRequestOnce.bend as PLspAnswersEachRequestOnce @@ -125,6 +114,7 @@ import ./proofs/LspFramesCountBytes.bend as PLspFramesCountBytes import ./proofs/LspNavigationUsesBinder.bend as PLspNavigationUsesBinder import ./proofs/LspPositionsUseNegotiatedEncoding.bend as PLspPositionsUseNegotiatedEncoding import ./proofs/LspPublishesOnDocumentEvents.bend as PLspPublishesOnDocumentEvents +import ./proofs/LspKeepsRoleMarks.bend as PLspKeepsRoleMarks import ./proofs/LspUnitsMatchCharsBelowAstral.bend as PLspUnitsMatchCharsBelowAstral import ./proofs/ArrowTerm.bend as PArrowTerm import ./proofs/AttributeDef.bend as PAttributeDef diff --git a/README.md b/README.md index 87ca8b6..37bb4a2 100644 --- a/README.md +++ b/README.md @@ -154,8 +154,10 @@ def space() -> String: ``` The groups are `correctness` (`hole`, `pick`, `put`; error by default), -`suspicious` (`unused`), `style` (`doc`, `space`, `wrap`, `param`) and `laws` (`coverage`), the -rest warn by default. Every rule, and the config in full, is in +`suspicious` (`unused`), `perf` (`strings`, `chars`), `style` (`doc`, `space`, +`wrap`, `param`) and `laws`, the rest warn by default, and `pedantic` (`tail`, +`strict`, `index`), off by default. The laws rules, `coverage` and `closed`, +are opt-in: a project that keeps laws turns each on by name. Every rule, and the config in full, is in [src/README.md](src/README.md). ## Develop diff --git a/bolt.bend b/bolt.bend index 3fbfa79..63358d6 100644 --- a/bolt.bend +++ b/bolt.bend @@ -1,5 +1,6 @@ # bolt.bend: how bolt lints this repo. Every rule is an error except `coverage`, -# which warns until the laws exist: the gate must see no errors. See +# which warns until the laws exist: the gate must see no errors. The opt-in +# and pedantic rules are turned on by name or group below. See # src/config.bend for the groups and levels. import Base @@ -11,20 +12,29 @@ def correctness() -> String: def suspicious() -> String: "error" +# what is slow but right +def perf() -> String: + "error" + # comments and whitespace def style() -> String: "error" +# advice that is noisy on idiomatic code, held here all the same: foreign, +# strict, fuel, index and rewalk were errors here before they moved +def pedantic() -> String: + "error" + +# but not a tail call on every step +def tail() -> String: + "off" + # a def named by no law, IO included: advice while guarantees are pending, # since deleting the closed laws left defs no law names. That rule -# is `coverage` (L001), which `def coverage()` could also set on its own. -def laws() -> String: +# is `coverage` (L001), opt-in, so only its own setting turns it on. +def coverage() -> String: "warn" -# a law with no binder +# a law with no binder (opt-in) def closed() -> String: "error" - -# an @unsafe def a law file reaches -def unsafe() -> String: - "error" diff --git a/laws/CharsReportManyCharArms.bend b/laws/CharsReportManyCharArms.bend index 204fa94..2b061d2 100644 --- a/laws/CharsReportManyCharArms.bend +++ b/laws/CharsReportManyCharArms.bend @@ -7,7 +7,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/correctness/chars.bend as Chars +import ../src/rules/perf/chars.bend as Chars import ./defs/MatchArmRules.bend as D1 # one when a case statement's first match column opens with a char literal, diff --git a/laws/CodeRulesIgnoreCommentsAndStrings.bend b/laws/CodeRulesIgnoreCommentsAndStrings.bend index 5bb81be..af9d7c6 100644 --- a/laws/CodeRulesIgnoreCommentsAndStrings.bend +++ b/laws/CodeRulesIgnoreCommentsAndStrings.bend @@ -20,18 +20,13 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/suspicious/thunk.bend as Thunk -import ../src/rules/suspicious/scan.bend as Scan import ../src/rules/tokens.bend as T import ../src/rules/correctness/arms.bend as Arms import ../src/rules/suspicious/unused.bend as Unused -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule import ../src/rules/style/wrap.bend as Wrap import ./defs/InertText.bend as D1 @@ -220,27 +215,6 @@ law inert_param: for e: {inert.binds(b1) == inert.binds(b2) : List<&2, Bind.Bind>} {Param.check.on(b1, path) == Param.check.on(b2, path) : List<&2, F.Finding>} -# LAW: table reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings (a -# string's text is only ever compared with a call's or a table's name) -law inert_table: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Table.check(Src.Src{path, text, toks, tree, bound, items}) - == Table.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # LAW: index reads what no string says: two sources as the lexer makes them # whose trees read the same with strings cut have the same findings law inert_index: @@ -281,26 +255,6 @@ law inert_concat: {Concat.check(Src.Src{path, text, toks, tree, bound, items}) == Concat.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} -# LAW: unit reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings -law inert_unit: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {UnitRule.check(Src.Src{path, text, toks, tree, bound, items}) - == UnitRule.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # LAW: thunk reads what no string says: two sources as the lexer makes them # whose trees read the same with strings cut have the same findings law inert_thunk: @@ -361,26 +315,6 @@ law inert_fuel: {FuelRule.check(Src.Src{path, text, toks, tree, bound, items}) == FuelRule.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} -# LAW: ring reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings -law inert_ring: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Ring.check(Src.Src{path, text, toks, tree, bound, items}) - == Ring.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # LAW: arms reads what no string says: two sources as the lexer makes them # whose trees read the same with strings cut have the same findings law inert_arms: @@ -440,48 +374,6 @@ law inert_unused: == Unused.check(Src.Src{path, text2, toks2, tree2, Bind.Bound{binds2, uses, scopes2}, items2}) : List<&2, F.Finding>} -# LAW: hoist reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings (a -# string's text is only ever compared with a name, a keyword or a mark) -law inert_hoist: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Hoist.check(Src.Src{path, text, toks, tree, bound, items}) - == Hoist.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - -# LAW: scan reads what no comment or string says: two sources as the lexer -# makes them whose trees read the same with comments and strings cut have -# the same findings -law inert_scan: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Scan.check(Src.Src{path, text, toks, tree, bound, items}) - == Scan.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # the tokens of each item's header, with comments and strings cut def inert.sigs(its: List<&2, Outline.Item>) -> List<&2, List<&2, Lex.Tok>>: match its: diff --git a/laws/ExemptPathsYieldNoFindings.bend b/laws/ExemptPathsYieldNoFindings.bend index d238fad..f1f91ef 100644 --- a/laws/ExemptPathsYieldNoFindings.bend +++ b/laws/ExemptPathsYieldNoFindings.bend @@ -1,7 +1,6 @@ # For every text, a per-file rule's check on a path its header exempts returns # no findings. The cost rules (`pick`, `strict`, `eager`, `tail`, `concat`, -# `index`, `table`, `hoist`, `ring`, `rewalk`, `unit`, `scan`, `thunk`) also -# skip, wherever it is, every def that is a proof: one whose signature returns +# `index`, `rewalk`, `thunk`) also skip, wherever it is, every def that is a proof: one whose signature returns # a proof (`-> {a == b : T}`), or one written with no type at all (no `:` # among its parameters and no `->`, as `def f(x, y):`), which is how Bend # fills the law named f; their "reports exactly" rows are read with that skip. @@ -22,15 +21,10 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/rewalk.bend as Rewalk -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/rewalk.bend as Rewalk +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/suspicious/thunk.bend as Thunk -import ../src/rules/suspicious/scan.bend as Scan import ../src/rules/calls.bend as Calls # The header exemptions. A per-file rule whose header exempts a path (a law @@ -138,17 +132,6 @@ law strict_exempt: for e: {Paths.is_law_file(path) == True{} : Bool} {Strict.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} -# LAW: hoist reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law hoist_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Hoist.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - # LAW: index reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds law index_exempt: for +path: String @@ -160,39 +143,6 @@ law index_exempt: for e: {Paths.is_law_file(path) == True{} : Bool} {Index.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} -# LAW: ring reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law ring_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Ring.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - -# LAW: table reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law table_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Table.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - -# LAW: unit reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law unit_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {UnitRule.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - # LAW: thunk reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds law thunk_exempt: for +path: String @@ -204,17 +154,6 @@ law thunk_exempt: for e: {Paths.is_law_file(path) == True{} : Bool} {Thunk.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} -# LAW: scan reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law scan_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Scan.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - # LAW: a def written with no type (no `:` among its parameters, no `->`) fills the law of its name, so it is a # proof: every cost rule that reads Calls.exempt skips it wherever it is, as it skips one that returns a proof law untyped_exempt: diff --git a/laws/ForeignReportsMissingBackend.bend b/laws/ForeignReportsMissingBackend.bend index ad60b25..6863e71 100644 --- a/laws/ForeignReportsMissingBackend.bend +++ b/laws/ForeignReportsMissingBackend.bend @@ -8,7 +8,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/correctness/foreign.bend as Foreign +import ../src/rules/pedantic/foreign.bend as Foreign # foreign (laws/ForeignReportsMissingBackend.bend). Over the file's top-level statements the rule # reports each def whose body is one import or more and nothing else (a diff --git a/laws/FuelReportsLiteralBudgets.bend b/laws/FuelReportsLiteralBudgets.bend index 42cbe3c..aea8a5d 100644 --- a/laws/FuelReportsLiteralBudgets.bend +++ b/laws/FuelReportsLiteralBudgets.bend @@ -14,7 +14,7 @@ import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline import ../src/rules/calls.bend as Calls import ../src/rules/tokens.bend as T -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule # fuel (laws/FuelReportsLiteralBudgets.bend). The rule lists the file's fuel parameters: for each # def of the file (Calls.defs) that returns no effect, each parameter diff --git a/laws/HoistReportsTablesInLoops.bend b/laws/HoistReportsTablesInLoops.bend deleted file mode 100644 index 6970ed8..0000000 --- a/laws/HoistReportsTablesInLoops.bend +++ /dev/null @@ -1,154 +0,0 @@ -# `hoist` reports exactly, in a def that calls itself and is not a law or a -# proof, outside a lambda's body (the rest of a chain after `=>`), a case -# pattern, and a case arm that does not call the def: a `List.get`, -# `List.set`, `String.get`, `Array.get` or `Array.set` call whose collection -# is a table, and a let of one plain name to a table that a later get, set or -# `name[..]` in its block or the statements after it indexes; where a table is -# a list literal with eight or more top-level commas, an array literal of more -# than eight slots (`[v : T*n]` with n not a literal 0 to 8, `[v : T^d]` with -# d not a literal 0 to 3), a `List.replicate`, `Array.new` or `List.range` -# whose count is not a literal 0 to 8, a `List.map` or `Array.map`, or a call -# to a def of the file, other than an undotted self-call, whose body is a -# single statement that is a list literal, or an array literal, -# `List.replicate`, `Array.new` or `List.range` sized by a number literal, of -# more than eight cells by those measures; and every value name in the table -# (a callee or a `~` template aside) is a parameter that each self-call passes -# back unchanged in its own position. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/calls.bend as Calls -import ../src/rules/tokens.bend as T - -# The recursion rule, hoist. It reads each def of the file whose body calls -# its own name and that is not a law or a proof, and walks that body as -# chains: a `=>` token makes the rest of its chain cold, a case's pattern is -# cold, and a case arm's body stays hot only when it calls the def; the rest -# keeps the heat it came in with, and the def's body starts hot. On a hot -# path it reports each call, a name token then a `(` group, that is a -# `List.get` / `List.set` (collection argument 2), `String.get` (0) or -# `Array.get` / `Array.set` (1) whose collection is a table, and each -# statement that is not a case, whose tokens before its first `=` hold exactly -# one lowercase name and no constructor or group, whose right side is a -# table, and whose name is indexed (`name[..]`, or a get or set's collection, -# bare or in parentheses) anywhere in its own block or the statements after -# it. A table is the rule's own recognizer (`is_table`): a list literal with -# eight or more top-level commas, an array literal `[v : T*n]` or `[v : T^d]` -# whose size is not a literal 0 to 8 after `*` or 0 to 3 after `^` (2^d -# slots), a `List.replicate` / `Array.new` / `List.range` whose count is not -# such a literal, a `List.map` / `Array.map`, or a call to a def of the file -# (not an undotted self-call) whose body is one constant-size table of that -# width (`wide_defs`); in every case -# with each value name under it (callees, `~` templates, dotted names and -# types aside) a parameter that every self-call passes back as that same lone -# name in its own position. - -# does a get or set's collection argument, at its slot, hold a table? -def hoist.slot( - ss: Hoist.Slot, - +as: List<&2, Tree.Node>, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - match ss: - case Hoist.Slot{on, at}: - Bool.and(on, Hoist.is_table(Calls.arg(as, at), self, wides, carried)) - -# does a call site report: hot, a `(` group, and a get or set on a table -def hoist.call( - +hot: Bool, - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - Bool.and(hot, Bool.and(open, hoist.slot(Hoist.slot_of(tt), Calls.args(kids), self, wides, carried))) - -# a plain let's name and right side: a table, and the name indexed afterwards -def hoist.got( - mm: Maybe<&2, Hoist.Got>, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - match mm: - case None{}: - False{} - case Some{Hoist.Got{+nm, +expr}}: - Bool.and(Hoist.is_table(expr, self, wides, carried), Hoist.uses_or(body, rest, nm)) - -# does a statement report: hot, and a plain let of a table indexed afterwards -def hoist.let( - +hot: Bool, - kids: Tree.Node, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - Bool.and(hot, hoist.got(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried)) - -# how many calls and lets hoist reports under the node, entered hot or cold -def hoist.count( - nn: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +hot: Bool -) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = Hoist.warm(hot, k) - Nat.add(Bool.pick(Nat, hoist.call(heat, String.eq(o, "("), t, kids, self, wides, carried), 1n, 0n), - Nat.add(hoist.count(kids, self, wides, carried, heat), hoist.count(rest, self, wides, carried, heat))) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - hoist.count(rest, self, wides, carried, Hoist.warm(hot, k)) - case Tree.NCons{Tree.Group{o, kids, cl}, rest}: - Nat.add(hoist.count(kids, self, wides, carried, hot), hoist.count(rest, self, wides, carried, hot)) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - Nat.add(hoist.count(kids, self, wides, carried, False{}), - Nat.add(hoist.count(body, self, wides, carried, Bool.and(hot, Calls.calls(body, self))), - hoist.count(rest, self, wides, carried, hot))) - case Tree.NCons{Tree.Stmt{kind, +kids, +body}, +rest}: - Nat.add(Bool.pick(Nat, hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n), - Nat.add(hoist.count(kids, self, wides, carried, hot), - Nat.add(hoist.count(body, self, wides, carried, hot), hoist.count(rest, self, wides, carried, hot)))) - case Tree.NCons{h, rest}: - hoist.count(rest, self, wides, carried, hot) - case other: - 0n - -# how many hoist reports over the defs: those in each def that calls itself -# and is not a law or a proof, walked hot, with its carried parameters -def hoist.total(ds: List<&2, Calls.Def>, +wides: List<&2, String>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - hoist.count(body, name, wides, Hoist.carried(sig, body, name), True{}), 0n), hoist.total(rest, wides, path)) - -# LAW: hoist reports one finding for each call and each let the walk above -# counts, in each def that calls itself and is not a law or a proof, with -# the file's defs whose body is one wide constant-size table as builders; -# none for anything else -law hoist_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, Hoist.check(Src.Src{path, text, toks, tree, bound, items})) - == hoist.total(Calls.defs(tree), Hoist.wide_defs(Calls.defs(tree), []), path) : Nat} diff --git a/laws/ImportReachIsClosed.bend b/laws/ImportReachIsClosed.bend new file mode 100644 index 0000000..6ec5d33 --- /dev/null +++ b/laws/ImportReachIsClosed.bend @@ -0,0 +1,36 @@ +# What a file reaches by relative imports through the run, the walk the +# coverage rule takes over the laws' graph (laws/LawNamesEveryItem.bend), +# holds the file, is shut under imports, and is the least list that is: the +# walk's fuel never cuts it short, and it reaches only files a chain of +# relative imports leads to. +import Base +import ../src/rules/digest.bend as Digest +import ../src/rules/imports.bend as Imports +import ./defs/Unsafe.bend as D1 + +# LAW: over every list of digests, what the file at from reaches holds it +law unsafe_from: + for +ds: List<&2, Digest.Digest> + for +from: String + {Imports.has(D1.unsafe.closure(ds, from), from) == True{} : Bool} + +# LAW: over every list of digests, what a file reaches is shut: every file in +# the run that one of its files imports is in it. So the walk's fuel never +# cuts it short +law unsafe_shut: + for +ds: List<&2, Digest.Digest> + for +from: String + {D1.unsafe.shut(ds, D1.unsafe.closure(ds, from), D1.unsafe.closure(ds, from)) == True{} : Bool} + +# LAW: and it is the least such list: every list that holds from and is shut +# holds each file it reaches, so it reaches only files a chain of relative +# imports through the run leads to +law unsafe_least: + for +ds: List<&2, Digest.Digest> + for +from: String + for +ss: List<&2, String> + for +pp: String + for hf: {Imports.has(ss, from) == True{} : Bool} + for hs: {D1.unsafe.shut(ds, ss, ss) == True{} : Bool} + for hp: {Imports.has(D1.unsafe.closure(ds, from), pp) == True{} : Bool} + {Imports.has(ss, pp) == True{} : Bool} diff --git a/laws/IndexReportsGetsInRecursion.bend b/laws/IndexReportsGetsInRecursion.bend index 652af80..4b5c8a8 100644 --- a/laws/IndexReportsGetsInRecursion.bend +++ b/laws/IndexReportsGetsInRecursion.bend @@ -1,6 +1,6 @@ # `index` reports exactly a `List.get` or `String.get` at a non-literal index -# anywhere in a recursive def, except a `List.get` on a fixed table that -# `table` reports and a get that is the def's own self-call (the def is named +# anywhere in a recursive def, except a `List.get` on a fixed table +# (src/rules/table.bend) and a get that is the def's own self-call (the def is named # `List.get` or `String.get`). import Base import ../src/syntax/lex.bend as Lex @@ -9,16 +9,16 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/table.bend as Table +import ../src/rules/pedantic/index.bend as Index +import ../src/rules/table.bend as Table import ../src/rules/calls.bend as Calls # The recursion rule, index. It reads each def of the file whose body calls # its own name and that is not a law or a proof, and every call in that body # at any depth: a name token, then a group. A `String.get(..)` whose index # (argument 1) is not one number token is a finding. So is a `List.get(..)` -# whose index (argument 3) is not one number token, unless `table` reports -# that same call: its fixed-table test, over the tables it finds in that def. +# whose index (argument 3) is not one number token, unless src/rules/table.bend +# calls it a get on a fixed table, over the tables it finds in that def. # A call whose name is the def's own (a def named `List.get` or `String.get` # stepping to itself) is neither. @@ -32,7 +32,8 @@ def index.lit(nn: Tree.Node) -> Bool: # does a call report, given whether its name is List.get (list) or # String.get (sget), whether its group opens with `(` (ob), its arguments, -# and what `table` reports for it? +# and what src/rules/table.bend makes of it (a finding when it is a fixed +# table's)? def index.fires(list: Bool, +ob: Bool, +sget: Bool, +as: List<&2, Tree.Node>, table: List<&2, F.Finding>) -> Bool: match list: case True{}: @@ -61,7 +62,7 @@ def index.site( 0n # how many calls at any depth of the node index reports, fixed the tables -# `table` sees, in the def named self +# src/rules/table.bend sees, in the def named self def index.count(nn: Tree.Node, +path: String, +fixed: List<&2, String>, +self: String) -> Nat: match nn: case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, +l, +c}}, +rest}: @@ -77,7 +78,7 @@ def index.count(nn: Tree.Node, +path: String, +fixed: List<&2, String>, +self: S 0n # how many calls index reports over the defs: those in each def that calls -# itself and is not a law or a proof, with the fixed tables `table` sees in it +# itself and is not a law or a proof, with the fixed tables it holds def index.total(ds: List<&2, Calls.Def>, +mods: List<&2, String>, +path: String) -> Nat: match ds: case Nil{}: @@ -89,7 +90,7 @@ def index.total(ds: List<&2, Calls.Def>, +mods: List<&2, String>, +path: String) # LAW: index reports one finding for each `List.get(..)` or `String.get(..)` # at an index that is not one number token, anywhere in a def that calls # itself (a base arm included) and is not a law or a proof, except a -# `List.get` that `table` reports as a get on a fixed table and a get that is +# `List.get` on a fixed table (src/rules/table.bend) and a get that is # the def's own self-call (in a def named `List.get` or `String.get`); none # for anything else law index_counts: diff --git a/laws/LawNamesEveryItem.bend b/laws/LawNamesEveryItem.bend index 46dcda6..423a849 100644 --- a/laws/LawNamesEveryItem.bend +++ b/laws/LawNamesEveryItem.bend @@ -24,7 +24,6 @@ import ../src/syntax/outline.bend as Outline import ../src/rules/calls.bend as Calls import ../src/rules/laws/closed.bend as Closed import ../src/rules/laws/law.bend as Coverage -import ../src/rules/laws/unsafe.bend as Unsafe import ../src/rules/imports.bend as Imports import ./defs/Unsafe.bend as D1 @@ -39,7 +38,7 @@ def coverage.lawful(ds: List<&2, Digest.Digest>) -> Bool: match ds: case Nil{}: False{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: Bool.or(is_laws, coverage.lawful(rest)) # coverage's reading of a file (laws/LawNamesEveryItem.bend). reach_digest and reach_binder, @@ -133,7 +132,7 @@ type CoverageView is Data: # a digest as the coverage rule reads it def coverage.view(dd: Digest.Digest) -> CoverageView: match dd: - case Digest.Digest{_path, norm, _dir, is_laws, _is_law_file, exempt, tops, says, _deps, _unsafes, _laws}: + case Digest.Digest{_path, norm, _dir, is_laws, _is_law_file, exempt, tops, says, _deps, _laws}: CoverageView{norm, is_laws, exempt, tops, says} # coverage by reach (laws/LawNamesEveryItem.bend). A def is covered when a quantified law of @@ -141,7 +140,7 @@ def coverage.view(dd: Digest.Digest) -> CoverageView: # items that holds what the laws name and every call of each def in it. A # type is covered when that set holds it or one of its constructors: a law or # a covered def names it, and a type calls nothing. The set is stated over a -# graph written as digests, so the unsafe laws' spec of a closure (the least +# graph written as digests, so the closure laws' spec of a closure (the least # list that holds its start and is shut, unsafe.shut) is the same spec here: # each node is a digest whose path is an item's key and whose imports are # what the item leads to. An item's key is its name, a space and its file's @@ -165,7 +164,7 @@ def reach.keys(ms: List<&2, Digest.Mention>) -> List<&2, String>: # a node: a digest with only a key and what it leads to def reach.node(+kk: String, to: List<&2, String>) -> Digest.Digest: - Digest.Digest{kk, kk, "", False{}, False{}, True{}, [], [], to, [], []} + Digest.Digest{kk, kk, "", False{}, False{}, True{}, [], [], to, []} # each constructor as a node that leads nowhere, onto the others def reach.ctors(cs: List<&2, String>, +pp: String, more: List<&2, Digest.Digest>) -> List<&2, Digest.Digest>: @@ -193,7 +192,7 @@ def reach.items(ds: List<&2, Digest.Digest>) -> List<&2, Digest.Digest>: match ds: case Nil{}: [] - case Con{Digest.Digest{_path, norm, _dir, _is_laws, _is_law_file, _exempt, tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, norm, _dir, _is_laws, _is_law_file, _exempt, tops, _says, _deps, _laws}, rest}: reach.nodes(tops, norm, reach.items(rest)) @@ -202,7 +201,7 @@ def reach.named(ds: List<&2, Digest.Digest>) -> List<&2, Digest.Mention>: match ds: case Nil{}: [] - case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, says, _deps, _laws}, rest}: +more = reach.named(rest) Bool.pick(List<&2, Digest.Mention>, is_laws, List.append(&2, Digest.Mention, says, more), more) @@ -248,7 +247,7 @@ def reach.files(fs: List<&2, Digest.Digest>, +cl: List<&2, String>) -> List<&2, match fs: case Nil{}: [] - case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _laws}, rest}: match exempt: case True{}: diff --git a/laws/LspDiagnosticsMatchCli.bend b/laws/LspDiagnosticsMatchCli.bend index 370468f..73f7287 100644 --- a/laws/LspDiagnosticsMatchCli.bend +++ b/laws/LspDiagnosticsMatchCli.bend @@ -1,7 +1,9 @@ # Diagnostics for an open document equal the CLI's per-file findings for that # file and text under the same bolt.bend, less what its noqa comments silence # (laws/NoqaSilencesNamedFindings.bend), then `noqa`'s findings on them, which judge no project rule's -# code. +# code. The editor is told the roles the imports of the tree give the files, +# read as `bolt ` reads them, so a file a LAWS.bend reaches is a law +# file in both. import Base import ../src/lsp/server.bend as Server import ../src/finding.bend as F @@ -17,7 +19,9 @@ import ../src/noqa.bend as Noqa def pub.cli(+world: W.World) -> List<&2, F.Graded>: +ss = Plan.srcs_of(Plan.gots(Plan.inputs(world), world)) +cs = Plan.confs(world) - Plan.after(Noqa.of_srcs(ss), False{}, Plan.grade(cs, world, Plan.per_file(ss)), fs => Plan.grade(cs, world, fs)) + Plan.after(Noqa.of_srcs(ss), False{}, + Plan.grade(cs, world, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, ss), ss)), + fs => Plan.grade(cs, world, fs)) # LAW: an open document's lint is the CLI's: over every file system and # working directory (a World), for a document at a path whose text the CLI @@ -26,12 +30,14 @@ def pub.cli(+world: W.World) -> List<&2, F.Graded>: # directory, are exactly what `bolt ` grades as that file's per-file # findings, the same noqa comments silencing the same findings and `noqa` # reporting what the CLI reports but for a project rule's code, which the -# editor cannot judge (none for a bolt.bend, which neither lints) +# editor cannot judge (none for a bolt.bend, which neither lints), the editor +# told the role marks the imports of the tree give the files, as `bolt +# ` reads them from that same file system and directory law lint_is_cli: for +world: W.World for +path: String for +text: String for h: {W.paths.of(world) == [path] : List<&2, String>} for ht: {W.text(world, path) == Some{text} : Maybe<&2, String>} - {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) == pub.cli(world) - : List<&2, F.Graded>} + {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world), Server.pub.marks(world)) + == pub.cli(world) : List<&2, F.Graded>} diff --git a/laws/LspKeepsRoleMarks.bend b/laws/LspKeepsRoleMarks.bend new file mode 100644 index 0000000..faa2514 --- /dev/null +++ b/laws/LspKeepsRoleMarks.bend @@ -0,0 +1,48 @@ +# The editor reads the roles the imports of the tree give the files (src/role.bend's +# marks, as `bolt ` reads them) on open and save, the checker's two +# runs, and keeps them with the document: after open or save the documents +# hold the marks read then, and a change keeps the marks as they were, so +# every change is linted with the roles of the last open or save +# (laws/LspPublishesOnDocumentEvents.bend says what each publishes). +import Base +import ../src/lsp/server.bend as Server +import ../src/lsp/docs.bend as Docs +import ../src/lsp/report.bend as Rep +import ../src/lsp/enc.bend as Enc +import ../src/role.bend as Role + +# the open documents after a method's work +def keep.docs(ww: Server.Work) -> List<&2, Docs.Doc>: + Server.Work{_notes, _answer, docs} = ww + docs + +# LAW: after open the documents hold the marks read with it +law open_keeps_marks: + for +ee: Enc.Enc + for +uri: String + for +text: String + for +ds: List<&2, Rep.Diag> + for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> + for +docs: List<&2, Docs.Doc> + {Docs.marks(keep.docs(Server.pub.open(ee, uri, text, ds, rs, ms, docs)), uri) == ms : List<&2, Role.Mark>} + +# LAW: after save the documents hold the marks read with it +law save_keeps_marks: + for +ee: Enc.Enc + for +uri: String + for +ds: List<&2, Rep.Diag> + for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> + for +docs: List<&2, Docs.Doc> + {Docs.marks(keep.docs(Server.pub.save(ee, uri, ds, rs, ms, docs)), uri) == ms : List<&2, Role.Mark>} + +# LAW: a change keeps the marks as they were +law change_keeps_marks: + for +ee: Enc.Enc + for +uri: String + for +text: String + for +rs: List<&2, Maybe<&2, String>> + for +docs: List<&2, Docs.Doc> + {Docs.marks(keep.docs(Server.pub.change(ee, uri, text, rs, docs)), uri) == Docs.marks(docs, uri) + : List<&2, Role.Mark>} diff --git a/laws/LspPublishesOnDocumentEvents.bend b/laws/LspPublishesOnDocumentEvents.bend index 4f7b7c5..da34110 100644 --- a/laws/LspPublishesOnDocumentEvents.bend +++ b/laws/LspPublishesOnDocumentEvents.bend @@ -1,5 +1,6 @@ -# Open and save publish checker plus lint; change publishes lint plus the last -# checker result; close publishes an empty list; an open bolt.bend gets no +# Open and save publish checker plus lint, each file told its role among the +# marks read with the checker's run; change publishes lint, told the marks +# kept, plus the last checker result; close publishes an empty list; an open bolt.bend gets no # lint. import Base import ../src/lsp/server.bend as Server @@ -9,6 +10,7 @@ import ../src/lsp/proto.bend as Proto import ../src/finding.bend as F import ../src/lint/plan.bend as Plan import ../src/lsp/enc.bend as Enc +import ../src/role.bend as Role # publishing # ---------- @@ -30,9 +32,11 @@ law open_publishes: for +text: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {pub.notes(Server.pub.open(ee, uri, text, ds, rs, docs)) - == [Server.pub.note(Enc.cols(ee, text), uri, ds, Server.pub.lint(Proto.path(uri), text, rs))] : List<&2, String>} + {pub.notes(Server.pub.open(ee, uri, text, ds, rs, ms, docs)) + == [Server.pub.note(Enc.cols(ee, text), uri, ds, Server.pub.lint(Proto.path(uri), text, rs, ms))] + : List<&2, String>} # LAW: save publishes the checker's new errors plus the lint of the text the # editor shows @@ -41,10 +45,12 @@ law save_publishes: for +uri: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {pub.notes(Server.pub.save(ee, uri, ds, rs, docs)) + {pub.notes(Server.pub.save(ee, uri, ds, rs, ms, docs)) == [Server.pub.note(Enc.cols(ee, Maybe.default(&2, String, Docs.get(docs, uri), "")), uri, ds, - Server.pub.lint(Proto.path(uri), Maybe.default(&2, String, Docs.get(docs, uri), ""), rs))] : List<&2, String>} + Server.pub.lint(Proto.path(uri), Maybe.default(&2, String, Docs.get(docs, uri), ""), rs, ms))] + : List<&2, String>} # LAW: change publishes the lint of the new text plus the checker's errors # the documents hold for it @@ -56,7 +62,7 @@ law change_publishes: for +docs: List<&2, Docs.Doc> {pub.notes(Server.pub.change(ee, uri, text, rs, docs)) == [Server.pub.note(Enc.cols(ee, text), uri, Docs.errors(docs, uri), - Server.pub.lint(Proto.path(uri), text, rs))] : List<&2, String>} + Server.pub.lint(Proto.path(uri), text, rs, Docs.marks(docs, uri)))] : List<&2, String>} # LAW: after open the documents hold the errors it published and the text # opened @@ -66,9 +72,10 @@ law open_keeps: for +text: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {Docs.errors(pub.docs(Server.pub.open(ee, uri, text, ds, rs, docs)), uri) == ds : List<&2, Rep.Diag>} - & {Docs.get(pub.docs(Server.pub.open(ee, uri, text, ds, rs, docs)), uri) == Some{text} : Maybe<&2, String>} + {Docs.errors(pub.docs(Server.pub.open(ee, uri, text, ds, rs, ms, docs)), uri) == ds : List<&2, Rep.Diag>} + & {Docs.get(pub.docs(Server.pub.open(ee, uri, text, ds, rs, ms, docs)), uri) == Some{text} : Maybe<&2, String>} # LAW: after save the documents hold the errors it published law save_keeps: @@ -76,8 +83,9 @@ law save_keeps: for +uri: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {Docs.errors(pub.docs(Server.pub.save(ee, uri, ds, rs, docs)), uri) == ds : List<&2, Rep.Diag>} + {Docs.errors(pub.docs(Server.pub.save(ee, uri, ds, rs, ms, docs)), uri) == ds : List<&2, Rep.Diag>} # LAW: change keeps the checker's errors as they were, so every change # publishes the last checker result, and keeps the new text @@ -105,4 +113,5 @@ law config_unlinted: for h: {Plan.is_config(path) == True{} : Bool} for text: String for rs: List<&2, Maybe<&2, String>> - {Server.pub.lint(path, text, rs) == [] : List<&2, F.Graded>} + for ms: List<&2, Role.Mark> + {Server.pub.lint(path, text, rs, ms) == [] : List<&2, F.Graded>} diff --git a/laws/NoqaReportsStaleComments.bend b/laws/NoqaReportsStaleComments.bend index c36ce03..83fe7f1 100644 --- a/laws/NoqaReportsStaleComments.bend +++ b/laws/NoqaReportsStaleComments.bend @@ -2,7 +2,7 @@ # one finding for a bare one (`#`, any spaces, then `noqa` at the comment's # end or before a space) and one for each code it names that no graded finding # of its file on its line has, a code that is no rule's included, where a -# project rule's code (`coverage`, `unsafe`) is judged only when the +# project rule's code (`coverage`'s) is judged only when the # run is the whole tree and never in the editor, and nothing else. It runs # after the filter, over the findings of every other rule, so its findings # come last (laws/OutputOrderIsFixed.bend) and no noqa comment silences them: a `# noqa: S005` @@ -24,9 +24,9 @@ def nq.used(gs: List<&2, F.Graded>, +path: String, +line: U32, +code: String) -> Bool.or(Bool.and(String.eq(p, path), Bool.and(U32.is_eq(l, line), String.eq(Codes.code(rule), code))), nq.used(rest, path, line, code)) -# a project rule's code: `coverage` or `unsafe`'s +# a project rule's code: `coverage`'s def nq.project(+code: String) -> Bool: - Bool.or(String.eq(code, "L001"), String.eq(code, "L003")) + String.eq(code, "L001") # how many of a mark's codes the run judges (any over the whole tree, no # project rule's otherwise) and silence nothing on its line diff --git a/laws/OutputOrderIsFixed.bend b/laws/OutputOrderIsFixed.bend index 6a8f8c4..fb7de46 100644 --- a/laws/OutputOrderIsFixed.bend +++ b/laws/OutputOrderIsFixed.bend @@ -1,6 +1,6 @@ # Output order is read failures, then per-file findings in file-list order and -# `Rules.on` order, then `setting`'s findings (C011), then `coverage` -# and `unsafe`, less the findings a noqa comment silences +# `Rules.on` order, then `setting`'s findings (C011), then `coverage`'s, +# less the findings a noqa comment silences # (laws/NoqaSilencesNamedFindings.bend), then `noqa`'s findings (S005) in file-list order. # # also rests on: laws/LinesInOrder.bend diff --git a/laws/PackagesScoped.bend b/laws/PackagesScoped.bend index c6f4b14..76a7321 100644 --- a/laws/PackagesScoped.bend +++ b/laws/PackagesScoped.bend @@ -4,8 +4,7 @@ import ../src/rules.bend as Rules import ../src/src.bend as Src import ./defs/ScopeWalkFiles.bend as D1 -# LAW: `coverage` sees the digests of one package's files at a time, and -# `unsafe` sees every file's +# LAW: `coverage` sees the digests of one package's files at a time law packages_scoped: for +srcs: List<&2, Src.Src> for +rs: List<&2, String> diff --git a/laws/RewalkReportsRepeatedWalks.bend b/laws/RewalkReportsRepeatedWalks.bend index a337c35..2c280c0 100644 --- a/laws/RewalkReportsRepeatedWalks.bend +++ b/laws/RewalkReportsRepeatedWalks.bend @@ -15,7 +15,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk import ../src/rules/calls.bend as Calls # The recursion rule, rewalk (laws/RewalkReportsRepeatedWalks.bend). It reads each def of the file diff --git a/laws/RingReportsDropAppendRotation.bend b/laws/RingReportsDropAppendRotation.bend deleted file mode 100644 index 0390d52..0000000 --- a/laws/RingReportsDropAppendRotation.bend +++ /dev/null @@ -1,88 +0,0 @@ -# `ring` reports exactly, in a def that calls itself, a self-call argument -# that appends onto a drop by a number literal, or a tail, of the parameter in -# its own position, when no `match` of the def has that parameter as a -# scrutinee. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/calls.bend as Calls - -# ring: one when the scan found a drop, else zero -def ring.hit(mm: Maybe<&2, Lex.Tok>) -> Nat: - match mm: - case None{}: - 0n - case Some{t}: - 1n - -# how many self-call arguments slide the window of their own slot: argument -# i is read against parameter i only (Ring.slot_wins: that parameter, when -# it is a window), and slides when Ring.plus_scan finds, over it, a -# `List.drop` / `String.drop` of the window by a number literal or a -# `List.tail` / `String.tail` of it, that is then `++`-appended onto or is -# the receiver of `List.append` / `String.append`. What is appended is not -# measured: the drop is the fixed part. -def ring.args(as: List<&2, Tree.Node>, +params: List<&2, String>, +wins: List<&2, String>) -> Nat: - match as: - case Nil{}: - 0n - case Con{h, rest}: - +slot = Maybe.default(&2, String, List.head(&2, String, params), "") - Nat.add(ring.hit(Ring.plus_scan(h, None{}, Ring.slot_wins(slot, wins))), - ring.args(rest, List.tail(&2, String, params), wins)) - -# how many sliding arguments the self-calls `self(..)` under the node hold, -# at any depth -def ring.count(nn: Tree.Node, +self: String, +params: List<&2, String>, +wins: List<&2, String>) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_k, +o, _l, _c}, +kids, _e}, rest}}: - Nat.add(Bool.pick(Nat, Bool.and(String.eq(o, "("), String.eq(t, self)), ring.args(Calls.args(kids), params, wins), - 0n), Nat.add(ring.count(kids, self, params, wins), ring.count(rest, self, params, wins))) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - Nat.add(ring.count(kids, self, params, wins), ring.count(rest, self, params, wins)) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(ring.count(kids, self, params, wins), - Nat.add(ring.count(body, self, params, wins), ring.count(rest, self, params, wins))) - case Tree.NCons{h, rest}: - ring.count(rest, self, params, wins) - case other: - 0n - -# LAW: over any chain, ring reports one finding for each argument of a -# self-call that drops and appends the window in its own parameter slot, and -# none for a window moved to another parameter's slot or any other call -law ring_walk_counts: - for nn: Tree.Node - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - {List.length(&2, F.Finding, Ring.walk(nn, self, params, path, wins)) == ring.count(nn, self, params, wins) : Nat} - -# the sliding arguments of each def of the list that calls itself and is not -# exempt; its windows are its parameters not named as any scrutinee of a -# `match` in its body (Ring.windows) -def ring.defs(ds: List<&2, Calls.Def>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - ring.count(body, name, Calls.names(sig), Ring.windows(sig, body)), 0n), ring.defs(rest, path)) - -# LAW: ring reports exactly those arguments, def by def, over the defs of the -# tree that call themselves, and none in a def that does not -law ring_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, Ring.check(Src.Src{path, text, toks, tree, bound, items})) - == ring.defs(Calls.defs(tree), path) : Nat} diff --git a/laws/RoleHoist.bend b/laws/RoleHoist.bend deleted file mode 100644 index e48ca76..0000000 --- a/laws/RoleHoist.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/hoist.bend as Hoist - -# LAW: hoist, which checks nothing in a law or proof file, told a file's role -# by its name finds what hoist finds -law role_hoist: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => Hoist.check(Src.Src{path, text, toks, tree, bound, items})) - == Hoist.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleIndex.bend b/laws/RoleIndex.bend index b2f9d1d..65dacfa 100644 --- a/laws/RoleIndex.bend +++ b/laws/RoleIndex.bend @@ -6,7 +6,7 @@ import ../src/role.bend as Role import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/index.bend as Index +import ../src/rules/pedantic/index.bend as Index # LAW: index, which checks nothing in a law or proof file, told a file's role # by its name finds what index finds diff --git a/laws/RoleRewalk.bend b/laws/RoleRewalk.bend index 17d57fd..502727e 100644 --- a/laws/RoleRewalk.bend +++ b/laws/RoleRewalk.bend @@ -6,7 +6,7 @@ import ../src/role.bend as Role import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk # LAW: rewalk, which checks nothing in a law or proof file, told a file's role # by its name finds what rewalk finds diff --git a/laws/RoleRing.bend b/laws/RoleRing.bend deleted file mode 100644 index cdc20ee..0000000 --- a/laws/RoleRing.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/ring.bend as Ring - -# LAW: ring, which checks nothing in a law or proof file, told a file's role -# by its name finds what ring finds -law role_ring: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => Ring.check(Src.Src{path, text, toks, tree, bound, items})) - == Ring.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleScan.bend b/laws/RoleScan.bend deleted file mode 100644 index 4c5508e..0000000 --- a/laws/RoleScan.bend +++ /dev/null @@ -1,20 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/scan.bend as Scan - -# LAW: scan told a file's role by its name finds what scan finds -law role_scan: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Scan.check.role(Role.of_path(path), Src.Src{path, text, toks, tree, bound, items}) - == Scan.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleScanExempt.bend b/laws/RoleScanExempt.bend deleted file mode 100644 index 16df7ba..0000000 --- a/laws/RoleScanExempt.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/scan.bend as Scan - -# LAW: scan reports nothing in a law or proof file, whatever the source holds -law role_scan_exempt: - for rr: Role.Role - for e: {Role.is_law_file(rr) == True{} : Bool} - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Scan.check.role(rr, Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} diff --git a/laws/RoleStrict.bend b/laws/RoleStrict.bend index 6f6778b..e6fd861 100644 --- a/laws/RoleStrict.bend +++ b/laws/RoleStrict.bend @@ -6,7 +6,7 @@ import ../src/role.bend as Role import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict # LAW: strict, which checks nothing in a law or proof file, told a file's role # by its name finds what strict finds diff --git a/laws/RoleTable.bend b/laws/RoleTable.bend deleted file mode 100644 index a881cbd..0000000 --- a/laws/RoleTable.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/table.bend as Table - -# LAW: table, which checks nothing in a law or proof file, told a file's role -# by its name finds what table finds -law role_table: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => Table.check(Src.Src{path, text, toks, tree, bound, items})) - == Table.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleUnit.bend b/laws/RoleUnit.bend deleted file mode 100644 index 1e516f7..0000000 --- a/laws/RoleUnit.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/unit.bend as UnitRule - -# LAW: unit, which checks nothing in a law or proof file, told a file's role -# by its name finds what unit finds -law role_unit: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => UnitRule.check(Src.Src{path, text, toks, tree, bound, items})) - == UnitRule.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/ScanReportsCarriedArgumentWalks.bend b/laws/ScanReportsCarriedArgumentWalks.bend deleted file mode 100644 index 4464cb4..0000000 --- a/laws/ScanReportsCarriedArgumentWalks.bend +++ /dev/null @@ -1,170 +0,0 @@ -# `scan` reports exactly, outside a LAWS.bend or a PROOF.bend, in a def that -# calls itself and is not a proof, outside a lambda's body (the rest of a -# chain after `=>`), a case pattern, and a case arm that does not call the -# def: each call, a plain or dotted name token then a `(` group, of a callee -# other than the def, one of whose walked arguments is exactly the lone name -# of a parameter that every self-call passes back as that same lone name in -# its own position (carried). A callee walks argument 2 of `List.contains`, -# `List.find`, `List.filter` and `List.length`, 3 of `List.any` and -# `List.all`, and 4 of `List.foldl` and `List.foldr` (the Base list searches); -# a def of the file walks, when it calls itself, its first live parameter that -# some self-call does not pass back unchanged, unless that parameter's type is -# `Nat` or `String`, and each parameter its body passes, anywhere, as the lone -# name of an argument a call of a Base search or of a def listed before it -# walks. One finding per call. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/paths.bend as Paths -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/scan.bend as Scan -import ../src/rules/calls.bend as Calls - -# scan (laws/ScanReportsCarriedArgumentWalks.bend). Outside a law file the rule walks each def of the -# file that calls itself (Calls.calls) and is not a proof, its body hot, as -# hoist does: a `=>` token makes the rest of its chain cold, a case's pattern -# is cold, and a case arm's body stays hot only when it calls the def. In a -# hot region it reports each call, a plain or dotted name token then a `(` -# group, of a callee other than the def, when one of the arguments the -# callee walks (Scan.slots, over the file's walks, Scan.walks) is exactly the -# lone name of a carried parameter (Hoist.carried). - -# a callee is a plain or a dotted name -def scan.callee(kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TName{}: - True{} - case Lex.TDotted{}: - True{} - case other: - False{} - -# is the token a plain name the list holds? -def scan.named(tok: Lex.Tok, +carried: List<&2, String>) -> Bool: - match tok: - case Lex.Tok{Lex.TName{}, +t, l, c}: - List.contains(~String, ~String.eq, carried, t) - case other: - False{} - -# is the argument exactly the lone name of a carried parameter? -def scan.lone(nn: Tree.Node, +carried: List<&2, String>) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - scan.named(tok, carried) - case other: - False{} - -# does some walked argument hold a carried parameter's lone name? -def scan.held(ss: List<&2, Nat>, +as: List<&2, Tree.Node>, +carried: List<&2, String>) -> Bool: - match ss: - case Nil{}: - False{} - case Con{at, rest}: - Bool.or(scan.lone(Calls.arg(as, at), carried), scan.held(rest, as, carried)) - -# does a call site report: a named callee, hot, a `(` group, not the def, -# and a walked argument carried -def scan.call( - kk: Lex.TokKind, - +hot: Bool, - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +ws: List<&2, Scan.Walk>, - +carried: List<&2, String> -) -> Bool: - Bool.and(scan.callee(kk), Bool.and(hot, Bool.and(open, Bool.and(Bool.not(String.eq(tt, self)), - scan.held(Scan.slots(tt, ws), Calls.args(kids), carried))))) - -# how many calls scan reports under the node, entered hot or cold -def scan.count( - nn: Tree.Node, - +self: String, - +ws: List<&2, Scan.Walk>, - +carried: List<&2, String>, - +hot: Bool -) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = Hoist.warm(hot, k) - Nat.add(Bool.pick(Nat, scan.call(k, heat, String.eq(o, "("), t, kids, self, ws, carried), 1n, 0n), - Nat.add(scan.count(kids, self, ws, carried, heat), scan.count(rest, self, ws, carried, heat))) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - scan.count(rest, self, ws, carried, Hoist.warm(hot, k)) - case Tree.NCons{Tree.Group{o, kids, cl}, rest}: - Nat.add(scan.count(kids, self, ws, carried, hot), scan.count(rest, self, ws, carried, hot)) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - Nat.add(scan.count(kids, self, ws, carried, False{}), - Nat.add(scan.count(body, self, ws, carried, Bool.and(hot, Calls.calls(body, self))), - scan.count(rest, self, ws, carried, hot))) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(scan.count(kids, self, ws, carried, hot), - Nat.add(scan.count(body, self, ws, carried, hot), scan.count(rest, self, ws, carried, hot))) - case Tree.NCons{h, rest}: - scan.count(rest, self, ws, carried, hot) - case other: - 0n - -# how many scan reports over the defs: those in each def that calls itself -# and is not a proof, walked hot, with its carried parameters -def scan.total(ds: List<&2, Calls.Def>, +ws: List<&2, Scan.Walk>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - scan.count(body, name, ws, Hoist.carried(sig, body, name), True{}), 0n), scan.total(rest, ws, path)) - -# LAW: outside a law file, scan reports one finding for each call the walk -# above counts, in each def that calls itself and is not a proof, with the -# file's walks; none for anything else -law scan_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == False{} : Bool} - {List.length(&2, F.Finding, Scan.check(Src.Src{path, text, toks, tree, bound, items})) - == scan.total(Calls.defs(tree), Scan.walks(Calls.defs(tree)), path) : Nat} - -# LAW: a walk whose def carries no parameter reports nothing, whatever the -# tree, the file's walks and the heat -law scan_quiet: - for nn: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +lead: String - for +path: String - for +hot: Bool - {Scan.walk(nn, self, ws, [], lead, path, hot) == Nil{} : List<&2, F.Finding>} - -# the list arguments of the Base list searches: 2, 3 or 4, counted from 0 -def scan.base(+tt: String) -> List<&2, Nat>: - Bool.pick(List<&2, Nat>, List.contains(~String, ~String.eq, ["List.contains", "List.find", "List.filter", - "List.length"], tt), [2n], - Bool.pick(List<&2, Nat>, List.contains(~String, ~String.eq, ["List.any", "List.all"], tt), [3n], - Bool.pick(List<&2, Nat>, List.contains(~String, ~String.eq, ["List.foldl", "List.foldr"], tt), [4n], []))) - -# the arguments the file's walks record for a name, in order -def scan.file(ws: List<&2, Scan.Walk>, +tt: String) -> List<&2, Nat>: - match ws: - case Nil{}: - Nil{} - case Con{Scan.Walk{+nm, at}, rest}: - +more = scan.file(rest, tt) - Bool.pick(List<&2, Nat>, String.eq(nm, tt), at <> more, more) - -# LAW: a callee walks the Base search's list argument, then each argument a -# walk of the file of that name records -law scan_slots: - for +tt: String - for ws: List<&2, Scan.Walk> - {Scan.slots(tt, ws) == List.append(&2, Nat, scan.base(tt), scan.file(ws, tt)) : List<&2, Nat>} diff --git a/laws/ScopeExemptionsFollowPaths.bend b/laws/ScopeExemptionsFollowPaths.bend index 2e9ef00..4f39282 100644 --- a/laws/ScopeExemptionsFollowPaths.bend +++ b/laws/ScopeExemptionsFollowPaths.bend @@ -22,13 +22,9 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/rewalk.bend as Rewalk -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/rewalk.bend as Rewalk +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/suspicious/thunk.bend as Thunk import ../src/rules/laws/closed.bend as Closed @@ -45,7 +41,7 @@ import ../src/rules/laws/closed.bend as Closed # whether the coverage rule lets a digest's file off def scope.exempt(dd: Digest.Digest) -> Bool: match dd: - case Digest.Digest{_path, _norm, _dir, _is_laws, _is_law_file, exempt, _tops, _says, _deps, _unsafes, _laws}: + case Digest.Digest{_path, _norm, _dir, _is_laws, _is_law_file, exempt, _tops, _says, _deps, _laws}: exempt # LAW: two files at the same path are exempt from `law` alike, whatever the @@ -122,11 +118,11 @@ law scope_param: # the findings of each rule a LAWS.bend or a PROOF.bend exempts, rule by rule def scope.law_file(+ss: Src.Src) -> List<&2, List<&2, F.Finding>>: [Pick.check(ss), Tail.check(ss), Concat.check(ss), Eager.check(ss), Rewalk.check(ss), Strict.check(ss), - Hoist.check(ss), Index.check(ss), Ring.check(ss), Table.check(ss), UnitRule.check(ss), Thunk.check(ss)] + Index.check(ss), Thunk.check(ss)] # LAW: on a LAWS.bend or a PROOF.bend, the findings of each rule its header -# exempts there (pick, tail, concat, eager, rewalk, strict, hoist, index, -# ring, table, unit, thunk) are the same for any two sources at that path +# exempts there (pick, tail, concat, eager, rewalk, strict, index, thunk) +# are the same for any two sources at that path law scope_law_file: for +path: String for +text: String diff --git a/laws/ScopeLawsGovernPackage.bend b/laws/ScopeLawsGovernPackage.bend index 2a07832..82d31ca 100644 --- a/laws/ScopeLawsGovernPackage.bend +++ b/laws/ScopeLawsGovernPackage.bend @@ -16,7 +16,7 @@ def has_laws(ds: List<&2, Digest.Digest>) -> Bool: match ds: case Nil{}: False{} - case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _laws}, rest}: Bool.or(is_laws, has_laws(rest)) @@ -41,7 +41,7 @@ def under_law(ds: List<&2, Digest.Digest>, +cl: List<&2, String>) -> List<&2, F. match ds: case Nil{}: [] - case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _laws}, rest}: List.append(&2, F.Finding, graded_file(exempt, tops, cl, norm, path), under_law(rest, cl)) diff --git a/laws/ScopePackageRootsFound.bend b/laws/ScopePackageRootsFound.bend index d61c8c3..1e3b8bb 100644 --- a/laws/ScopePackageRootsFound.bend +++ b/laws/ScopePackageRootsFound.bend @@ -12,8 +12,7 @@ import ./defs/ScopeWalkFiles.bend as D1 # Packages (laws/ScopePackageRootsFound.bend, laws/ScopeProjectRulesRunOnRoot.bend). A package root other than `.` is a # directory the walk reads whose listing names a main.bend; `coverage` runs -# on each package's files alone, `unsafe` on every file. A -# directory is named as Digest.dir names one: normalized, with its slash, and +# on each package's files alone. A directory is named as Digest.dir names one: normalized, with its slash, and # `` for `.`. # whether a listing names a main.bend diff --git a/laws/ScopeProjectRulesRunOnRoot.bend b/laws/ScopeProjectRulesRunOnRoot.bend index 756121c..e594778 100644 --- a/laws/ScopeProjectRulesRunOnRoot.bend +++ b/laws/ScopeProjectRulesRunOnRoot.bend @@ -1,7 +1,6 @@ # When `.` is the only package root and the run holds a file, the project -# rules' findings are those of `coverage` and `unsafe` over the digests of -# every file in the run, in that order: -# a repo with one package is linted as it was before packages. +# rules' findings are those of `coverage` over the digests of every file in +# the run: a repo with one package is linted as it was before packages. import Base import ../src/finding.bend as F import ../src/rules.bend as Rules @@ -9,15 +8,13 @@ import ../src/src.bend as Src import ../src/role.bend as Role import ../src/rules/digest.bend as Digest import ../src/rules/laws/law.bend as Law -import ../src/rules/laws/unsafe.bend as Unsafe # LAW: when `.` is the only package root and the run holds a file, the -# project findings are coverage and unsafe over every file's digest: a repo -# with one package is linted as before +# project findings are coverage over every file's digest: a repo with one +# package is linted as before law one_package: for +s: Src.Src for +ss: List<&2, Src.Src> {Rules.project(s <> ss, []) - == List.concat(&2, F.Finding, [Law.check(Digest.all.role(Role.marks(s <> ss), s <> ss)), - Unsafe.check(Digest.all.role(Role.marks(s <> ss), s <> ss))]) + == List.concat(&2, F.Finding, [Law.check(Digest.all.role(Role.marks(s <> ss), s <> ss)), []]) : List<&2, F.Finding>} diff --git a/laws/ScopeRulesSeeTheirInputs.bend b/laws/ScopeRulesSeeTheirInputs.bend index b56e1d0..293bfe3 100644 --- a/laws/ScopeRulesSeeTheirInputs.bend +++ b/laws/ScopeRulesSeeTheirInputs.bend @@ -1,6 +1,5 @@ # A per-file rule sees one file's `Src`; `coverage` sees the digests of one -# package's files (laws/ScopePackageRootsFound.bend) at a time; `unsafe` sees -# the digests of every file in the run; `setting` sees the text of each +# package's files (laws/ScopePackageRootsFound.bend) at a time; `setting` sees the text of each # bolt.bend grading reads; and nothing else. # # also rests on: laws/PackagesScoped.bend diff --git a/laws/SpaceReportsBlanksAndWidth.bend b/laws/SpaceReportsBlanksAndWidth.bend index 383fc44..829e0dc 100644 --- a/laws/SpaceReportsBlanksAndWidth.bend +++ b/laws/SpaceReportsBlanksAndWidth.bend @@ -10,7 +10,6 @@ import ../src/rules/style/space.bend as Space import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/laws/unsafe.bend as Unsafe import ./defs/Unsafe.bend as D1 # True when the line's last char is whitespace diff --git a/laws/StrictReportsEagerSelfCalls.bend b/laws/StrictReportsEagerSelfCalls.bend index a51061e..ce110d8 100644 --- a/laws/StrictReportsEagerSelfCalls.bend +++ b/laws/StrictReportsEagerSelfCalls.bend @@ -9,7 +9,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict import ../src/rules/calls.bend as Calls # does the chain, up to its first top-level comma or `=>`, hold a `&&` or diff --git a/laws/StringsReportLongLiteralArms.bend b/laws/StringsReportLongLiteralArms.bend index 00f1d15..4038551 100644 --- a/laws/StringsReportLongLiteralArms.bend +++ b/laws/StringsReportLongLiteralArms.bend @@ -7,7 +7,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/correctness/strings.bend as Strings +import ../src/rules/perf/strings.bend as Strings import ./defs/MatchArmRules.bend as D1 # the chars between the quotes of the string a case statement's first match diff --git a/laws/TableReportsFixedTableAccess.bend b/laws/TableReportsFixedTableAccess.bend deleted file mode 100644 index 128ac8f..0000000 --- a/laws/TableReportsFixedTableAccess.bend +++ /dev/null @@ -1,85 +0,0 @@ -# `table` reports exactly, in a def that calls itself, a `List.get` or -# `List.set` call whose index is not one number token and whose list is a -# fixed table: a list literal, a number-sized array, or `List.replicate` / -# `Array.new` / `List.range` with a number count, written inline, as a table -# def of the file, or held by the let of that name in scope. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/calls.bend as Calls -import ../src/rules/tokens.bend as T - -# The table rules: table (laws/TableReportsFixedTableAccess.bend) and ring (laws/RingReportsDropAppendRotation.bend). -# Each -# reads each def of the file whose body calls it, `name(..)` (Calls.calls), -# and is not exempt, and walks that body at any depth: a call site is a name -# token then a group. - -# table: is the call `List.get(..)` or `List.set(..)` whose index (argument -# 3, counting `&2` and the type) is not one number token (Table.literal) and -# whose list (argument 2) is a fixed table (Table.owns): a list literal with -# a top-level comma, a `[v : T*n]` or `[v : T^n]` array sized by a number, or -# `List.replicate` / `Array.new` / `List.range` with a number count, written -# inline, as `name()` or `name` for a def of the file whose body is one such -# table (Table.modules), or as a use of a name whose let in scope binds one -# (Table.scope). A let reaches the statements after it and their blocks, not -# an enclosing block or a sibling case arm, and a later let of the name to -# anything else ends it; a case pattern, a lambda or a parameter binding the -# same name does not end it. -def table.site(+tt: String, +op: String, kids: Tree.Node, +fixed: List<&2, String>) -> Bool: - +as = Calls.args(kids) - Bool.and(Bool.and(String.eq(op, "("), Bool.or(String.eq(tt, "List.get"), String.eq(tt, "List.set"))), - Bool.and(Bool.not(Table.literal(Calls.arg(as, 3n))), Table.owns(Calls.arg(as, 2n), fixed))) - -# how many call sites under the node, at any depth, are a fixed-table get or -# set at a computed index -def table.count(nn: Tree.Node, +fixed: List<&2, String>) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_k, +o, _l, _c}, +kids, _e}, rest}}: - Nat.add(Bool.pick(Nat, table.site(t, o, kids, fixed), 1n, 0n), - Nat.add(table.count(kids, fixed), table.count(rest, fixed))) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - Nat.add(table.count(kids, fixed), table.count(rest, fixed)) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(table.count(kids, fixed), Nat.add(table.count(body, fixed), table.count(rest, fixed))) - case Tree.NCons{h, rest}: - table.count(rest, fixed) - case other: - 0n - -# LAW: over any chain, table reports one finding for each fixed-table -# `List.get(..)` or `List.set(..)` at an index that is not one number token, -# and none for any other call: a literal index (0 and 1 included), a list -# that is not a fixed table, `List.head` and `List.tail` stay quiet -law table_walk_counts: - for nn: Tree.Node - for +path: String - for +fixed: List<&2, String> - {List.length(&2, F.Finding, Table.walk(nn, path, fixed)) == table.count(nn, fixed) : Nat} - -# the fixed-table gets and sets of each def of the list that calls itself -# and is not exempt, the tables in scope of its body -def table.defs(ds: List<&2, Calls.Def>, +mods: List<&2, String>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - table.count(body, Table.scope(body, mods)), 0n), table.defs(rest, mods, path)) - -# LAW: table reports exactly those gets and sets, def by def, over the defs -# of the tree that call themselves, and none in a def that does not -law table_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, Table.check(Src.Src{path, text, toks, tree, bound, items})) - == table.defs(Calls.defs(tree), Table.modules(Calls.defs(tree), []), path) : Nat} diff --git a/laws/UnitReportsMultiplyByOne.bend b/laws/UnitReportsMultiplyByOne.bend deleted file mode 100644 index cd5b082..0000000 --- a/laws/UnitReportsMultiplyByOne.bend +++ /dev/null @@ -1,158 +0,0 @@ -# `unit` reports exactly a multiply or divide by one on a recursive step. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/unit.bend as UnitRule -import ../src/rules/calls.bend as Calls - -# unit (laws/UnitReportsMultiplyByOne.bend). The rule walks each def of the file that calls -# itself (Calls.calls) and is not exempt (a law file, a proof file, a def -# returning a proof), its body hot: the recursive step. A case arm's body is -# hot only when it calls the def too (a base case is not), a case's own -# pattern tokens never; anything else, lambda bodies included, is as hot as -# what holds it. In a hot region it reports one finding for each operator -# `*` with the literal one (a number token `1`, `1n` or `1.0`) right after or -# right before it, or `/` with it right after (the divisor); and one for each -# call, a leaf then a `(` group, of `Nat.mul`, `U32.mul` or `F32.mul` with -# the literal one alone as some argument, or of `Nat.div`, `U32.div` or -# `F32.div` with it alone as the last. Right before an operator is the leaf -# just before it in its chain, none past a group, a statement or another -# operator; right after is the node just after it, when a leaf. - -# does the maybe hold the literal one: a number token `1`, `1n` or `1.0`? -def unit.one(mm: Maybe<&2, Lex.Tok>) -> Bool: - match mm: - case Some{Lex.Tok{Lex.TNum{}, +t, l, c}}: - Bool.or(String.eq(t, "1"), Bool.or(String.eq(t, "1n"), String.eq(t, "1.0"))) - case other: - False{} - -# is the operator's text `*` with the literal one after or before it, or `/` -# with it after? -def unit.by_one(+op: String, prev: Maybe<&2, Lex.Tok>, +nxt: Maybe<&2, Lex.Tok>) -> Bool: - Bool.pick(Bool, String.eq(op, "*"), Bool.or(unit.one(nxt), unit.one(prev)), - Bool.and(String.eq(op, "/"), unit.one(nxt))) - -# is the token an operator by one, between prev and nxt? -def unit.hit(tok: Lex.Tok, prev: Maybe<&2, Lex.Tok>, nxt: Maybe<&2, Lex.Tok>) -> Bool: - match tok: - case Lex.Tok{Lex.TOp{}, t, l, c}: - unit.by_one(t, prev, nxt) - case other: - False{} - -# what an operator after this leaf sees before it: the leaf, none when it is -# an operator -def unit.next(tok: Lex.Tok) -> Maybe<&2, Lex.Tok>: - match tok: - case Lex.Tok{Lex.TOp{}, t, l, c}: - None{} - case other: - Some{other} - -# the node right after, when it is a leaf -def unit.first(nn: Tree.Node) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{tok}, rest}: - Some{tok} - case other: - None{} - -# the name a group right after the leaf calls, "" after no leaf -def unit.name(mm: Maybe<&2, Lex.Tok>) -> String: - match mm: - case None{}: - "" - case Some{Lex.Tok{k, t, l, c}}: - t - -# is the argument the literal one alone? -def unit.lone(aa: Tree.Node) -> Bool: - match aa: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - unit.one(Some{tok}) - case other: - False{} - -# is some argument the literal one alone? -def unit.any(as: List<&2, Tree.Node>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, rest}: - Bool.or(unit.lone(h), unit.any(rest)) - -# is the last argument the literal one alone? -def unit.last(as: List<&2, Tree.Node>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, Nil{}}: - unit.lone(h) - case Con{h, rest}: - unit.last(rest) - -# is the call a multiply with some argument one, else a divide by one? -def unit.call(+tt: String, +as: List<&2, Tree.Node>) -> Bool: - Bool.pick(Bool, List.contains(~String, ~String.eq, ["Nat.mul", "U32.mul", "F32.mul"], tt), - unit.any(as), - Bool.and(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), - unit.last(as))) - -# how many multiplies and divides by one the chain holds, at any depth, from -# a region as hot as hot, the leaf before it prev -def unit.count(nn: Tree.Node, +prev: Maybe<&2, Lex.Tok>, +self: String, +hot: Bool) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{+tok}, +rest}: - +m = unit.count(rest, unit.next(tok), self, hot) - Bool.pick(Nat, Bool.and(hot, unit.hit(tok, prev, unit.first(rest))), 1n+m, m) - case Tree.NCons{Tree.Group{Lex.Tok{ok, +o, ol, oc}, +kids, close}, rest}: - +own = Bool.and(String.eq(o, "("), Bool.and(hot, unit.call(unit.name(prev), Calls.args(kids)))) - Nat.add(unit.count(kids, None{}, self, hot), - Nat.add(Bool.pick(Nat, own, 1n, 0n), unit.count(rest, None{}, self, hot))) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - Nat.add(unit.count(kids, None{}, self, False{}), - Nat.add(unit.count(body, None{}, self, Bool.and(hot, Calls.calls(body, self))), unit.count(rest, None{}, self, - hot))) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(unit.count(kids, None{}, self, hot), - Nat.add(unit.count(body, None{}, self, hot), unit.count(rest, None{}, self, hot))) - case Tree.NCons{h, rest}: - unit.count(rest, prev, self, hot) - case other: - 0n - -# LAW: over any chain, unit reports one finding for each multiply or divide -# by one in a hot region, and none for anything else -law unit_walk_counts: - for nn: Tree.Node - for +prev: Maybe<&2, Lex.Tok> - for +self: String - for +path: String - for +hot: Bool - {List.length(&2, F.Finding, UnitRule.walk(nn, prev, self, path, hot)) == unit.count(nn, prev, self, hot) : Nat} - -# the multiplies and divides by one of each def of the list that calls itself -# and is not exempt, its body hot -def unit.defs(ds: List<&2, Calls.Def>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - unit.count(body, None{}, name, True{}), 0n), unit.defs(rest, path)) - -# LAW: unit reports exactly those, def by def, over the defs of the tree -law unit_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, UnitRule.check(Src.Src{path, text, toks, tree, bound, items})) - == unit.defs(Calls.defs(tree), path) : Nat} diff --git a/laws/UnsafeReportsReachableForeignDefs.bend b/laws/UnsafeReportsReachableForeignDefs.bend deleted file mode 100644 index f840602..0000000 --- a/laws/UnsafeReportsReachableForeignDefs.bend +++ /dev/null @@ -1,107 +0,0 @@ -# An `@unsafe def` or a foreign def (a def whose body is only imports of `.c` -# or `.js` files) reachable by relative imports from a law file in the run is -# a finding, at the def, that names the first such law file and the import -# path from it to the def's file. -import Base -import ../src/finding.bend as F -import ../src/rules/digest.bend as Digest -import ../src/rules/laws/unsafe.bend as Unsafe -import ../src/rules/imports.bend as Imports -import ./defs/Unsafe.bend as D1 - -# the import path from the file at from to the file at to, over the digests, -# as the finding spells it: `a -> b -> c` -def unsafe.via(+all: List<&2, Digest.Digest>, +from: String, +to: String) -> String: - String.join(Imports.trail(Digest.edges(all), Imports.paths(Digest.edges(all)), List.length(&2, Digest.Digest, all), - from, to), " -> ") - -# the path of the first law file among ls (a LAWS.bend or a PROOF.bend) -# whose closure over all holds the path pp -def unsafe.origin(ls: List<&2, Digest.Digest>, +all: List<&2, Digest.Digest>, +pp: String) -> Maybe<&2, String>: - match ls: - case Nil{}: - None{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - +more = unsafe.origin(rest, all, pp) - Bool.pick(Maybe<&2, String>, Bool.and(is_law_file, Imports.has(D1.unsafe.closure(all, norm), pp)), Some{path}, - more) - -# what the finding says of a foreign or an `@unsafe` def, reached from the -# law file at origin along the import path via -def unsafe.says(foreign: Bool, +name: String, +origin: String, +via: String) -> String: - match foreign: - case True{}: - "foreign def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; move the effect into a sibling module no law file imports, and pass it in as a service." - case False{}: - "@unsafe def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; recurse on a shrinking argument or on fuel and drop @unsafe, or move it into a sibling module no law file imports." - -# one finding for each def, at its `@` (7 wide) or, a foreign def, its -# header's start (3 wide), naming the law file and the import path -def unsafe.each(us: List<&2, Digest.Unsafe>, +origin: String, +via: String, +path: String) -> List<&2, F.Finding>: - match us: - case Nil{}: - Nil{} - case Con{Digest.Unsafe{n, l, c, +fo}, rest}: - F.Finding{path, l, c, Bool.pick(U32, fo, 3, 7), "unsafe", unsafe.says(fo, n, origin, via)} - <> unsafe.each(rest, origin, via, path) - -# a file's unsafe and foreign defs when a law file reaches it, else nothing; -# the path runs from that law file to the file at norm -def unsafe.at( - mm: Maybe<&2, String>, - us: List<&2, Digest.Unsafe>, - +path: String, - +norm: String, - +all: List<&2, Digest.Digest> -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{+origin}: - unsafe.each(us, origin, unsafe.via(all, Imports.norm(origin), norm), path) - -# the findings of every file of fs, in order, against the law files of all -def unsafe.files(fs: List<&2, Digest.Digest>, +all: List<&2, Digest.Digest>) -> List<&2, F.Finding>: - match fs: - case Nil{}: - Nil{} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - List.append(&2, F.Finding, unsafe.at(unsafe.origin(all, all, norm), unsafes, path, norm, all), - unsafe.files(rest, all)) - -# LAW: over every list of digests, unsafe reports exactly each `@unsafe def` -# and each foreign def of each file that the closure of some law file holds, -# in file order, and names the first such law file and the import path from -# it to the file -law unsafe_reports: - for +ds: List<&2, Digest.Digest> - {Unsafe.check(ds) == unsafe.files(ds, ds) : List<&2, F.Finding>} - -# LAW: over every list of digests, what the file at from reaches holds it -law unsafe_from: - for +ds: List<&2, Digest.Digest> - for +from: String - {Imports.has(D1.unsafe.closure(ds, from), from) == True{} : Bool} - -# LAW: over every list of digests, what a file reaches is shut: every file in -# the run that one of its files imports is in it. So the walk's fuel never -# cuts it short -law unsafe_shut: - for +ds: List<&2, Digest.Digest> - for +from: String - {D1.unsafe.shut(ds, D1.unsafe.closure(ds, from), D1.unsafe.closure(ds, from)) == True{} : Bool} - -# LAW: and it is the least such list: every list that holds from and is shut -# holds each file it reaches, so it reaches only files a chain of relative -# imports through the run leads to -law unsafe_least: - for +ds: List<&2, Digest.Digest> - for +from: String - for +ss: List<&2, String> - for +pp: String - for hf: {Imports.has(ss, from) == True{} : Bool} - for hs: {D1.unsafe.shut(ds, ss, ss) == True{} : Bool} - for hp: {Imports.has(D1.unsafe.closure(ds, from), pp) == True{} : Bool} - {Imports.has(ss, pp) == True{} : Bool} diff --git a/laws/defs/GradedFindingCounts.bend b/laws/defs/GradedFindingCounts.bend index b1b14c5..0857df0 100644 --- a/laws/defs/GradedFindingCounts.bend +++ b/laws/defs/GradedFindingCounts.bend @@ -150,12 +150,14 @@ def pkgs(rs: List<&2, String>) -> List<&2, String>: # every finding of a run, ungraded: the read failures, then each source's # per-file findings, each rule seeing that one source and its role among the -# run's law and proof files (src/role.bend), then `setting`'s, +# law and proof files (src/role.bend) of the run, or of the whole tree when +# files were named (Plan.marks.run), then `setting`'s, # seeing each bolt.bend grading reads, then the project rules over every # source in the run, by package def in_scope(+world: W.World) -> List<&2, F.Finding>: +ps = Plan.inputs(world) - List.concat(&2, F.Finding, [unread_all(world, ps), on_all(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), world, ps), + List.concat(&2, F.Finding, [unread_all(world, ps), + on_all(Plan.marks.run(W.paths.of(world), world, Plan.srcs_of(Plan.gots(ps, world))), world, ps), settings_found(world), D2.packed(parsed_all(world, ps), pkgs(Plan.roots(world)))]) diff --git a/laws/defs/ScopeWalkFiles.bend b/laws/defs/ScopeWalkFiles.bend index fd7c884..e83b794 100644 --- a/laws/defs/ScopeWalkFiles.bend +++ b/laws/defs/ScopeWalkFiles.bend @@ -6,7 +6,6 @@ import ../../src/src.bend as Src import ../../src/role.bend as Role import ../../src/rules/digest.bend as Digest import ../../src/rules/laws/law.bend as Law -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../src/lint/world.bend as W # whether a directory is among the roots @@ -31,10 +30,10 @@ def part(ds: List<&2, Digest.Digest>, +rs: List<&2, String>, +pkg: String) -> Li match ds: case Nil{}: [] - case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = part(rest, rs, pkg) Bool.pick(List<&2, Digest.Digest>, String.eq(nearest(Rules.ups(dir), rs), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> more, more) + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> more, more) # `coverage` on the files of each package but `.` def covered(ps: List<&2, String>, +ds: List<&2, Digest.Digest>, +rs: List<&2, String>) -> List<&2, F.Finding>: @@ -44,11 +43,10 @@ def covered(ps: List<&2, String>, +ds: List<&2, Digest.Digest>, +rs: List<&2, St case Con{+dir, rest}: List.append(&2, F.Finding, Law.check(part(ds, rs, dir)), covered(rest, ds, rs)) -# the project rules' findings: `coverage` on each package's files, `.` first, -# then `unsafe` on every file +# the project rules' findings: `coverage` on each package's files, `.` first def packed(+srcs: List<&2, Src.Src>, +rs: List<&2, String>) -> List<&2, F.Finding>: +ds = Digest.all.role(Role.marks(srcs), srcs) - List.concat(&2, F.Finding, [Law.check(part(ds, rs, "")), covered(rs, ds, rs), Unsafe.check(ds)]) + List.concat(&2, F.Finding, [Law.check(part(ds, rs, "")), covered(rs, ds, rs)]) # The walk (laws/ScopeWalksWholeTree.bend). With no paths named, the planner lints what a # breadth-first search over the World's listings reaches from `.`. The spec diff --git a/laws/defs/Unsafe.bend b/laws/defs/Unsafe.bend index 4e10e1c..ffce175 100644 --- a/laws/defs/Unsafe.bend +++ b/laws/defs/Unsafe.bend @@ -19,13 +19,11 @@ def share(kk: Lex.TokKind, +tt: String) -> U32: def over(+width: U32, +more: Nat) -> Nat: Bool.pick(Nat, U32.is_gt(width, 120), 1n+more, more) -# unsafe (laws/UnsafeReportsReachableForeignDefs.bend). Over every list of digests, as coverage's laws are. -# A digest records a file's relative imports (normalized, `deps`) and its -# `@unsafe def`s and foreign defs (a def whose body is only imports of `.c` -# or `.js` files); the import graph is theirs, and the closure a law file +# Reach (laws/ImportReachIsClosed.bend), over every list of digests, as +# coverage's laws are. A digest records a file's relative imports +# (normalized, `deps`); the import graph is theirs, and the closure a file # reaches over it is Imports.closure.go's, the walk every project rule -# shares. A finding names the import path from the law file to the def's -# file, a shortest one, as Imports.trail walks it. +# shares. # every file of the digests the file at from reaches by relative imports, # itself first, as the closure walk takes it @@ -46,7 +44,7 @@ def unsafe.imports(ds: List<&2, Digest.Digest>, +pp: String) -> List<&2, String> match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = unsafe.imports(rest, pp) Bool.pick(List<&2, String>, String.eq(norm, pp), deps, more) @@ -55,7 +53,7 @@ def unsafe.read(ds: List<&2, Digest.Digest>, +pp: String) -> Bool: match ds: case Nil{}: False{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = unsafe.read(rest, pp) Bool.or(String.eq(norm, pp), more) diff --git a/proofs/CharsReportManyCharArms.bend b/proofs/CharsReportManyCharArms.bend index a0f0416..563afed 100644 --- a/proofs/CharsReportManyCharArms.bend +++ b/proofs/CharsReportManyCharArms.bend @@ -5,7 +5,7 @@ import ../src/finding.bend as F import ../src/rules/tokens.bend as T import ../src/syntax/tree.bend as Tree import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/correctness/chars.bend as Chars +import ../src/rules/perf/chars.bend as Chars import ../laws/CharsReportManyCharArms.bend as Laws import ../laws/defs/MatchArmRules.bend as D1 import ./lemmas/MatchStatementCounts.bend as M1 diff --git a/proofs/CodeRulesIgnoreCommentsAndStrings.bend b/proofs/CodeRulesIgnoreCommentsAndStrings.bend index 47e0545..8a0d2b9 100644 --- a/proofs/CodeRulesIgnoreCommentsAndStrings.bend +++ b/proofs/CodeRulesIgnoreCommentsAndStrings.bend @@ -17,16 +17,12 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule -import ../src/rules/suspicious/scan.bend as Scan +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index +import ../src/rules/table.bend as Table import ../src/rules/correctness/arms.bend as Arms import ../src/rules/suspicious/unused.bend as Unused -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule import ../src/rules/style/wrap.bend as Wrap import ../src/rules/suspicious/thunk.bend as Thunk import ../laws/CodeRulesIgnoreCommentsAndStrings.bend as Laws @@ -2857,56 +2853,6 @@ def inert.contains_keep(xs, b, t, ef, ep): # table # ----- -# a chain read that way is one number as it was -law inert.table.literal: - for nn: Tree.Node - {Table.literal(D1.inert.node(nn)) == Table.literal(nn) : Bool} - -def inert.table.literal(nn): - match nn: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: - match r: - case Tree.NNil{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - # a chain read that way is one number as it was law inert.table.lone_num: for nn: Tree.Node @@ -3782,248 +3728,6 @@ def inert.table.modules(ds, acc, e): inert.table.modules(rest, Table.mod_one(D1.inert.node(body), name, acc), inert.dok.rest(name, sig, body, rest, e)) -# what hit reads of a call: its name against get and set, its index and list -def inert.table.hq( - +open: Bool, - +get: Bool, - set: Bool, - lit: Bool, - own: Bool, - +tt: String, - +path: String, - +line: U32, - +col: U32 -) -> List<&2, F.Finding>: - Bool.pick(List<&2, F.Finding>, Bool.and(Bool.and(open, Bool.or(get, set)), Bool.and(Bool.not(lit), own)), - [Table.cite(get, path, line, col, U32.from_nat(String.length(tt)))], []) - -# a call named neither get nor set is no hit -law inert.table.hq_off: - for open: Bool - for lit: Bool - for own: Bool - for +tt: String - for +path: String - for +line: U32 - for +col: U32 - {inert.table.hq(open, False{}, False{}, lit, own, tt, path, line, col) == [] : List<&2, F.Finding>} - -def inert.table.hq_off(open, _lit, _own, _tt, _path, _line, _col): - match open: - case True{}: {==} - case False{}: {==} - -# a call whose name is no get and no set, as hit reads it, is no hit -law inert.table.hit_off: - for +open: Bool - for +tt: String - for +kids: Tree.Node - for +fixed: List<&2, String> - for +path: String - for +line: U32 - for +col: U32 - for eg: {String.eq(tt, "List.get") == False{} : Bool} - for es: {String.eq(tt, "List.set") == False{} : Bool} - {Table.hit(open, tt, kids, fixed, path, line, col) == [] : List<&2, F.Finding>} - -def inert.table.hit_off(open, tt, kids, fixed, path, line, col, eg, es): - +aa = Calls.args(kids) - +lit = Table.literal(Calls.arg(aa, 3n)) - +own = Table.owns(Calls.arg(aa, 2n), fixed) - %Equal.sym(Bool, String.eq(tt, "List.get"), False{}, eg) : {inert.table.hq(open, _, String.eq(tt, "List.set"), lit, - own, tt, path, line, col) == [] : List<&2, F.Finding>} - %Equal.sym(Bool, String.eq(tt, "List.set"), False{}, es) : {inert.table.hq(open, False{}, _, lit, own, tt, path, - line, col) == [] : List<&2, F.Finding>} - inert.table.hq_off(open, lit, own, tt, path, line, col) - -# a call read that way is the hit it was: a string is named neither get nor -# set, and a name is read as it was -law inert.table.hit: - for +open: Bool - for b: Bool - for +t: String - for +kids: Tree.Node - for +fixed: List<&2, String> - for +path: String - for +line: U32 - for +col: U32 - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Table.hit(open, D1.inert.keep(b, t), D1.inert.node(kids), fixed, path, line, col) - == Table.hit(open, t, kids, fixed, path, line, col) : List<&2, F.Finding>} - -def inert.table.hit(open, b, t, kids, fixed, path, line, col, ef, ek): - match b: - case True{}: - Equal.trans(List<&2, F.Finding>, Table.hit(open, D1.inert.cut(t), D1.inert.node(kids), fixed, path, line, - col), [], Table.hit(open, t, kids, fixed, path, line, col), - inert.table.hit_off(open, D1.inert.cut(t), D1.inert.node(kids), fixed, path, line, col, - M1.inert.neq(D1.inert.cut(t), "List.get", M1.inert.cut_marked(t, ef), {==}), - M1.inert.neq(D1.inert.cut(t), "List.set", M1.inert.cut_marked(t, ef), {==})), - Equal.sym(List<&2, F.Finding>, Table.hit(open, t, kids, fixed, path, line, col), [], - inert.table.hit_off(open, t, kids, fixed, path, line, col, M1.inert.neq(t, "List.get", ef, {==}), - M1.inert.neq(t, "List.set", ef, {==})))) - case False{}: - +aa = Calls.args(kids) - +lhs = Table.hit(open, t, D1.inert.node(kids), fixed, path, line, col) - %inert.table.literal(Calls.arg(aa, 3n)) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), - String.eq(t, "List.set"), _, Table.owns(Calls.arg(aa, 2n), fixed), t, path, line, col) : List<&2, F.Finding>} - %inert.table.owns(Calls.arg(aa, 2n), fixed, inert.ok.arg(kids, 2n, ek)) : {lhs == inert.table.hq(open, - String.eq(t, "List.get"), String.eq(t, "List.set"), Table.literal(D1.inert.node(Calls.arg(aa, 3n))), _, t, - path, line, col) : List<&2, F.Finding>} - %inert.arg(aa, 3n) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), String.eq(t, "List.set"), - Table.literal(_), Table.owns(D1.inert.node(Calls.arg(aa, 2n)), fixed), t, path, line, col) - : List<&2, F.Finding>} - %inert.arg(aa, 2n) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), String.eq(t, "List.set"), - Table.literal(Calls.arg(inert.nodes(aa), 3n)), Table.owns(_, fixed), t, path, line, col) : List<&2, F.Finding>} - %inert.args(kids) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), String.eq(t, "List.set"), - Table.literal(Calls.arg(_, 3n)), Table.owns(Calls.arg(_, 2n), fixed), t, path, line, col) - : List<&2, F.Finding>} - {==} - -# a name then a group: the call's hit, the group and the rest walked as they were -law inert.table.lg: - for +kk: Lex.TokKind - for +t: String - for +o: String - for +l: U32 - for +c: U32 - for +path: String - for +fixed: List<&2, String> - for +kids: Tree.Node - for +r2: Tree.Node - for +ef: {D1.inert.fits.go(D1.inert.blanks(kk), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Table.walk(D1.inert.node(kids), path, fixed) == Table.walk(kids, path, fixed) : List<&2, F.Finding>} - for ir: {Table.walk(D1.inert.node(r2), path, fixed) == Table.walk(r2, path, fixed) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Table.hit(String.eq(o, "("), D1.inert.keep(D1.inert.blanks(kk), t), - D1.inert.node(kids), fixed, path, l, c), Table.walk(D1.inert.node(kids), path, fixed), - Table.walk(D1.inert.node(r2), path, fixed)]) - == List.concat(&2, F.Finding, [Table.hit(String.eq(o, "("), t, kids, fixed, path, l, c), Table.walk(kids, path, - fixed), Table.walk(r2, path, fixed)]) : List<&2, F.Finding>} - -def inert.table.lg(kk, t, o, l, c, path, fixed, kids, r2, ef, ek, ik, ir): - inert.cat3(Table.hit(String.eq(o, "("), D1.inert.keep(D1.inert.blanks(kk), t), D1.inert.node(kids), fixed, path, - l, c), Table.hit(String.eq(o, "("), t, kids, fixed, path, l, c), Table.walk(D1.inert.node(kids), path, fixed), - Table.walk(kids, path, fixed), Table.walk(D1.inert.node(r2), path, fixed), Table.walk(r2, path, fixed), - inert.table.hit(String.eq(o, "("), D1.inert.blanks(kk), t, kids, fixed, path, l, c, ef, ek), ik, ir) - -# table's walk reads no string: a leaf's text is compared with `List.get` -# and `List.set`, which no string equals -law inert.table.walk: - for nn: Tree.Node - for +path: String - for +fixed: List<&2, String> - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Table.walk(D1.inert.node(nn), path, fixed) == Table.walk(nn, path, fixed) : List<&2, F.Finding>} - -def inert.table.walk(nn, path, fixed, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{+o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl}, r2}, e) - +ec = inert.ok_h(Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl}, r2, eg) - inert.table.lg(k, t, ot, l, c, path, fixed, kids, r2, - inert.ok_h(lf, Tree.NCons{Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl}, r2}, e), ec, - inert.table.walk(kids, path, fixed, ec), - inert.table.walk(r2, path, fixed, inert.ok_t(Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, - cl}, r2, eg))) - case Tree.Leaf{x}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Table.walk(D1.inert.node(kids), path, fixed), Table.walk(kids, path, fixed), - Table.walk(D1.inert.node(rest), path, fixed), Table.walk(rest, path, fixed), - inert.table.walk(kids, path, fixed, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.cat3(Table.walk(D1.inert.node(kids), path, fixed), Table.walk(kids, path, fixed), - Table.walk(D1.inert.node(body), path, fixed), Table.walk(body, path, fixed), - Table.walk(D1.inert.node(rest), path, fixed), Table.walk(rest, path, fixed), - inert.table.walk(kids, path, fixed, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.table.walk(body, path, fixed, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e))) - case Tree.NNil{}: inert.table.walk(rest, path, fixed, e) - case Tree.NCons{x, y}: inert.table.walk(rest, path, fixed, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# table over the defs read that way reports what it reports over the defs -law inert.table.go: - for ds: List<&2, Calls.Def> - for +mods: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Table.check.go(inert.defs(ds), mods, path, acc) == Table.check.go(ds, mods, path, acc) : List<&2, F.Finding>} - -def inert.table.go(ds, mods, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - D1.inert.node(sig))))), [], _u => Table.walk(ib, path, Table.scope(ib, mods))) - %inert.table.scope(body, mods, eb) : {Table.check.go(inert.defs(rest), mods, path, lst <> acc) - == Table.check.go(rest, mods, path, Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(body, name), - Bool.not(Calls.exempt(path, sig)))), [], _u => Table.walk(body, path, _)) <> acc) : List<&2, F.Finding>} - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, D1.inert.node(sig))))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - Table.walk(ib, path, Table.scope(ib, mods)), Table.walk(body, path, Table.scope(ib, mods)), - inert.gate(path, name, sig, body, en, eb), inert.table.walk(body, path, Table.scope(ib, mods), eb)) : - {Table.check.go(inert.defs(rest), mods, path, lst <> acc) == Table.check.go(rest, mods, path, _ <> acc) - : List<&2, F.Finding>} - inert.table.go(rest, mods, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -# table over the defs read that way, their table defs read that way too -law inert.table.all: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Table.check.go(inert.defs(ds), Table.modules(inert.defs(ds), []), path, []) - == Table.check.go(ds, Table.modules(ds, []), path, []) : List<&2, F.Finding>} - -def inert.table.all(ds, path, e): - %inert.table.modules(ds, [], e) : {Table.check.go(inert.defs(ds), Table.modules(inert.defs(ds), []), path, []) - == Table.check.go(ds, _, path, []) : List<&2, F.Finding>} - inert.table.go(ds, Table.modules(inert.defs(ds), []), path, [], e) - -def Laws.inert_table(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Table.check.go(Calls.defs(z), Table.modules(Calls.defs(z), []), path, []), tree, tree2, - D1.inert.node(tree), D1.inert.node(tree2), - inert.lift(z => Table.check.go(z, Table.modules(z, []), path, []), tree, inert.table.all(Calls.defs(tree), path, - inert.dok.of(tree, ok))), - inert.lift(z => Table.check.go(z, Table.modules(z, []), path, []), tree2, inert.table.all(Calls.defs(tree2), path, - inert.dok.of(tree2, ok2))), - e) - -# index -# ----- - # a chain read that way is one number as it was law inert.index.literal: for nn: Tree.Node @@ -5024,123 +4728,6 @@ def inert.mfits(mm: Maybe<&2, Lex.Tok>) -> Bool: case None{}: True{} case Some{tok}: D1.inert.fits(tok) -# a token read that way is the literal one it was: a number is never cut -law inert.unit.one_of: - for mm: Maybe<&2, Lex.Tok> - {UnitRule.one_of(inert.mtok(mm)) == UnitRule.one_of(mm) : Maybe<&2, Lex.Tok>} - -def inert.unit.one_of(mm): - match mm: - case None{}: {==} - case Some{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# which literal an operator's finding points at, from the two literals -def inert.unit.whichp(+op: String, +right: Maybe<&2, Lex.Tok>, left: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "*"), UnitRule.either(right, left), - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "/"), right, None{})) - -# the literal an operator points at, its neighbours read that way -law inert.unit.which: - for +op: String - for +prev: Maybe<&2, Lex.Tok> - for +nxt: Maybe<&2, Lex.Tok> - {UnitRule.which(op, inert.mtok(prev), inert.mtok(nxt)) == UnitRule.which(op, prev, nxt) : Maybe<&2, Lex.Tok>} - -def inert.unit.which(op, prev, nxt): - +lhs = UnitRule.which(op, inert.mtok(prev), inert.mtok(nxt)) - %inert.unit.one_of(nxt) : {lhs == inert.unit.whichp(op, _, UnitRule.one_of(prev)) : Maybe<&2, Lex.Tok>} - %inert.unit.one_of(prev) : {lhs == inert.unit.whichp(op, UnitRule.one_of(inert.mtok(nxt)), _) : Maybe<&2, Lex.Tok>} - {==} - -# a leaf read that way hits as it did: only an operator's text is read -law inert.unit.op_hit: - for hot: Bool - for tok: Lex.Tok - for +prev: Maybe<&2, Lex.Tok> - for +nxt: Maybe<&2, Lex.Tok> - for +path: String - {UnitRule.op_hit(D1.inert.tok(tok), inert.mtok(prev), inert.mtok(nxt), path, hot) - == UnitRule.op_hit(tok, prev, nxt, path, hot) : List<&2, F.Finding>} - -def inert.unit.op_hit(hot, tok, prev, nxt, path): - match hot: - case False{}: - match tok: - case Lex.Tok{k, t, l, c}: {==} - case True{}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: - Equal.cong(Maybe<&2, Lex.Tok>, List<&2, F.Finding>, z => UnitRule.cite(z, path), - UnitRule.which(t, inert.mtok(prev), inert.mtok(nxt)), UnitRule.which(t, prev, nxt), - inert.unit.which(t, prev, nxt)) - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# the first leaf of a chain read that way is the leaf it was, read that way -law inert.unit.first_tok: - for nn: Tree.Node - {UnitRule.first_tok(D1.inert.node(nn)) == inert.mtok(UnitRule.first_tok(nn)) : Maybe<&2, Lex.Tok>} - -def inert.unit.first_tok(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - # a token dropped or kept, read that way law inert.mt_pick: for c: Bool @@ -5165,366 +4752,10 @@ def inert.mf_pick(c, _tok, e): case True{}: {==} case False{}: e -# what a leaf leaves behind, read that way -law inert.unit.after: - for tok: Lex.Tok - {UnitRule.after(D1.inert.tok(tok)) == inert.mtok(UnitRule.after(tok)) : Maybe<&2, Lex.Tok>} - -def inert.unit.after(tok): - match tok: - case Lex.Tok{+k, +t, +l, +c}: inert.mt_pick(UnitRule.op_kind(k), Lex.Tok{k, t, l, c}) - -# what a leaf leaves behind as the lexer makes it -law inert.unit.mf_after: - for tok: Lex.Tok - for +e: {D1.inert.fits(tok) == True{} : Bool} - {inert.mfits(UnitRule.after(tok)) == True{} : Bool} - -def inert.unit.mf_after(tok, e): - match tok: - case Lex.Tok{+k, +t, +l, +c}: inert.mf_pick(UnitRule.op_kind(k), Lex.Tok{k, t, l, c}, e) - -# an argument read that way is the literal one it was -law inert.unit.arg_one: - for aa: Tree.Node - {UnitRule.arg_one(D1.inert.node(aa)) == UnitRule.arg_one(aa) : Maybe<&2, Lex.Tok>} - -def inert.unit.arg_one(aa): - match aa: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: inert.unit.one_of(Some{tok}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the last argument read that way is the literal one it was -law inert.unit.last_one: - for as: List<&2, Tree.Node> - {UnitRule.last_one(inert.nodes(as)) == UnitRule.last_one(as) : Maybe<&2, Lex.Tok>} - -def inert.unit.last_one(as): - match as: - case Nil{}: {==} - case Con{h, +rest}: - match rest: - case Nil{}: inert.unit.arg_one(h) - case Con{x, y}: inert.unit.last_one(rest) - -# the last argument that is the literal one, the arguments read that way -law inert.unit.any_one: - for as: List<&2, Tree.Node> - {UnitRule.any_one(inert.nodes(as)) == UnitRule.any_one(as) : Maybe<&2, Lex.Tok>} - -def inert.unit.any_one(as): - match as: - case Nil{}: {==} - case Con{+h, +rest}: - %inert.unit.any_one(rest) : {UnitRule.any_one(inert.nodes(Con{h, rest})) == UnitRule.either(_, - UnitRule.arg_one(h)) : Maybe<&2, Lex.Tok>} - %inert.unit.arg_one(h) : {UnitRule.any_one(inert.nodes(Con{h, rest})) == UnitRule.either( - UnitRule.any_one(inert.nodes(rest)), _) : Maybe<&2, Lex.Tok>} - {==} - -# a divide's divisor, the arguments read that way -law inert.unit.on_div_is: - for d: Bool - for +as: List<&2, Tree.Node> - for +path: String - {UnitRule.on_div_is(d, inert.nodes(as), path) == UnitRule.on_div_is(d, as, path) : List<&2, F.Finding>} - -def inert.unit.on_div_is(d, as, path): - match d: - case False{}: {==} - case True{}: - Equal.cong(Maybe<&2, Lex.Tok>, List<&2, F.Finding>, z => UnitRule.cite(z, path), - UnitRule.last_one(inert.nodes(as)), UnitRule.last_one(as), inert.unit.last_one(as)) - -# the three divides -def inert.unit.divs() -> List<&2, String>: - ["Nat.div", "U32.div", "F32.div"] - -# the three multiplies -def inert.unit.muls() -> List<&2, String>: - ["Nat.mul", "U32.mul", "F32.mul"] - -# a multiply or a divide, the name and arguments read that way -law inert.unit.on_op_is: - for m: Bool - for +b: Bool - for +t: String - for +as: List<&2, Tree.Node> - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {UnitRule.on_op_is(m, D1.inert.keep(b, t), inert.nodes(as), path) == UnitRule.on_op_is(m, t, as, path) - : List<&2, F.Finding>} - -def inert.unit.on_op_is(m, b, t, as, path, ef): - match m: - case True{}: - Equal.cong(Maybe<&2, Lex.Tok>, List<&2, F.Finding>, z => UnitRule.cite(z, path), - UnitRule.any_one(inert.nodes(as)), UnitRule.any_one(as), inert.unit.any_one(as)) - case False{}: - +cd = List.contains(~String, ~String.eq, inert.unit.divs(), D1.inert.keep(b, t)) - %inert.contains_keep(inert.unit.divs(), b, t, ef, {==}) : {UnitRule.on_div(D1.inert.keep(b, t), - inert.nodes(as), path) == UnitRule.on_div_is(_, as, path) : List<&2, F.Finding>} - inert.unit.on_div_is(cd, as, path) - -# a call's identity operands, the name and arguments read that way -law inert.unit.on_op: - for +b: Bool - for +t: String - for +as: List<&2, Tree.Node> - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {UnitRule.on_op(D1.inert.keep(b, t), inert.nodes(as), path) == UnitRule.on_op(t, as, path) : List<&2, F.Finding>} - -def inert.unit.on_op(b, t, as, path, ef): - +kt = D1.inert.keep(b, t) - %inert.contains_keep(inert.unit.muls(), b, t, ef, {==}) : {UnitRule.on_op(kt, inert.nodes(as), path) - == UnitRule.on_op_is(_, t, as, path) : List<&2, F.Finding>} - inert.unit.on_op_is(List.contains(~String, ~String.eq, inert.unit.muls(), kt), b, t, as, path, ef) - -# an application's findings, the name and the group read that way -law inert.unit.own: - for call: Bool - for hot: Bool - for +b: Bool - for +t: String - for +kids: Tree.Node - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {UnitRule.own_of(call, D1.inert.keep(b, t), D1.inert.node(kids), path, hot) - == UnitRule.own_of(call, t, kids, path, hot) : List<&2, F.Finding>} - -def inert.unit.own(call, hot, b, t, kids, path, ef): - match call: - case False{}: {==} - case True{}: - match hot: - case False{}: {==} - case True{}: - %Equal.sym(List<&2, Tree.Node>, Calls.args(D1.inert.node(kids)), inert.nodes(Calls.args(kids)), - inert.args(kids)) : {UnitRule.on_op(D1.inert.keep(b, t), _, path) == UnitRule.on_op(t, Calls.args(kids), - path) : List<&2, F.Finding>} - inert.unit.on_op(b, t, Calls.args(kids), path, ef) - -# an application's findings, the callee and the group read that way -law inert.unit.callee: - for +call: Bool - for +hot: Bool - for prev: Maybe<&2, Lex.Tok> - for +kids: Tree.Node - for +path: String - for +ep: {inert.mfits(prev) == True{} : Bool} - {UnitRule.own_of(call, UnitRule.callee(inert.mtok(prev)), D1.inert.node(kids), path, hot) - == UnitRule.own_of(call, UnitRule.callee(prev), kids, path, hot) : List<&2, F.Finding>} - -def inert.unit.callee(call, hot, prev, kids, path, ep): - match prev: - case None{}: inert.unit.own(call, hot, False{}, "", kids, path, {==}) - case Some{tok}: - match tok: - case Lex.Tok{+k, +t, l, c}: inert.unit.own(call, hot, D1.inert.blanks(k), t, kids, path, ep) - -# a statement, read that way: its tokens, its body and the rest walked as -# they were, a case arm hot by its self-call as it was -law inert.unit.stmt: - for sk: Tree.StmtKind - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +prev: Maybe<&2, Lex.Tok> - for +self: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for i1: {UnitRule.walk(D1.inert.node(kids), None{}, self, path, False{}) == UnitRule.walk(kids, None{}, self, - path, False{}) : List<&2, F.Finding>} - for i2: {UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot) == UnitRule.walk(kids, None{}, self, path, - hot) : List<&2, F.Finding>} - for i3: {UnitRule.walk(D1.inert.node(body), None{}, self, path, Bool.and(hot, Calls.calls(body, self))) - == UnitRule.walk(body, None{}, self, path, Bool.and(hot, Calls.calls(body, self))) : List<&2, F.Finding>} - for i4: {UnitRule.walk(D1.inert.node(body), None{}, self, path, hot) == UnitRule.walk(body, None{}, self, path, - hot) : List<&2, F.Finding>} - for i5: {UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot) == UnitRule.walk(rest, None{}, self, path, - hot) : List<&2, F.Finding>} - {UnitRule.walk(Tree.NCons{Tree.Stmt{sk, D1.inert.node(kids), D1.inert.node(body)}, D1.inert.node(rest)}, - inert.mtok(prev), self, path, hot) - == UnitRule.walk(Tree.NCons{Tree.Stmt{sk, kids, body}, rest}, prev, self, path, hot) : List<&2, F.Finding>} - -def inert.unit.stmt(sk, kids, body, rest, _prev, self, path, hot, es, _ek, eb, i1, i2, i3, i4, i5): - match sk: - case Tree.SDef{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SType{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SLaw{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SImport{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SCase{}: - +ch = Bool.and(hot, Calls.calls(body, self)) - +ci = Bool.and(hot, Calls.calls(D1.inert.node(body), self)) - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, False{}), - UnitRule.walk(kids, None{}, self, path, False{}), UnitRule.walk(D1.inert.node(body), None{}, self, path, ci), - UnitRule.walk(body, None{}, self, path, ch), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i1, - Equal.trans(List<&2, F.Finding>, UnitRule.walk(D1.inert.node(body), None{}, self, path, ci), - UnitRule.walk(D1.inert.node(body), None{}, self, path, ch), UnitRule.walk(body, None{}, self, path, ch), - Equal.cong(Bool, List<&2, F.Finding>, z => UnitRule.walk(D1.inert.node(body), None{}, self, path, - Bool.and(hot, z)), Calls.calls(D1.inert.node(body), self), Calls.calls(body, self), - inert.calls(body, self, es, eb)), i3), i5) - case Tree.SFor{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SLet{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.STerm{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - -# unit's walk reads no string: an operator's text, a callee's against the -# multiplies and divides, and a number's against the one -law inert.unit.walk: - for nn: Tree.Node - for +prev: Maybe<&2, Lex.Tok> - for +self: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +ep: {inert.mfits(prev) == True{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {UnitRule.walk(D1.inert.node(nn), inert.mtok(prev), self, path, hot) == UnitRule.walk(nn, prev, self, path, hot) - : List<&2, F.Finding>} - -def inert.unit.walk(nn, prev, self, path, hot, es, ep, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - +tk = {Lex.Tok{k, t, l, c} : Lex.Tok} - +ir = D1.inert.node(rest) - +lhs = UnitRule.walk(D1.inert.node(Tree.NCons{Tree.Leaf{tk}, rest}), inert.mtok(prev), self, path, hot) - %inert.unit.walk(rest, UnitRule.after(tk), self, path, hot, es, inert.unit.mf_after(tk, - inert.ok_h(Tree.Leaf{tk}, rest, e)), inert.ok_t(Tree.Leaf{tk}, rest, e)) : {lhs - == List.append(&2, F.Finding, UnitRule.op_hit(tk, prev, UnitRule.first_tok(rest), path, hot), _) - : List<&2, F.Finding>} - %inert.unit.after(tk) : {lhs == List.append(&2, F.Finding, UnitRule.op_hit(tk, prev, - UnitRule.first_tok(rest), path, hot), UnitRule.walk(ir, _, self, path, hot)) : List<&2, F.Finding>} - %inert.unit.op_hit(hot, tk, prev, UnitRule.first_tok(rest), path) : {lhs == List.append(&2, F.Finding, _, - UnitRule.walk(ir, UnitRule.after(D1.inert.tok(tk)), self, path, hot)) : List<&2, F.Finding>} - %inert.unit.first_tok(rest) : {lhs == List.append(&2, F.Finding, UnitRule.op_hit(D1.inert.tok(tk), - inert.mtok(prev), _, path, hot), UnitRule.walk(ir, UnitRule.after(D1.inert.tok(tk)), self, path, hot)) - : List<&2, F.Finding>} - {==} - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +gp = {Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl} : Tree.Node} - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), UnitRule.walk(kids, None{}, - self, path, hot), - UnitRule.own_of(String.eq(ot, "("), UnitRule.callee(inert.mtok(prev)), D1.inert.node(kids), path, - hot), UnitRule.own_of(String.eq(ot, "("), UnitRule.callee(prev), kids, path, hot), - UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), UnitRule.walk(rest, None{}, self, path, - hot), - inert.unit.walk(kids, None{}, self, path, hot, es, {==}, inert.ok_h(gp, rest, e)), - inert.unit.callee(String.eq(ot, "("), hot, prev, kids, path, ep), - inert.unit.walk(rest, None{}, self, path, hot, es, {==}, inert.ok_t(gp, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es2 = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e) - inert.unit.stmt(sk, kids, body, rest, prev, self, path, hot, es, ek, eb, - inert.unit.walk(kids, None{}, self, path, False{}, es, {==}, ek), - inert.unit.walk(kids, None{}, self, path, hot, es, {==}, ek), - inert.unit.walk(body, None{}, self, path, Bool.and(hot, Calls.calls(body, self)), es, {==}, eb), - inert.unit.walk(body, None{}, self, path, hot, es, {==}, eb), - inert.unit.walk(rest, None{}, self, path, hot, es, {==}, er)) - case Tree.NNil{}: inert.unit.walk(rest, prev, self, path, hot, es, ep, e) - case Tree.NCons{x, y}: - inert.unit.walk(rest, prev, self, path, hot, es, ep, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# unit over the defs read that way reports what it reports over the defs -law inert.unit.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {UnitRule.check.go(inert.defs(ds), path, acc) == UnitRule.check.go(ds, path, acc) : List<&2, F.Finding>} - -def inert.unit.go(ds, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - D1.inert.node(sig))))), [], _u => UnitRule.walk(ib, None{}, name, path, True{})) - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, D1.inert.node(sig))))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - UnitRule.walk(ib, None{}, name, path, True{}), UnitRule.walk(body, None{}, name, path, True{}), - inert.gate(path, name, sig, body, en, eb), inert.unit.walk(body, None{}, name, path, True{}, en, {==}, eb)) : - {UnitRule.check.go(inert.defs(rest), path, lst <> acc) == UnitRule.check.go(rest, path, _ <> acc) - : List<&2, F.Finding>} - inert.unit.go(rest, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -def Laws.inert_unit(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => UnitRule.check.go(Calls.defs(z), path, []), tree, tree2, D1.inert.node(tree), - D1.inert.node(tree2), - inert.lift(z => UnitRule.check.go(z, path, []), tree, inert.unit.go(Calls.defs(tree), path, [], inert.dok.of(tree, - ok))), - inert.lift(z => UnitRule.check.go(z, path, []), tree2, inert.unit.go(Calls.defs(tree2), path, [], - inert.dok.of(tree2, ok2))), - e) - -# thunk -# ----- - -# a name is never cut, read in the order thunk tests it -law inert.thunk.name_code: - for k: Lex.TokKind - {Bool.and(Lex.is_name(k), D1.inert.blanks(k)) == False{} : Bool} +# a name is never cut, read in the order thunk tests it +law inert.thunk.name_code: + for k: Lex.TokKind + {Bool.and(Lex.is_name(k), D1.inert.blanks(k)) == False{} : Bool} def inert.thunk.name_code(k): match k: @@ -7097,81 +6328,236 @@ def Laws.inert_fuel(path, _text, tree, _toks, _bound, _items, _text2, tree2, _to # ring # ---- -# a chain read that way is one number as it was -law inert.ring.is_num: - for nn: Tree.Node - {Ring.is_num(D1.inert.node(nn)) == Ring.is_num(nn) : Bool} +# a leaf, a group and the rest as the lexer makes them: each fits +law inert.ok3: + for +a: Tree.Node + for +g: Tree.Node + for +r: Tree.Node + for e: {D1.inert.ok(Tree.NCons{a, Tree.NCons{g, r}}) == True{} : Bool} + {Bool.and(D1.inert.ok(a), Bool.and(D1.inert.ok(g), D1.inert.ok(r))) == True{} : Bool} -def inert.ring.is_num(nn): - match nn: +def inert.ok3(_a, _g, _r, e): + e + +# a case statement's pattern read that way is the pattern it had, read that way +law inert.pattern: + for kids: Tree.Node + {T.pattern(D1.inert.node(kids)) == D1.inert.node(T.pattern(kids)) : Tree.Node} + +def inert.pattern(kids): + match kids: + case Tree.NCons{h, rest}: {==} + case Tree.Leaf{tok}: {==} + case Tree.Group{o, gk, cl}: {==} + case Tree.Stmt{sk, sks, sb}: {==} + case Tree.NNil{}: {==} + +# a from-k-up arm whose binder is read that way: a binder is never cut +law inert.arms.bpick: + for x: Bool + for b: Lex.TokKind + for +p: String + for -f: String -> Arms.Arm + {Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(D1.inert.keep(D1.inert.blanks(b), p)), Arms.AOther{}) + == Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(p), Arms.AOther{}) : Arms.Arm} + +def inert.arms.bpick(x, b, _p, _f): + match x: + case False{}: {==} + case True{}: + match b: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + +# what follows a Nat literal read that way makes the arm it made +law inert.arms.shape: + for rest: Tree.Node + for +kk: U32 + for +tt: String + for +line: U32 + for +col: U32 + {Arms.lit.shape(D1.inert.node(rest), kk, tt, line, col) == Arms.lit.shape(rest, kk, tt, line, col) : Arms.Arm} + +def inert.arms.shape(rest, kk, tt, line, col): + match rest: case Tree.NCons{h, +r}: match h: case Tree.Leaf{tok}: match tok: - case Lex.Tok{k, t, l, c}: + case Lex.Tok{k, +o, l, c}: match k: case Lex.TName{}: {==} case Lex.TUpper{}: {==} case Lex.TDotted{}: {==} case Lex.TWild{}: {==} case Lex.TKey{}: {==} - case Lex.TNum{}: - match r: - case Tree.NNil{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} + case Lex.TNum{}: {==} case Lex.TStr{}: {==} case Lex.TChar{}: {==} case Lex.TComment{}: {==} case Lex.TSpace{}: {==} case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a chain read that way is the one name it was -law inert.ring.name_text: - for nn: Tree.Node - {Ring.name_text(D1.inert.node(nn)) == Ring.name_text(nn) : String} - -def inert.ring.name_text(nn): - match nn: - case Tree.NCons{h, +r}: + case Lex.TOp{}: + match r: + case Tree.NCons{h2, +r2}: + match h2: + case Tree.Leaf{tok2}: + match tok2: + case Lex.Tok{+b, +p, l2, c2}: + match r2: + case Tree.NCons{h3, r3}: + match h3: + case Tree.Leaf{tok3}: + match tok3: + case Lex.Tok{k3, s, l3, c3}: + match k3: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: + inert.arms.bpick(String.eq(o, "+"), b, p, z => Arms.APlus{kk, + tt ++ "+" ++ z, line, col, T.width(tt)}) + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Lex.TColon{}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + +# a Nat literal's arm, what follows it read that way +law inert.arms.lit: + for mm: Maybe<&2, U32> + for +tt: String + for +rest: Tree.Node + for +line: U32 + for +col: U32 + {Arms.lit.of(mm, tt, D1.inert.node(rest), line, col) == Arms.lit.of(mm, tt, rest, line, col) : Arms.Arm} + +def inert.arms.lit(mm, tt, rest, line, col): + match mm: + case None{}: {==} + case Some{+k}: inert.arms.shape(rest, k, tt, line, col) + +# a case pattern read that way makes the arm it made: a Nat literal, a capital +# name and a binder are never cut +law inert.arms.arm: + for pat: Tree.Node + {Arms.arm(D1.inert.node(pat)) == Arms.arm(pat) : Arms.Arm} + +def inert.arms.arm(pat): + match pat: + case Tree.NCons{h, +rest}: match h: case Tree.Leaf{tok}: match tok: - case Lex.Tok{k, t, l, c}: + case Lex.Tok{k, +t, +l, +c}: match k: - case Lex.TName{}: - match r: - case Tree.NNil{}: {==} + case Lex.TName{}: {==} + case Lex.TUpper{}: + match rest: + case Tree.NCons{g, more}: + match g: + case Tree.Group{op, gk, cl}: + match gk: + case Tree.NCons{h3, +r3}: + match h3: + case Tree.Leaf{tok3}: + match tok3: + case Lex.Tok{+k3, +p, l2, c2}: + match r3: + case Tree.NNil{}: + inert.arms.bpick(String.eq(t, "Succ"), k3, p, z => Arms.APlus{1, + "Succ{" ++ z ++ "}", l, c, 4}) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.Leaf{x}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} case Tree.Leaf{x}: {==} case Tree.Group{x, y, z}: {==} case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} + case Tree.NNil{}: {==} case Lex.TDotted{}: {==} case Lex.TWild{}: {==} case Lex.TKey{}: {==} - case Lex.TNum{}: {==} + case Lex.TNum{}: inert.arms.lit(T.nat_value(t), t, rest, l, c) case Lex.TStr{}: {==} case Lex.TChar{}: {==} case Lex.TComment{}: {==} @@ -7188,6227 +6574,609 @@ def inert.ring.name_text(nn): case Lex.TOpen{}: {==} case Lex.TClose{}: {==} case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} case Tree.NNil{}: {==} case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} case Tree.NNil{}: {==} -# arguments read that way name a window as they did -law inert.ring.has_name: - for as: List<&2, Tree.Node> - for +wins: List<&2, String> - {Ring.has_name(inert.nodes(as), wins) == Ring.has_name(as, wins) : Bool} - -def inert.ring.has_name(as, wins): - match as: - case Nil{}: {==} - case Con{+h, +rest}: - +lhs = Ring.has_name(inert.nodes(Con{h, rest}), wins) - %inert.ring.has_name(rest, wins) : {lhs == Bool.or(Ring.win_name(h, wins), _) : Bool} - %inert.ring.name_text(h) : {lhs == Bool.or(Bool.and(Bool.not(String.is_empty(_)), List.contains(~String, - ~String.eq, wins, _)), Ring.has_name(inert.nodes(rest), wins)) : Bool} - {==} - -# arguments read that way end with a number as they did -law inert.ring.last_num: - for as: List<&2, Tree.Node> - {Ring.last_num(inert.nodes(as)) == Ring.last_num(as) : Bool} - -def inert.ring.last_num(as): - match as: - case Nil{}: {==} - case Con{h, +rest}: - match rest: - case Nil{}: inert.ring.is_num(h) - case Con{x, y}: inert.ring.last_num(rest) - -# what drop_ok reads: the name among the drops and tails, the window named, -# the name a tail, the count a number -def inert.ring.dparts(kd: Bool, hn: Bool, +tl: Bool, ln: Bool) -> Bool: - Bool.and(kd, Bool.and(hn, Bool.or(tl, ln))) - -# the drops and tails -def inert.ring.drops() -> List<&2, String>: - ["List.drop", "String.drop", "List.tail", "String.tail"] - -# a call's name and arguments read that way are the fixed drop they were -law inert.ring.drop_ok: - for +b: Bool - for +t: String - for +as: List<&2, Tree.Node> - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Ring.drop_ok(D1.inert.keep(b, t), inert.nodes(as), wins) == Ring.drop_ok(t, as, wins) : Bool} - -def inert.ring.drop_ok(b, t, as, wins, ef): - +kt = D1.inert.keep(b, t) - +lhs = Ring.drop_ok(kt, inert.nodes(as), wins) - %inert.contains_keep(inert.ring.drops(), b, t, ef, {==}) : {lhs == inert.ring.dparts(_, Ring.has_name(as, wins), - Bool.or(String.eq(t, "List.tail"), String.eq(t, "String.tail")), Ring.last_num(as)) : Bool} - %M1.inert.eq_keep(b, t, "List.tail", ef, {==}) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, - inert.ring.drops(), kt), Ring.has_name(as, wins), Bool.or(_, String.eq(t, "String.tail")), Ring.last_num(as)) - : Bool} - %M1.inert.eq_keep(b, t, "String.tail", ef, {==}) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, - inert.ring.drops(), kt), Ring.has_name(as, wins), Bool.or(String.eq(kt, "List.tail"), _), Ring.last_num(as)) - : Bool} - %inert.ring.has_name(as, wins) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, inert.ring.drops(), - kt), _, Bool.or(String.eq(kt, "List.tail"), String.eq(kt, "String.tail")), Ring.last_num(as)) : Bool} - %inert.ring.last_num(as) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, inert.ring.drops(), kt), - Ring.has_name(inert.nodes(as), wins), Bool.or(String.eq(kt, "List.tail"), String.eq(kt, "String.tail")), _) - : Bool} - {==} - -# a drop's token is kept only for a drop's name, which is never cut -law inert.ring.drop_pick: - for b: Bool - for +t: String - for +k: Lex.TokKind - for +l: U32 - for +c: U32 - for +as: List<&2, Tree.Node> - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Bool.pick(Maybe<&2, Lex.Tok>, Ring.drop_ok(t, as, wins), Some{Lex.Tok{k, D1.inert.keep(b, t), l, c}}, None{}) - == Bool.pick(Maybe<&2, Lex.Tok>, Ring.drop_ok(t, as, wins), Some{Lex.Tok{k, t, l, c}}, None{}) - : Maybe<&2, Lex.Tok>} - -def inert.ring.drop_pick(b, t, k, l, c, as, wins, ef): - match b: - case False{}: {==} - case True{}: - +tl = Bool.or(String.eq(t, "List.tail"), String.eq(t, "String.tail")) - %Equal.sym(Bool, List.contains(~String, ~String.eq, inert.ring.drops(), t), False{}, - inert.contains_marked(inert.ring.drops(), t, {==}, ef)) : {Bool.pick(Maybe<&2, Lex.Tok>, - inert.ring.dparts(_, Ring.has_name(as, wins), tl, Ring.last_num(as)), Some{Lex.Tok{k, D1.inert.cut(t), l, - c}}, None{}) == Bool.pick(Maybe<&2, Lex.Tok>, inert.ring.dparts(_, Ring.has_name(as, wins), tl, - Ring.last_num(as)), Some{Lex.Tok{k, t, l, c}}, None{}) : Maybe<&2, Lex.Tok>} - {==} - -# a call read that way is the fixed drop it was -law inert.ring.drop_tok: - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - {Ring.drop_tok(k, D1.inert.keep(D1.inert.blanks(k), t), l, c, D1.inert.node(kids), wins) - == Ring.drop_tok(k, t, l, c, kids, wins) : Maybe<&2, Lex.Tok>} - -def inert.ring.drop_tok(k, t, l, c, kids, wins, ef): - +b = D1.inert.blanks(k) - +kt = D1.inert.keep(b, t) - +aa = Calls.args(kids) - +lhs = Ring.drop_tok(k, kt, l, c, D1.inert.node(kids), wins) - %inert.ring.drop_pick(b, t, k, l, c, aa, wins, ef) : {lhs == _ : Maybe<&2, Lex.Tok>} - %inert.ring.drop_ok(b, t, aa, wins, ef) : {lhs == Bool.pick(Maybe<&2, Lex.Tok>, _, Some{Lex.Tok{k, kt, l, c}}, - None{}) : Maybe<&2, Lex.Tok>} - %inert.args(kids) : {lhs == Bool.pick(Maybe<&2, Lex.Tok>, Ring.drop_ok(kt, _, wins), Some{Lex.Tok{k, kt, l, c}}, - None{}) : Maybe<&2, Lex.Tok>} - {==} - -# an application read that way is the drop it was -law inert.ring.here_of: - for open: Bool - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - {Ring.here_of(open, k, D1.inert.keep(D1.inert.blanks(k), t), l, c, D1.inert.node(kids), wins) - == Ring.here_of(open, k, t, l, c, kids, wins) : Maybe<&2, Lex.Tok>} - -def inert.ring.here_of(open, k, t, l, c, kids, wins, ef): - match open: - case False{}: {==} - case True{}: inert.ring.drop_tok(k, t, l, c, kids, wins, ef) - -# two maybes read as they did give the first of them as it did -law inert.ring.or2: - for -a1: Maybe<&2, Lex.Tok> - for -a2: Maybe<&2, Lex.Tok> - for -b1: Maybe<&2, Lex.Tok> - for -b2: Maybe<&2, Lex.Tok> - for ea: {a1 == a2 : Maybe<&2, Lex.Tok>} - for eb: {b1 == b2 : Maybe<&2, Lex.Tok>} - {Ring.or_tok(a1, b1) == Ring.or_tok(a2, b2) : Maybe<&2, Lex.Tok>} - -def inert.ring.or2(a1, _a2, b1, b2, ea, eb): - %ea : {Ring.or_tok(a1, b1) == Ring.or_tok(_, b2) : Maybe<&2, Lex.Tok>} - %eb : {Ring.or_tok(a1, b1) == Ring.or_tok(a1, _) : Maybe<&2, Lex.Tok>} - {==} - -# a drop anywhere under a node read that way is the drop it was -law inert.ring.drop_in: - for nn: Tree.Node - for +wins: List<&2, String> - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Ring.drop_in(D1.inert.node(nn), wins) == Ring.drop_in(nn, wins) : Maybe<&2, Lex.Tok>} +# a match statement read that way has one scrutinee as it had: only a +# keyword's text is read +law inert.arms.single: + for kids: Tree.Node + {Arms.single(D1.inert.node(kids)) == Arms.single(kids) : Bool} -def inert.ring.drop_in(nn, wins, e): - match nn: - case Tree.NCons{h, +rest}: +def inert.arms.single(kids): + match kids: + case Tree.NCons{h, +r}: match h: case Tree.Leaf{tok}: match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.ring.or2(Ring.here_of(String.eq(ot, "("), k, D1.inert.keep(D1.inert.blanks(k), t), l, - c, D1.inert.node(kids), wins), Ring.here_of(String.eq(ot, "("), k, t, l, c, kids, wins), - Ring.or_tok(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(D1.inert.node(r2), - wins)), Ring.or_tok(Ring.drop_in(kids, wins), Ring.drop_in(r2, wins)), - inert.ring.here_of(String.eq(ot, "("), k, t, l, c, kids, wins, inert.ok_h(lf, - Tree.NCons{gp, r2}, e)), - inert.ring.or2(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - Ring.drop_in(D1.inert.node(r2), wins), Ring.drop_in(r2, wins), - inert.ring.drop_in(kids, wins, inert.ok_h(gp, r2, eg)), - inert.ring.drop_in(r2, wins, inert.ok_t(gp, r2, eg)))) - case Tree.Leaf{x}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.ring.or2(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - Ring.drop_in(D1.inert.node(rest), wins), Ring.drop_in(rest, wins), - inert.ring.drop_in(kids, wins, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.ring.or2(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - Ring.or_tok(Ring.drop_in(D1.inert.node(body), wins), Ring.drop_in(D1.inert.node(rest), wins)), - Ring.or_tok(Ring.drop_in(body, wins), Ring.drop_in(rest, wins)), - inert.ring.drop_in(kids, wins, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.or2(Ring.drop_in(D1.inert.node(body), wins), Ring.drop_in(body, wins), - Ring.drop_in(D1.inert.node(rest), wins), Ring.drop_in(rest, wins), - inert.ring.drop_in(body, wins, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e)))) - case Tree.NNil{}: inert.ring.drop_in(rest, wins, e) - case Tree.NCons{x, y}: inert.ring.drop_in(rest, wins, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the receiver of an append, its arguments read that way -law inert.ring.receiver_is: - for list: Bool - for str: Bool - for +as: List<&2, Tree.Node> - for +wins: List<&2, String> - for +ea: {inert.noks(as) == True{} : Bool} - {Ring.receiver_is(list, str, inert.nodes(as), wins) == Ring.receiver_is(list, str, as, wins) : Maybe<&2, Lex.Tok>} - -def inert.ring.receiver_is(list, str, as, wins, ea): - match list str: - case True{} s: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), 2n), D1.inert.node(Calls.arg(as, 2n)), inert.arg(as, 2n)) : - {Ring.drop_in(_, wins) == Ring.drop_in(Calls.arg(as, 2n), wins) : Maybe<&2, Lex.Tok>} - inert.ring.drop_in(Calls.arg(as, 2n), wins, inert.ok.nth(as, 2n, ea)) - case False{} True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), 0n), D1.inert.node(Calls.arg(as, 0n)), inert.arg(as, 0n)) : - {Ring.drop_in(_, wins) == Ring.drop_in(Calls.arg(as, 0n), wins) : Maybe<&2, Lex.Tok>} - inert.ring.drop_in(Calls.arg(as, 0n), wins, inert.ok.nth(as, 0n, ea)) - case False{} False{}: {==} - -# the receiver of an append, the call's name and arguments read that way -law inert.ring.receiver: - for +b: Bool - for +t: String - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Ring.receiver(D1.inert.keep(b, t), D1.inert.node(kids), wins) == Ring.receiver(t, kids, wins) - : Maybe<&2, Lex.Tok>} - -def inert.ring.receiver(b, t, kids, wins, ef, ek): - +kt = D1.inert.keep(b, t) - +aa = Calls.args(kids) - +lhs = Ring.receiver(kt, D1.inert.node(kids), wins) - %inert.ring.receiver_is(String.eq(t, "List.append"), String.eq(t, "String.append"), aa, wins, - inert.ok.args.go(kids, Tree.NNil{}, [], ek, {==}, {==})) : {lhs == _ : Maybe<&2, Lex.Tok>} - %M1.inert.eq_keep(b, t, "List.append", ef, {==}) : {lhs == Ring.receiver_is(_, String.eq(t, "String.append"), - inert.nodes(aa), wins) : Maybe<&2, Lex.Tok>} - %M1.inert.eq_keep(b, t, "String.append", ef, {==}) : {lhs == Ring.receiver_is(String.eq(kt, "List.append"), _, - inert.nodes(aa), wins) : Maybe<&2, Lex.Tok>} - %inert.args(kids) : {lhs == Ring.receiver_is(String.eq(kt, "List.append"), String.eq(kt, "String.append"), _, wins) - : Maybe<&2, Lex.Tok>} - {==} + case Lex.Tok{k, t, l, c}: + match k: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: + match r: + case Tree.NCons{x, +r2}: + match r2: + case Tree.NCons{h3, +r3}: + match h3: + case Tree.Leaf{tok3}: + match tok3: + case Lex.Tok{k3, s, l3, c3}: + match k3: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: + match r3: + case Tree.NNil{}: {==} + case Tree.Leaf{y}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NCons{y, z}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{y, z}: {==} + case Tree.Leaf{y}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NNil{}: {==} + case Tree.Leaf{y}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NNil{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# an application's receiver, read that way -law inert.ring.call_recv: - for open: Bool - for +b: Bool - for +t: String +# a case's arm, its tokens read that way +law inert.arms.case: for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Ring.call_recv(open, D1.inert.keep(b, t), D1.inert.node(kids), wins) == Ring.call_recv(open, t, kids, wins) - : Maybe<&2, Lex.Tok>} + {Arms.arm(T.pattern(D1.inert.node(kids))) == Arms.arm(T.pattern(kids)) : Arms.Arm} -def inert.ring.call_recv(open, b, t, kids, wins, ef, ek): - match open: - case False{}: {==} - case True{}: inert.ring.receiver(b, t, kids, wins, ef, ek) +def inert.arms.case(kids): + Equal.trans(Arms.Arm, Arms.arm(T.pattern(D1.inert.node(kids))), Arms.arm(D1.inert.node(T.pattern(kids))), + Arms.arm(T.pattern(kids)), Equal.cong(Tree.Node, Arms.Arm, z => Arms.arm(z), T.pattern(D1.inert.node(kids)), + D1.inert.node(T.pattern(kids)), inert.pattern(kids)), inert.arms.arm(T.pattern(kids))) -# the drop a call or a bracket is, read that way -law inert.ring.call_drop: - for open: Bool - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Ring.call_drop(open, k, D1.inert.keep(D1.inert.blanks(k), t), l, c, D1.inert.node(kids), wins) - == Ring.call_drop(open, k, t, l, c, kids, wins) : Maybe<&2, Lex.Tok>} +# a match's arms read that way are judged as they were +law inert.arms.arms: + for body: Tree.Node + for +wide: Maybe<&2, Arms.Wide> + for +path: String + {Arms.arms(D1.inert.node(body), wide, path) == Arms.arms(body, wide, path) : List<&2, F.Finding>} + +def inert.arms.arms(body, wide, path): + match body: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, b}: + match sk: + case Tree.SCase{}: + +ao = Arms.arm(T.pattern(kids)) + +ai = Arms.arm(T.pattern(D1.inert.node(kids))) + +lhs = Arms.verdict(wide, ai, path, Arms.arms(D1.inert.node(rest), Arms.widen(wide, ai), path)) + %inert.arms.arms(rest, Arms.widen(wide, ao), path) : {lhs == Arms.verdict(wide, ao, path, _) + : List<&2, F.Finding>} + %inert.arms.case(kids) : {lhs == Arms.verdict(wide, _, path, Arms.arms(D1.inert.node(rest), + Arms.widen(wide, _), path)) : List<&2, F.Finding>} + {==} + case Tree.SDef{}: inert.arms.arms(rest, wide, path) + case Tree.SType{}: inert.arms.arms(rest, wide, path) + case Tree.SLaw{}: inert.arms.arms(rest, wide, path) + case Tree.SImport{}: inert.arms.arms(rest, wide, path) + case Tree.SFor{}: inert.arms.arms(rest, wide, path) + case Tree.SLet{}: inert.arms.arms(rest, wide, path) + case Tree.STerm{}: inert.arms.arms(rest, wide, path) + case Tree.Leaf{x}: inert.arms.arms(rest, wide, path) + case Tree.Group{x, y, z}: inert.arms.arms(rest, wide, path) + case Tree.NNil{}: inert.arms.arms(rest, wide, path) + case Tree.NCons{x, y}: inert.arms.arms(rest, wide, path) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -def inert.ring.call_drop(open, k, t, l, c, kids, wins, ef, ek): - match open: - case False{}: inert.ring.drop_in(kids, wins, ek) - case True{}: inert.ring.drop_tok(k, t, l, c, kids, wins, ef) +# arms reads no string: a Nat literal, `Succ`, `match` and `+` are read by +# their kind, and a binder by its +law inert.arms.walk: + for nn: Tree.Node + for +path: String + {Arms.walk(D1.inert.node(nn), path) == Arms.walk(nn, path) : List<&2, F.Finding>} -# a leaf, a group and the rest as the lexer makes them: each fits -law inert.ok3: - for +a: Tree.Node - for +g: Tree.Node - for +r: Tree.Node - for e: {D1.inert.ok(Tree.NCons{a, Tree.NCons{g, r}}) == True{} : Bool} - {Bool.and(D1.inert.ok(a), Bool.and(D1.inert.ok(g), D1.inert.ok(r))) == True{} : Bool} +def inert.arms.walk(nn, path): + match nn: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, +body}: + inert.cat3(Lazy.stop(List<&2, F.Finding>, Bool.not(Arms.single(D1.inert.node(kids))), [], + _u => Arms.arms(D1.inert.node(body), None{}, path)), Lazy.stop(List<&2, F.Finding>, + Bool.not(Arms.single(kids)), [], _u => Arms.arms(body, None{}, path)), + Arms.walk(D1.inert.node(body), path), Arms.walk(body, path), Arms.walk(D1.inert.node(rest), path), + Arms.walk(rest, path), + inert.stop(Bool.not(Arms.single(D1.inert.node(kids))), Bool.not(Arms.single(kids)), + Arms.arms(D1.inert.node(body), None{}, path), Arms.arms(body, None{}, path), + Equal.cong(Bool, Bool, z => Bool.not(z), Arms.single(D1.inert.node(kids)), Arms.single(kids), + inert.arms.single(kids)), inert.arms.arms(body, None{}, path)), + inert.arms.walk(body, path), inert.arms.walk(rest, path)) + case Tree.Leaf{x}: inert.arms.walk(rest, path) + case Tree.Group{x, y, z}: inert.arms.walk(rest, path) + case Tree.NNil{}: inert.arms.walk(rest, path) + case Tree.NCons{x, y}: inert.arms.walk(rest, path) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -def inert.ok3(_a, _g, _r, e): - e +def Laws.inert_arms(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, _ok, _ok2, e): + M1.inert.via(Tree.Node, z => Arms.walk(z, path), tree, tree2, D1.inert.node(tree), D1.inert.node(tree2), + inert.arms.walk(tree, path), inert.arms.walk(tree2, path), e) -# a name then a group, read that way: the scan finds the drop it found -law inert.ring.ps_lg: - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +seen: Maybe<&2, Lex.Tok> - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Ring.plus_scan(D1.inert.node(kids), None{}, wins) == Ring.plus_scan(kids, None{}, wins) - : Maybe<&2, Lex.Tok>} - for ir: {Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, t, l, c, kids, - wins)), wins) == Ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, t, l, c, kids, wins)), - wins) : Maybe<&2, Lex.Tok>} - {Ring.or_tok(Ring.call_recv(String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), - wins), Ring.or_tok(Ring.plus_scan(D1.inert.node(kids), None{}, wins), Ring.plus_scan(D1.inert.node(r2), - Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, D1.inert.keep(D1.inert.blanks(k), t), l, c, - D1.inert.node(kids), wins)), wins))) - == Ring.or_tok(Ring.call_recv(String.eq(o, "("), t, kids, wins), Ring.or_tok(Ring.plus_scan(kids, None{}, wins), - Ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, t, l, c, kids, wins)), wins))) - : Maybe<&2, Lex.Tok>} +# unused +# ------ -def inert.ring.ps_lg(k, t, l, c, o, kids, r2, seen, wins, ef, ek, ik, ir): - +op = String.eq(o, "(") - +kt = D1.inert.keep(D1.inert.blanks(k), t) - +ki = D1.inert.node(kids) - +di = Ring.call_drop(op, k, kt, l, c, ki, wins) - +lhs = Ring.or_tok(Ring.call_recv(op, kt, ki, wins), Ring.or_tok(Ring.plus_scan(ki, None{}, wins), - Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, di), wins))) - %ir : {lhs == Ring.or_tok(Ring.call_recv(op, t, kids, wins), Ring.or_tok(Ring.plus_scan(kids, None{}, wins), _)) - : Maybe<&2, Lex.Tok>} - %inert.ring.call_drop(op, k, t, l, c, kids, wins, ef, ek) : {lhs == Ring.or_tok(Ring.call_recv(op, t, kids, wins), - Ring.or_tok(Ring.plus_scan(kids, None{}, wins), Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, _), wins))) - : Maybe<&2, Lex.Tok>} - %ik : {lhs == Ring.or_tok(Ring.call_recv(op, t, kids, wins), Ring.or_tok(_, Ring.plus_scan(D1.inert.node(r2), - Ring.or_tok(seen, di), wins))) : Maybe<&2, Lex.Tok>} - %inert.ring.call_recv(op, D1.inert.blanks(k), t, kids, wins, ef, ek) : {lhs == Ring.or_tok(_, Ring.or_tok( - Ring.plus_scan(ki, None{}, wins), Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, di), wins))) - : Maybe<&2, Lex.Tok>} - {==} +# a token by where it stands only +def inert.canon(tok: Lex.Tok) -> Lex.Tok: + match tok: + case Lex.Tok{k, t, l, c}: Lex.Tok{Lex.TSpace{}, "", l, c} -# an operator: the scan past it, read that way, finds what it found -law inert.ring.ps_op: - for +t: String - for +seen: Maybe<&2, Lex.Tok> - for +rest: Tree.Node - for +wins: List<&2, String> - for ih: {Ring.plus_scan(D1.inert.node(rest), Ring.pp_seen(String.eq(t, "++"), seen), wins) - == Ring.plus_scan(rest, Ring.pp_seen(String.eq(t, "++"), seen), wins) : Maybe<&2, Lex.Tok>} - {Ring.or_tok(Ring.pp_found(String.eq(t, "++"), seen), Ring.plus_scan(D1.inert.node(rest), - Ring.pp_seen(String.eq(t, "++"), seen), wins)) - == Ring.or_tok(Ring.pp_found(String.eq(t, "++"), seen), Ring.plus_scan(rest, Ring.pp_seen(String.eq(t, "++"), - seen), wins)) : Maybe<&2, Lex.Tok>} - -def inert.ring.ps_op(t, seen, rest, wins, ih): - Equal.cong(Maybe<&2, Lex.Tok>, Maybe<&2, Lex.Tok>, z => Ring.or_tok(Ring.pp_found(String.eq(t, "++"), seen), z), - Ring.plus_scan(D1.inert.node(rest), Ring.pp_seen(String.eq(t, "++"), seen), wins), - Ring.plus_scan(rest, Ring.pp_seen(String.eq(t, "++"), seen), wins), ih) - -# ring's scan reads no string: an operator's text, a drop's or an append's -# name against the few it knows, a window's name -law inert.ring.plus_scan: +# the tokens, each by where it stands only +def inert.canons(ts: List<&2, Lex.Tok>) -> List<&2, Lex.Tok>: + match ts: + case Nil{}: Nil{} + case Con{h, t}: inert.canon(h) <> inert.canons(t) + +# a token read that way stands where it stood +law inert.canon_tok: + for tok: Lex.Tok + {inert.canon(D1.inert.tok(tok)) == inert.canon(tok) : Lex.Tok} + +def inert.canon_tok(tok): + match tok: + case Lex.Tok{k, t, l, c}: {==} + +# a group's close onto tokens that stand alike +law inert.canon_close: + for close: Maybe<&2, Lex.Tok> + for -x: List<&2, Lex.Tok> + for -y: List<&2, Lex.Tok> + for e: {inert.canons(x) == inert.canons(y) : List<&2, Lex.Tok>} + {inert.canons(Tree.leaf_close(close, x)) == inert.canons(Tree.leaf_close(close, y)) : List<&2, Lex.Tok>} + +def inert.canon_close(close, x, y, e): + match close: + case None{}: e + case Some{+tok}: + Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => inert.canon(tok) <> z, inert.canons(x), inert.canons(y), + e) + +# the leaves of a node read that way stand where they stood +law inert.canon_leaves: for nn: Tree.Node - for +seen: Maybe<&2, Lex.Tok> - for +wins: List<&2, String> - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Ring.plus_scan(D1.inert.node(nn), seen, wins) == Ring.plus_scan(nn, seen, wins) : Maybe<&2, Lex.Tok>} + for -a: List<&2, Lex.Tok> + for -b: List<&2, Lex.Tok> + for e: {inert.canons(a) == inert.canons(b) : List<&2, Lex.Tok>} + {inert.canons(Tree.leaves.go(D1.inert.node(nn), a)) == inert.canons(Tree.leaves.go(nn, b)) : List<&2, Lex.Tok>} -def inert.ring.plus_scan(nn, seen, wins, e): +def inert.canon_leaves(nn, a, b, e): match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TName{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TName{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TUpper{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TUpper{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TUpper{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TDotted{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TDotted{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TDotted{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TWild{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TWild{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TWild{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TKey{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TKey{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TKey{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNum{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TNum{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TNum{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TStr{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TStr{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TStr{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TChar{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TChar{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TChar{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComment{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TComment{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TComment{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TSpace{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TSpace{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TSpace{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNewline{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TNewline{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TNewline{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Leaf{x}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Stmt{x, y, z}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.NNil{}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.NCons{x, y}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Group{x, y, z}: - match x: - case Lex.Tok{a1, a2, a3, a4}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Leaf{x}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Group{x, y, z}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Stmt{x, y, z}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.NNil{}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Lex.TColon{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TColon{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TColon{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TEq{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TEq{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TEq{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TBind{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TBind{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TBind{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TArrow{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TArrow{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TArrow{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TLam{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TLam{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TLam{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAll{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TAll{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TAll{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAmp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TAmp{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TAmp{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOpen{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TOpen{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TOpen{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TClose{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TClose{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TClose{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComma{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TComma{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TComma{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.ring.or2(Ring.plus_scan(D1.inert.node(kids), None{}, wins), Ring.plus_scan(kids, None{}, wins), - Ring.plus_scan(D1.inert.node(rest), Ring.or_tok(seen, Ring.drop_in(D1.inert.node(kids), wins)), wins), - Ring.plus_scan(rest, Ring.or_tok(seen, Ring.drop_in(kids, wins)), wins), - inert.ring.plus_scan(kids, None{}, wins, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - Equal.trans(Maybe<&2, Lex.Tok>, Ring.plus_scan(D1.inert.node(rest), Ring.or_tok(seen, - Ring.drop_in(D1.inert.node(kids), wins)), wins), Ring.plus_scan(D1.inert.node(rest), - Ring.or_tok(seen, Ring.drop_in(kids, wins)), wins), Ring.plus_scan(rest, Ring.or_tok(seen, - Ring.drop_in(kids, wins)), wins), - Equal.cong(Maybe<&2, Lex.Tok>, Maybe<&2, Lex.Tok>, z => Ring.plus_scan(D1.inert.node(rest), - Ring.or_tok(seen, z), wins), Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - inert.ring.drop_in(kids, wins, inert.ok_h(Tree.Group{o, kids, cl}, rest, e))), - inert.ring.plus_scan(rest, Ring.or_tok(seen, Ring.drop_in(kids, wins)), wins, - inert.ok_t(Tree.Group{o, kids, cl}, rest, e)))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.ring.or2(Ring.plus_scan(D1.inert.node(kids), None{}, wins), Ring.plus_scan(kids, None{}, wins), - Ring.or_tok(Ring.plus_scan(D1.inert.node(body), None{}, wins), Ring.plus_scan(D1.inert.node(rest), - seen, wins)), Ring.or_tok(Ring.plus_scan(body, None{}, wins), Ring.plus_scan(rest, seen, wins)), - inert.ring.plus_scan(kids, None{}, wins, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.or2(Ring.plus_scan(D1.inert.node(body), None{}, wins), Ring.plus_scan(body, None{}, wins), - Ring.plus_scan(D1.inert.node(rest), seen, wins), Ring.plus_scan(rest, seen, wins), - inert.ring.plus_scan(body, None{}, wins, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e)))) - case Tree.NNil{}: inert.ring.plus_scan(rest, seen, wins, e) - case Tree.NCons{x, y}: inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# every self-call argument that slides a window, the arguments read that way -law inert.ring.slides: - for as: List<&2, Tree.Node> - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - for +e: {inert.noks(as) == True{} : Bool} - {Ring.slides(inert.nodes(as), params, path, wins) == Ring.slides(as, params, path, wins) : List<&2, F.Finding>} - -def inert.ring.slides(as, params, path, wins, e): - match as: - case Nil{}: {==} - case Con{+h, +rest}: - +sw = Ring.slot_wins(Maybe.default(&2, String, List.head(&2, String, params), ""), wins) - +pt = List.tail(&2, String, params) - +lhs = Ring.slides(inert.nodes(Con{h, rest}), params, path, wins) - %inert.ring.slides(rest, pt, path, wins, M1.inert.and_r(D1.inert.ok(h), inert.noks(rest), e)) : {lhs - == List.append(&2, F.Finding, Ring.slide(h, path, sw), _) : List<&2, F.Finding>} - %inert.ring.plus_scan(h, None{}, sw, M1.inert.and_l(D1.inert.ok(h), inert.noks(rest), e)) : {lhs - == List.append(&2, F.Finding, Ring.slide_at(_, path), Ring.slides(inert.nodes(rest), pt, path, wins)) - : List<&2, F.Finding>} - {==} - -# a name then a group: a self-call's sliding arguments, the group and the rest -# walked as they were -law inert.ring.lg: - for +b: Bool - for +t: String - for +o: String - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - for +kids: Tree.Node - for +r2: Tree.Node - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +es: {D1.inert.marked(self) == False{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Ring.walk(D1.inert.node(kids), self, params, path, wins) == Ring.walk(kids, self, params, path, wins) - : List<&2, F.Finding>} - for ir: {Ring.walk(D1.inert.node(r2), self, params, path, wins) == Ring.walk(r2, self, params, path, wins) - : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), - String.eq(D1.inert.keep(b, t), self))), [], _u => Ring.slides(Calls.args(D1.inert.node(kids)), params, path, - wins)), Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(D1.inert.node(r2), self, params, - path, wins)]) - == List.concat(&2, F.Finding, [Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), - String.eq(t, self))), [], _u => Ring.slides(Calls.args(kids), params, path, wins)), Ring.walk(kids, self, params, - path, wins), Ring.walk(r2, self, params, path, wins)]) : List<&2, F.Finding>} - -def inert.ring.lg(b, t, o, self, params, path, wins, kids, r2, ef, es, ek, ik, ir): - +aa = Calls.args(kids) - +hi = Ring.slides(Calls.args(D1.inert.node(kids)), params, path, wins) - +eh = Equal.trans(List<&2, F.Finding>, hi, Ring.slides(inert.nodes(aa), params, path, wins), Ring.slides(aa, params, - path, wins), Equal.cong(List<&2, Tree.Node>, List<&2, F.Finding>, z => Ring.slides(z, params, path, wins), - Calls.args(D1.inert.node(kids)), inert.nodes(aa), inert.args(kids)), - inert.ring.slides(aa, params, path, wins, inert.ok.args.go(kids, Tree.NNil{}, [], ek, {==}, {==}))) - inert.cat3(Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), String.eq(D1.inert.keep(b, t), - self))), [], _u => hi), Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), String.eq(t, self))), - [], _u => Ring.slides(aa, params, path, wins)), - Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(kids, self, params, path, wins), - Ring.walk(D1.inert.node(r2), self, params, path, wins), Ring.walk(r2, self, params, path, wins), - inert.stop(Bool.not(Bool.and(String.eq(o, "("), String.eq(D1.inert.keep(b, t), self))), Bool.not(Bool.and( - String.eq(o, "("), String.eq(t, self))), hi, Ring.slides(aa, params, path, wins), - Equal.cong(Bool, Bool, z => Bool.not(Bool.and(String.eq(o, "("), z)), String.eq(D1.inert.keep(b, t), self), - String.eq(t, self), M1.inert.eq_keep(b, t, self, ef, es)), eh), - ik, ir) - -# ring's walk reads no string: a leaf's text is compared with the def's name, -# which no string equals -law inert.ring.walk: - for nn: Tree.Node - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Ring.walk(D1.inert.node(nn), self, params, path, wins) == Ring.walk(nn, self, params, path, wins) - : List<&2, F.Finding>} - -def inert.ring.walk(nn, self, params, path, wins, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - +ec = inert.ok_h(gp, r2, eg) - inert.ring.lg(D1.inert.blanks(k), t, ot, self, params, path, wins, kids, r2, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), es, ec, - inert.ring.walk(kids, self, params, path, wins, es, ec), - inert.ring.walk(r2, self, params, path, wins, es, inert.ok_t(gp, r2, eg))) - case Tree.Leaf{x}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(kids, self, params, path, - wins), Ring.walk(D1.inert.node(rest), self, params, path, wins), Ring.walk(rest, self, params, path, - wins), - inert.ring.walk(kids, self, params, path, wins, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es2 = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.cat3(Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(kids, self, params, path, - wins), Ring.walk(D1.inert.node(body), self, params, path, wins), Ring.walk(body, self, params, path, - wins), Ring.walk(D1.inert.node(rest), self, params, path, wins), Ring.walk(rest, self, params, path, - wins), - inert.ring.walk(kids, self, params, path, wins, es, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), - es2)), - inert.ring.walk(body, self, params, path, wins, es, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), - es2)), - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e))) - case Tree.NNil{}: inert.ring.walk(rest, self, params, path, wins, es, e) - case Tree.NCons{x, y}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the top-level names of a chain read that way are the ones it had -law inert.ring.names_in: - for nn: Tree.Node - for +acc: List<&2, String> - {Ring.names_in(D1.inert.node(nn), acc) == Ring.names_in(nn, acc) : List<&2, String>} - -def inert.ring.names_in(nn, acc): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - Equal.cong(List<&2, String>, List<&2, String>, z => t <> z, Ring.names_in(D1.inert.node(rest), - acc), Ring.names_in(rest, acc), inert.ring.names_in(rest, acc)) - case Lex.TUpper{}: inert.ring.names_in(rest, acc) - case Lex.TDotted{}: inert.ring.names_in(rest, acc) - case Lex.TWild{}: inert.ring.names_in(rest, acc) - case Lex.TKey{}: inert.ring.names_in(rest, acc) - case Lex.TNum{}: inert.ring.names_in(rest, acc) - case Lex.TStr{}: inert.ring.names_in(rest, acc) - case Lex.TChar{}: inert.ring.names_in(rest, acc) - case Lex.TComment{}: inert.ring.names_in(rest, acc) - case Lex.TSpace{}: inert.ring.names_in(rest, acc) - case Lex.TNewline{}: inert.ring.names_in(rest, acc) - case Lex.TOp{}: inert.ring.names_in(rest, acc) - case Lex.TColon{}: inert.ring.names_in(rest, acc) - case Lex.TEq{}: inert.ring.names_in(rest, acc) - case Lex.TBind{}: inert.ring.names_in(rest, acc) - case Lex.TArrow{}: inert.ring.names_in(rest, acc) - case Lex.TLam{}: inert.ring.names_in(rest, acc) - case Lex.TAll{}: inert.ring.names_in(rest, acc) - case Lex.TAmp{}: inert.ring.names_in(rest, acc) - case Lex.TOpen{}: inert.ring.names_in(rest, acc) - case Lex.TClose{}: inert.ring.names_in(rest, acc) - case Lex.TComma{}: inert.ring.names_in(rest, acc) - case Tree.Group{o, gk, cl}: inert.ring.names_in(rest, acc) - case Tree.Stmt{sk, sks, sb}: inert.ring.names_in(rest, acc) - case Tree.NNil{}: inert.ring.names_in(rest, acc) - case Tree.NCons{x, y}: inert.ring.names_in(rest, acc) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the scrutinees of a statement read that way are the ones it had: only a -# keyword's text is read -law inert.ring.match_names: - for kids: Tree.Node - for +acc: List<&2, String> - {Ring.match_names(D1.inert.node(kids), acc) == Ring.match_names(kids, acc) : List<&2, String>} - -def inert.ring.match_names(kids, acc): - match kids: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +kw, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: - Equal.cong(List<&2, String>, List<&2, String>, z => Lazy.stop(List<&2, String>, - Bool.not(String.eq(kw, "match")), acc, _u => z), Ring.names_in(D1.inert.node(rest), acc), - Ring.names_in(rest, acc), inert.ring.names_in(rest, acc)) - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# three chains matched in turn, each read as it did -law inert.ring.m3: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +a0: List<&2, String> - for ik: {Ring.matched(D1.inert.node(kids), a0) == Ring.matched(kids, a0) : List<&2, String>} - for ib: {Ring.matched(D1.inert.node(body), Ring.matched(kids, a0)) == Ring.matched(body, Ring.matched(kids, a0)) - : List<&2, String>} - for ir: {Ring.matched(D1.inert.node(rest), Ring.matched(body, Ring.matched(kids, a0))) == Ring.matched(rest, - Ring.matched(body, Ring.matched(kids, a0))) : List<&2, String>} - {Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(body), Ring.matched(D1.inert.node(kids), a0))) - == Ring.matched(rest, Ring.matched(body, Ring.matched(kids, a0))) : List<&2, String>} - -def inert.ring.m3(kids, body, rest, a0, ik, ib, ir): - +lhs = Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(body), Ring.matched(D1.inert.node(kids), - a0))) - %ir : {lhs == _ : List<&2, String>} - %ib : {lhs == Ring.matched(D1.inert.node(rest), _) : List<&2, String>} - %ik : {lhs == Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(body), _)) : List<&2, String>} - {==} - -# the names a def matches, its body read that way, are the ones it matched -law inert.ring.matched: - for nn: Tree.Node - for +acc: List<&2, String> - {Ring.matched(D1.inert.node(nn), acc) == Ring.matched(nn, acc) : List<&2, String>} - -def inert.ring.matched(nn, acc): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.STerm{}: - %inert.ring.match_names(kids, acc) : {Ring.matched(D1.inert.node(Tree.NCons{Tree.Stmt{Tree.STerm{}, - kids, body}, rest}), acc) == Ring.matched(rest, Ring.matched(body, Ring.matched(kids, _))) - : List<&2, String>} - inert.ring.m3(kids, body, rest, Ring.match_names(D1.inert.node(kids), acc), - inert.ring.matched(kids, Ring.match_names(D1.inert.node(kids), acc)), - inert.ring.matched(body, Ring.matched(kids, Ring.match_names(D1.inert.node(kids), acc))), - inert.ring.matched(rest, Ring.matched(body, Ring.matched(kids, Ring.match_names(D1.inert.node(kids), - acc))))) - case Tree.SDef{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SType{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SLaw{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SImport{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SCase{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SFor{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SLet{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.Group{o, +kids, cl}: - %inert.ring.matched(rest, Ring.matched(kids, acc)) : {Ring.matched(D1.inert.node(rest), - Ring.matched(D1.inert.node(kids), acc)) == _ : List<&2, String>} - %inert.ring.matched(kids, acc) : {Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(kids), - acc)) == Ring.matched(D1.inert.node(rest), _) : List<&2, String>} - {==} - case Tree.Leaf{tok}: inert.ring.matched(rest, acc) - case Tree.NNil{}: inert.ring.matched(rest, acc) - case Tree.NCons{x, y}: inert.ring.matched(rest, acc) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the windows of a def read that way are the ones they were -law inert.ring.windows: - for +sig: Tree.Node - for +body: Tree.Node - {Ring.windows(D1.inert.node(sig), D1.inert.node(body)) == Ring.windows(sig, body) : List<&2, String>} - -def inert.ring.windows(sig, body): - +lhs = Ring.windows(D1.inert.node(sig), D1.inert.node(body)) - %inert.names(sig) : {lhs == Ring.windows.go(_, Ring.matched(body, []), []) : List<&2, String>} - %inert.ring.matched(body, []) : {lhs == Ring.windows.go(Calls.names(D1.inert.node(sig)), _, []) : List<&2, String>} - {==} - -# ring over the defs read that way reports what it reports over the defs -law inert.ring.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Ring.check.go(inert.defs(ds), path, acc) == Ring.check.go(ds, path, acc) : List<&2, F.Finding>} - -def inert.ring.go(ds, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +isg = D1.inert.node(sig) - +wi = Ring.walk(ib, name, Calls.names(isg), path, Ring.windows(isg, ib)) - +wo = Ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body)) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - isg)))), [], _u => wi) - +ew = Equal.trans(List<&2, F.Finding>, wi, Ring.walk(ib, name, Calls.names(sig), path, Ring.windows(sig, body)), - wo, Equal.trans(List<&2, F.Finding>, wi, Ring.walk(ib, name, Calls.names(sig), path, Ring.windows(isg, ib)), - Ring.walk(ib, name, Calls.names(sig), path, Ring.windows(sig, body)), - Equal.cong(List<&2, String>, List<&2, F.Finding>, z => Ring.walk(ib, name, z, path, Ring.windows(isg, ib)), - Calls.names(isg), Calls.names(sig), inert.names(sig)), - Equal.cong(List<&2, String>, List<&2, F.Finding>, z => Ring.walk(ib, name, Calls.names(sig), path, z), - Ring.windows(isg, ib), Ring.windows(sig, body), inert.ring.windows(sig, body))), - inert.ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body), en, eb)) - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, isg)))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), wi, wo, - inert.gate(path, name, sig, body, en, eb), ew) : - {Ring.check.go(inert.defs(rest), path, lst <> acc) == Ring.check.go(rest, path, _ <> acc) - : List<&2, F.Finding>} - inert.ring.go(rest, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -def Laws.inert_ring(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Ring.check.go(Calls.defs(z), path, []), tree, tree2, D1.inert.node(tree), - D1.inert.node(tree2), - inert.lift(z => Ring.check.go(z, path, []), tree, inert.ring.go(Calls.defs(tree), path, [], inert.dok.of(tree, - ok))), - inert.lift(z => Ring.check.go(z, path, []), tree2, inert.ring.go(Calls.defs(tree2), path, [], - inert.dok.of(tree2, ok2))), - e) - -# Inert text, arms and on (laws/CodeRulesIgnoreCommentsAndStrings.bend): the tree rules not over a -# file's defs, on the lemmas above. - -# arms -# ---- - -# a case statement's pattern read that way is the pattern it had, read that way -law inert.pattern: - for kids: Tree.Node - {T.pattern(D1.inert.node(kids)) == D1.inert.node(T.pattern(kids)) : Tree.Node} - -def inert.pattern(kids): - match kids: - case Tree.NCons{h, rest}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a from-k-up arm whose binder is read that way: a binder is never cut -law inert.arms.bpick: - for x: Bool - for b: Lex.TokKind - for +p: String - for -f: String -> Arms.Arm - {Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(D1.inert.keep(D1.inert.blanks(b), p)), Arms.AOther{}) - == Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(p), Arms.AOther{}) : Arms.Arm} - -def inert.arms.bpick(x, b, _p, _f): - match x: - case False{}: {==} - case True{}: - match b: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# what follows a Nat literal read that way makes the arm it made -law inert.arms.shape: - for rest: Tree.Node - for +kk: U32 - for +tt: String - for +line: U32 - for +col: U32 - {Arms.lit.shape(D1.inert.node(rest), kk, tt, line, col) == Arms.lit.shape(rest, kk, tt, line, col) : Arms.Arm} - -def inert.arms.shape(rest, kk, tt, line, col): - match rest: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +o, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: - match r: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Leaf{tok2}: - match tok2: - case Lex.Tok{+b, +p, l2, c2}: - match r2: - case Tree.NCons{h3, r3}: - match h3: - case Tree.Leaf{tok3}: - match tok3: - case Lex.Tok{k3, s, l3, c3}: - match k3: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: - inert.arms.bpick(String.eq(o, "+"), b, p, z => Arms.APlus{kk, - tt ++ "+" ++ z, line, col, T.width(tt)}) - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a Nat literal's arm, what follows it read that way -law inert.arms.lit: - for mm: Maybe<&2, U32> - for +tt: String - for +rest: Tree.Node - for +line: U32 - for +col: U32 - {Arms.lit.of(mm, tt, D1.inert.node(rest), line, col) == Arms.lit.of(mm, tt, rest, line, col) : Arms.Arm} - -def inert.arms.lit(mm, tt, rest, line, col): - match mm: - case None{}: {==} - case Some{+k}: inert.arms.shape(rest, k, tt, line, col) - -# a case pattern read that way makes the arm it made: a Nat literal, a capital -# name and a binder are never cut -law inert.arms.arm: - for pat: Tree.Node - {Arms.arm(D1.inert.node(pat)) == Arms.arm(pat) : Arms.Arm} - -def inert.arms.arm(pat): - match pat: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: - match rest: - case Tree.NCons{g, more}: - match g: - case Tree.Group{op, gk, cl}: - match gk: - case Tree.NCons{h3, +r3}: - match h3: - case Tree.Leaf{tok3}: - match tok3: - case Lex.Tok{+k3, +p, l2, c2}: - match r3: - case Tree.NNil{}: - inert.arms.bpick(String.eq(t, "Succ"), k3, p, z => Arms.APlus{1, - "Succ{" ++ z ++ "}", l, c, 4}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: inert.arms.lit(T.nat_value(t), t, rest, l, c) - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a match statement read that way has one scrutinee as it had: only a -# keyword's text is read -law inert.arms.single: - for kids: Tree.Node - {Arms.single(D1.inert.node(kids)) == Arms.single(kids) : Bool} - -def inert.arms.single(kids): - match kids: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: - match r: - case Tree.NCons{x, +r2}: - match r2: - case Tree.NCons{h3, +r3}: - match h3: - case Tree.Leaf{tok3}: - match tok3: - case Lex.Tok{k3, s, l3, c3}: - match k3: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: - match r3: - case Tree.NNil{}: {==} - case Tree.Leaf{y}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NCons{y, z}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{y, z}: {==} - case Tree.Leaf{y}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NNil{}: {==} - case Tree.Leaf{y}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NNil{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a case's arm, its tokens read that way -law inert.arms.case: - for +kids: Tree.Node - {Arms.arm(T.pattern(D1.inert.node(kids))) == Arms.arm(T.pattern(kids)) : Arms.Arm} - -def inert.arms.case(kids): - Equal.trans(Arms.Arm, Arms.arm(T.pattern(D1.inert.node(kids))), Arms.arm(D1.inert.node(T.pattern(kids))), - Arms.arm(T.pattern(kids)), Equal.cong(Tree.Node, Arms.Arm, z => Arms.arm(z), T.pattern(D1.inert.node(kids)), - D1.inert.node(T.pattern(kids)), inert.pattern(kids)), inert.arms.arm(T.pattern(kids))) - -# a match's arms read that way are judged as they were -law inert.arms.arms: - for body: Tree.Node - for +wide: Maybe<&2, Arms.Wide> - for +path: String - {Arms.arms(D1.inert.node(body), wide, path) == Arms.arms(body, wide, path) : List<&2, F.Finding>} - -def inert.arms.arms(body, wide, path): - match body: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, b}: - match sk: - case Tree.SCase{}: - +ao = Arms.arm(T.pattern(kids)) - +ai = Arms.arm(T.pattern(D1.inert.node(kids))) - +lhs = Arms.verdict(wide, ai, path, Arms.arms(D1.inert.node(rest), Arms.widen(wide, ai), path)) - %inert.arms.arms(rest, Arms.widen(wide, ao), path) : {lhs == Arms.verdict(wide, ao, path, _) - : List<&2, F.Finding>} - %inert.arms.case(kids) : {lhs == Arms.verdict(wide, _, path, Arms.arms(D1.inert.node(rest), - Arms.widen(wide, _), path)) : List<&2, F.Finding>} - {==} - case Tree.SDef{}: inert.arms.arms(rest, wide, path) - case Tree.SType{}: inert.arms.arms(rest, wide, path) - case Tree.SLaw{}: inert.arms.arms(rest, wide, path) - case Tree.SImport{}: inert.arms.arms(rest, wide, path) - case Tree.SFor{}: inert.arms.arms(rest, wide, path) - case Tree.SLet{}: inert.arms.arms(rest, wide, path) - case Tree.STerm{}: inert.arms.arms(rest, wide, path) - case Tree.Leaf{x}: inert.arms.arms(rest, wide, path) - case Tree.Group{x, y, z}: inert.arms.arms(rest, wide, path) - case Tree.NNil{}: inert.arms.arms(rest, wide, path) - case Tree.NCons{x, y}: inert.arms.arms(rest, wide, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# arms reads no string: a Nat literal, `Succ`, `match` and `+` are read by -# their kind, and a binder by its -law inert.arms.walk: - for nn: Tree.Node - for +path: String - {Arms.walk(D1.inert.node(nn), path) == Arms.walk(nn, path) : List<&2, F.Finding>} - -def inert.arms.walk(nn, path): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, +body}: - inert.cat3(Lazy.stop(List<&2, F.Finding>, Bool.not(Arms.single(D1.inert.node(kids))), [], - _u => Arms.arms(D1.inert.node(body), None{}, path)), Lazy.stop(List<&2, F.Finding>, - Bool.not(Arms.single(kids)), [], _u => Arms.arms(body, None{}, path)), - Arms.walk(D1.inert.node(body), path), Arms.walk(body, path), Arms.walk(D1.inert.node(rest), path), - Arms.walk(rest, path), - inert.stop(Bool.not(Arms.single(D1.inert.node(kids))), Bool.not(Arms.single(kids)), - Arms.arms(D1.inert.node(body), None{}, path), Arms.arms(body, None{}, path), - Equal.cong(Bool, Bool, z => Bool.not(z), Arms.single(D1.inert.node(kids)), Arms.single(kids), - inert.arms.single(kids)), inert.arms.arms(body, None{}, path)), - inert.arms.walk(body, path), inert.arms.walk(rest, path)) - case Tree.Leaf{x}: inert.arms.walk(rest, path) - case Tree.Group{x, y, z}: inert.arms.walk(rest, path) - case Tree.NNil{}: inert.arms.walk(rest, path) - case Tree.NCons{x, y}: inert.arms.walk(rest, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -def Laws.inert_arms(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, _ok, _ok2, e): - M1.inert.via(Tree.Node, z => Arms.walk(z, path), tree, tree2, D1.inert.node(tree), D1.inert.node(tree2), - inert.arms.walk(tree, path), inert.arms.walk(tree2, path), e) - -# unused -# ------ - -# a token by where it stands only -def inert.canon(tok: Lex.Tok) -> Lex.Tok: - match tok: - case Lex.Tok{k, t, l, c}: Lex.Tok{Lex.TSpace{}, "", l, c} - -# the tokens, each by where it stands only -def inert.canons(ts: List<&2, Lex.Tok>) -> List<&2, Lex.Tok>: - match ts: - case Nil{}: Nil{} - case Con{h, t}: inert.canon(h) <> inert.canons(t) - -# a token read that way stands where it stood -law inert.canon_tok: - for tok: Lex.Tok - {inert.canon(D1.inert.tok(tok)) == inert.canon(tok) : Lex.Tok} - -def inert.canon_tok(tok): - match tok: - case Lex.Tok{k, t, l, c}: {==} - -# a group's close onto tokens that stand alike -law inert.canon_close: - for close: Maybe<&2, Lex.Tok> - for -x: List<&2, Lex.Tok> - for -y: List<&2, Lex.Tok> - for e: {inert.canons(x) == inert.canons(y) : List<&2, Lex.Tok>} - {inert.canons(Tree.leaf_close(close, x)) == inert.canons(Tree.leaf_close(close, y)) : List<&2, Lex.Tok>} - -def inert.canon_close(close, x, y, e): - match close: - case None{}: e - case Some{+tok}: - Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => inert.canon(tok) <> z, inert.canons(x), inert.canons(y), - e) - -# the leaves of a node read that way stand where they stood -law inert.canon_leaves: - for nn: Tree.Node - for -a: List<&2, Lex.Tok> - for -b: List<&2, Lex.Tok> - for e: {inert.canons(a) == inert.canons(b) : List<&2, Lex.Tok>} - {inert.canons(Tree.leaves.go(D1.inert.node(nn), a)) == inert.canons(Tree.leaves.go(nn, b)) : List<&2, Lex.Tok>} - -def inert.canon_leaves(nn, a, b, e): - match nn: - case Tree.Leaf{+tok}: - %inert.canon_tok(tok) : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == _ <> inert.canons(b) - : List<&2, Lex.Tok>} - %e : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == inert.canon(D1.inert.tok(tok)) <> _ - : List<&2, Lex.Tok>} - {==} - case Tree.Group{+open, kids, close}: - inert.canon_close(close, Tree.leaves.go(D1.inert.node(kids), open <> a), Tree.leaves.go(kids, open <> b), - inert.canon_leaves(kids, open <> a, open <> b, Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, - z => inert.canon(open) <> z, inert.canons(a), inert.canons(b), e))) - case Tree.Stmt{sk, kids, body}: - inert.canon_leaves(body, Tree.leaves.go(D1.inert.node(kids), a), Tree.leaves.go(kids, b), - inert.canon_leaves(kids, a, b, e)) - case Tree.NNil{}: e - case Tree.NCons{h, t}: - inert.canon_leaves(t, Tree.leaves.go(D1.inert.node(h), a), Tree.leaves.go(h, b), inert.canon_leaves(h, a, b, e)) - -# reversing, by where they stand -law inert.canon_rev: - for xs: List<&2, Lex.Tok> - for +acc: List<&2, Lex.Tok> - {inert.canons(List.reverse.go(&2, Lex.Tok, xs, acc)) == List.reverse.go(&2, Lex.Tok, inert.canons(xs), - inert.canons(acc)) : List<&2, Lex.Tok>} - -def inert.canon_rev(xs, acc): - match xs: - case Nil{}: {==} - case Con{+h, t}: inert.canon_rev(t, h <> acc) - -# the lines of tokens are the lines of where they stand -law inert.canon_lines: - for ts: List<&2, Lex.Tok> - for +acc: List<&2, U32> - {Unused.lines(ts, acc) == Unused.lines(inert.canons(ts), acc) : List<&2, U32>} - -def inert.canon_lines(ts, acc): - match ts: - case Nil{}: {==} - case Con{h, +t}: - match h: - case Lex.Tok{k, x, +l, c}: - Equal.cong(List<&2, U32>, List<&2, U32>, z => l <> z, Unused.lines(t, acc), - Unused.lines(inert.canons(t), acc), inert.canon_lines(t, acc)) - -# the lines of a node's leaves read that way are the lines they were -law inert.unused.lines: - for +kids: Tree.Node - for +acc: List<&2, U32> - {Unused.lines(Tree.leaves(D1.inert.node(kids)), acc) == Unused.lines(Tree.leaves(kids), acc) : List<&2, U32>} - -def inert.unused.lines(kids, acc): - +li = Tree.leaves.go(D1.inert.node(kids), []) - +lo = Tree.leaves.go(kids, []) - +ec = Equal.trans(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), List.reverse.go(&2, Lex.Tok, - inert.canons(li), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), inert.canon_rev(li, []), - Equal.trans(List<&2, Lex.Tok>, List.reverse.go(&2, Lex.Tok, inert.canons(li), []), List.reverse.go(&2, Lex.Tok, - inert.canons(lo), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), - Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => List.reverse.go(&2, Lex.Tok, z, []), inert.canons(li), - inert.canons(lo), inert.canon_leaves(kids, [], [], {==})), - Equal.sym(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), List.reverse.go(&2, Lex.Tok, - inert.canons(lo), []), inert.canon_rev(lo, [])))) - Equal.trans(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, li, []), acc), - Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), - Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), - inert.canon_lines(List.reverse.go(&2, Lex.Tok, li, []), acc), - Equal.trans(List<&2, U32>, Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), - Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), - Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), - Equal.cong(List<&2, Lex.Tok>, List<&2, U32>, z => Unused.lines(z, acc), - inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), ec), - Equal.sym(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), - Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), - inert.canon_lines(List.reverse.go(&2, Lex.Tok, lo, []), acc)))) - -# a node's `:` test, the node read that way, is the one it was -law inert.unused.colon: - for nn: Tree.Node - {Unused.colon(D1.inert.node(nn)) == Unused.colon(nn) : Bool} - -def inert.unused.colon(nn): - match nn: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - -# a statement's `:` ending, its chain read that way, is the one it was -law inert.unused.ends: - for kids: Tree.Node - for last: Bool - {Unused.foreign.ends(D1.inert.node(kids), last) == Unused.foreign.ends(kids, last) : Bool} - -def inert.unused.ends(kids, _last): - match kids: - case Tree.NCons{+h, +t}: - Equal.trans(Bool, Unused.foreign.ends(D1.inert.node(t), Unused.colon(D1.inert.node(h))), - Unused.foreign.ends(D1.inert.node(t), Unused.colon(h)), Unused.foreign.ends(t, Unused.colon(h)), - Equal.cong(Bool, Bool, z => Unused.foreign.ends(D1.inert.node(t), z), Unused.colon(D1.inert.node(h)), - Unused.colon(h), inert.unused.colon(h)), - inert.unused.ends(t, Unused.colon(h))) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a body's opening import, the body read that way, is the one it was -law inert.unused.opens: - for body: Tree.Node - for open: Bool - {Unused.foreign.opens(D1.inert.node(body), open) == Unused.foreign.opens(body, open) : Bool} - -def inert.unused.opens(body, open): - match body: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, b}: - match open: - case True{}: - Equal.trans(Bool, - Unused.foreign.opens(D1.inert.node(rest), - Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), - Unused.foreign.opens(D1.inert.node(rest), Bool.not(Unused.foreign.ends(kids, False{}))), - Unused.foreign.opens(rest, Bool.not(Unused.foreign.ends(kids, False{}))), - Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(rest), Bool.not(z)), - Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), - inert.unused.ends(kids, False{})), - inert.unused.opens(rest, Bool.not(Unused.foreign.ends(kids, False{})))) - case False{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a def's lines onto the lines after it, the tree read that way, are the ones -# they were -law inert.unused.def: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +ih: {Unused.foreign(D1.inert.node(rest)) == Unused.foreign(rest) : List<&2, U32>} - {Bool.pick(List<&2, U32>, - Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), - Unused.lines(Tree.leaves(D1.inert.node(kids)), Unused.foreign(D1.inert.node(rest))), - Unused.foreign(D1.inert.node(rest))) - == Bool.pick(List<&2, U32>, Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))), - Unused.lines(Tree.leaves(kids), Unused.foreign(rest)), Unused.foreign(rest)) : List<&2, U32>} - -def inert.unused.def(kids, body, rest, ih): - +fi = Unused.foreign(D1.inert.node(rest)) - +fo = Unused.foreign(rest) - +ra = Unused.lines(Tree.leaves(D1.inert.node(kids)), fi) - +rb = Unused.lines(Tree.leaves(kids), fo) - +c1 = Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))) - +c2 = Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))) - +ec = Equal.trans(Bool, c1, - Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(kids, False{}))), c2, - Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(body), Bool.not(z)), - Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), - inert.unused.ends(kids, False{})), - inert.unused.opens(body, Bool.not(Unused.foreign.ends(kids, False{})))) - +ea = Equal.trans(List<&2, U32>, ra, Unused.lines(Tree.leaves(kids), fi), rb, inert.unused.lines(kids, fi), - Equal.cong(List<&2, U32>, List<&2, U32>, z => Unused.lines(Tree.leaves(kids), z), fi, fo, ih)) - Equal.trans(List<&2, U32>, Bool.pick(List<&2, U32>, c1, ra, fi), Bool.pick(List<&2, U32>, c2, ra, fi), - Bool.pick(List<&2, U32>, c2, rb, fo), - Equal.cong(Bool, List<&2, U32>, z => Bool.pick(List<&2, U32>, z, ra, fi), c1, c2, ec), - M2.unused.pick(c2, ra, rb, fi, fo, ea, ih)) - -# the foreign defs' header lines, the tree read that way, are the ones they were -law inert.unused.foreign: - for root: Tree.Node - {Unused.foreign(D1.inert.node(root)) == Unused.foreign(root) : List<&2, U32>} - -def inert.unused.foreign(root): - match root: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.SDef{}: inert.unused.def(kids, body, rest, inert.unused.foreign(rest)) - case Tree.SType{}: inert.unused.foreign(rest) - case Tree.SLaw{}: inert.unused.foreign(rest) - case Tree.SImport{}: inert.unused.foreign(rest) - case Tree.SCase{}: inert.unused.foreign(rest) - case Tree.SFor{}: inert.unused.foreign(rest) - case Tree.SLet{}: inert.unused.foreign(rest) - case Tree.STerm{}: inert.unused.foreign(rest) - case Tree.Leaf{x}: inert.unused.foreign(rest) - case Tree.Group{x, y, z}: inert.unused.foreign(rest) - case Tree.NNil{}: inert.unused.foreign(rest) - case Tree.NCons{x, y}: inert.unused.foreign(rest) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# does a binder of this kind count, its note read as unused reads it -def inert.unused.rep(kk: Bind.BindKind, +note: String, +line: U32, +fl: List<&2, U32>) -> Bool: - match kk: - case Bind.KLocal{}: - True{} - case Bind.KParam{}: - Bool.and(Bool.not(String.starts_with(note, "-")), Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) - case other: - False{} - -# unused over binders as it reads them -def inert.unused.go( - binds: List<&2, Bind.Bind>, - +hh: Unused.Hits, - +uses: List<&2, Bind.Use>, - +fl: List<&2, U32>, - +path: String -) -> List<&2, F.Finding>: - match binds: - case Nil{}: - Nil{} - case Con{Bind.Bind{+name, +line, +col, +kind, +note}, rest}: - +more = inert.unused.go(rest, hh, uses, fl, path) - +hit = Bool.and(inert.unused.rep(kind, note, line, fl), Bool.not(String.starts_with(name, "_"))) - Bool.pick(List<&2, F.Finding>, Lazy.and_then(hit, _u => Bool.not(Unused.seen(hh, uses, line, col))), - F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", - Unused.what(kind) ++ " " ++ name ++ " is never used."} <> more, more) - -# a leading `-`, kept as unused reads a note -law inert.unused.dash: - for s: Bool - {String.starts_with(Bool.pick(String, s, "-", ""), "-") == s : Bool} - -def inert.unused.dash(s): - match s: - case True{}: {==} - case False{}: {==} - -# a binder kept as unused reads it -law inert.unused.one: - for kind: Bind.BindKind - for +note: String - for +line: U32 - for +fl: List<&2, U32> - {Unused.reportable(kind, note, line, fl) == inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), - "-", ""), line, fl) : Bool} - -def inert.unused.one(kind, note, line, fl): - match kind: - case Bind.KParam{}: - %Equal.sym(Bool, String.starts_with(Bool.pick(String, String.starts_with(note, "-"), "-", ""), "-"), - String.starts_with(note, "-"), inert.unused.dash(String.starts_with(note, "-"))) : - {Bool.and(Bool.not(String.starts_with(note, "-")), - Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) == Bool.and(Bool.not(_), Bool.not(List.contains(~U32, - ~U32.is_eq, fl, line))) : Bool} - {==} - case Bind.KItem{}: {==} - case Bind.KCtor{}: {==} - case Bind.KTypeParam{}: {==} - case Bind.KField{}: {==} - case Bind.KLocal{}: {==} - case Bind.KPat{}: {==} - case Bind.KFor{}: {==} - case Bind.KTypeVar{}: {==} - -# unused over binders is unused over the binders as it reads them -law inert.unused.via: - for binds: List<&2, Bind.Bind> - for +hh: Unused.Hits - for +uses: List<&2, Bind.Use> - for +fl: List<&2, U32> - for +path: String - {Unused.check.go(binds, hh, uses, fl, path) == inert.unused.go(Laws.inert.ubinds(binds), hh, uses, fl, path) - : List<&2, F.Finding>} - -def inert.unused.via(binds, hh, uses, fl, path): - match binds: - case Nil{}: {==} - case Con{Bind.Bind{+name, +line, +col, +kind, +note}, +rest}: - +mo = Unused.check.go(rest, hh, uses, fl, path) - +x = {F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", - Unused.what(kind) ++ " " ++ name ++ " is never used."} : F.Finding} - +lhs = Unused.check.go(Con{Bind.Bind{name, line, col, kind, note}, rest}, hh, uses, fl, path) - %inert.unused.via(rest, hh, uses, fl, path) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and( - inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), "-", ""), line, fl), - Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> _, _) - : List<&2, F.Finding>} - %inert.unused.one(kind, note, line, fl) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and(_, - Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> mo, mo) - : List<&2, F.Finding>} - {==} - -# unused over binders and lines that read alike -law inert.unused.cong: - for -x1: List<&2, Bind.Bind> - for -x2: List<&2, Bind.Bind> - for -f1: List<&2, U32> - for -f2: List<&2, U32> - for +hh: Unused.Hits - for +uses: List<&2, Bind.Use> - for +path: String - for e1: {x1 == x2 : List<&2, Bind.Bind>} - for e2: {f1 == f2 : List<&2, U32>} - {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x2, hh, uses, f2, path) : List<&2, F.Finding>} - -def inert.unused.cong(x1, _x2, f1, f2, hh, uses, path, e1, e2): - %e1 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(_, hh, uses, f2, path) : List<&2, F.Finding>} - %e2 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x1, hh, uses, _, path) : List<&2, F.Finding>} - {==} - -def Laws.inert_unused( - path, - _text, - _toks, - tree, - binds, - uses, - _scopes, - _items, - _text2, - _toks2, - tree2, - binds2, - _scopes2, - _items2, - et, - eb -): - +f1 = Unused.foreign(tree) - +f2 = Unused.foreign(tree2) - +ef = Equal.trans(List<&2, U32>, f1, Unused.foreign(D1.inert.node(tree)), f2, - Equal.sym(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), f1, inert.unused.foreign(tree)), - Equal.trans(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), Unused.foreign(D1.inert.node(tree2)), f2, - Equal.cong(Tree.Node, List<&2, U32>, z => Unused.foreign(z), D1.inert.node(tree), D1.inert.node(tree2), et), - inert.unused.foreign(tree2))) - +hh = Unused.index(uses) - Equal.trans(List<&2, F.Finding>, Unused.check.go(binds, hh, uses, f1, path), - inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), Unused.check.go(binds2, hh, uses, f2, path), - inert.unused.via(binds, hh, uses, f1, path), - Equal.trans(List<&2, F.Finding>, inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), - inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), Unused.check.go(binds2, hh, uses, f2, path), - inert.unused.cong(Laws.inert.ubinds(binds), Laws.inert.ubinds(binds2), f1, f2, hh, uses, path, eb, ef), - Equal.sym(List<&2, F.Finding>, Unused.check.go(binds2, hh, uses, f2, path), - inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), inert.unused.via(binds2, hh, uses, f2, path)))) - -# Inert text, hoist (laws/CodeRulesIgnoreCommentsAndStrings.bend): a def's self-calls, its carried -# parameters, the tables it builds and the names it indexes, each read the -# way the lemmas above read a tree. - -# hoist -# ----- - -# self-calls' argument lists, each argument read that way -def inert.hoist.css(cs: List<&2, List<&2, Tree.Node>>) -> List<&2, List<&2, Tree.Node>>: - match cs: - case Nil{}: Nil{} - case Con{h, t}: inert.nodes(h) <> inert.hoist.css(t) - -# argument lists appended, then read that way, are each read that way, appended -law inert.hoist.css_app: - for a: List<&2, List<&2, Tree.Node>> - for +b: List<&2, List<&2, Tree.Node>> - {inert.hoist.css(List.append(&2, List<&2, Tree.Node>, a, b)) == List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(a), inert.hoist.css(b)) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.css_app(a, b): - match a: - case Nil{}: {==} - case Con{+h, +t}: - Equal.cong(List<&2, List<&2, Tree.Node>>, List<&2, List<&2, Tree.Node>>, z => inert.nodes(h) <> z, - inert.hoist.css(List.append(&2, List<&2, Tree.Node>, t, b)), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(t), inert.hoist.css(b)), inert.hoist.css_app(t, b)) - -# two runs of argument lists joined, each read that way, are the join read that way -law inert.hoist.cat2: - for +a: List<&2, List<&2, Tree.Node>> - for +b: List<&2, List<&2, Tree.Node>> - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b)]) == inert.hoist.css(List.concat(&2, - List<&2, Tree.Node>, [a, b])) - : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.cat2(a, b): - +lhs = List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b)]) - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, a, List.append(&2, - List<&2, Tree.Node>, b, []))), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), inert.hoist.css(List.append(&2, List<&2, Tree.Node>, b, - []))), - inert.hoist.css_app(a, List.append(&2, List<&2, Tree.Node>, b, []))) : {lhs == _ : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, b, [])), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(b), - inert.hoist.css([])), inert.hoist.css_app(b, [])) : {lhs == List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(a), _) : List<&2, List<&2, Tree.Node>>} - {==} - -# three runs of argument lists joined, each read that way, are the join read that way -law inert.hoist.cat3: - for +a: List<&2, List<&2, Tree.Node>> - for +b: List<&2, List<&2, Tree.Node>> - for +c: List<&2, List<&2, Tree.Node>> - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b), inert.hoist.css(c)]) - == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [a, b, c])) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.cat3(a, b, c): - +lhs = List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b), inert.hoist.css(c)]) - +bc = List.append(&2, List<&2, Tree.Node>, b, List.append(&2, List<&2, Tree.Node>, c, [])) - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, a, bc)), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), inert.hoist.css(bc)), - inert.hoist.css_app(a, bc)) : {lhs == _ : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(bc), List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(b), - inert.hoist.css(List.append(&2, List<&2, Tree.Node>, c, []))), inert.hoist.css_app(b, List.append(&2, List<&2, - Tree.Node>, c, []))) : - {lhs == List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), _) : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, c, [])), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(c), - inert.hoist.css([])), inert.hoist.css_app(c, [])) : - {lhs == List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(b), _)) : List<&2, List<&2, Tree.Node>>} - {==} - -# a self-call picked onto the rest, each read that way -law inert.hoist.pick: - for me: Bool - for +as: List<&2, Tree.Node> - for +more: List<&2, List<&2, Tree.Node>> - {Bool.pick(List<&2, List<&2, Tree.Node>>, me, inert.nodes(as) <> inert.hoist.css(more), inert.hoist.css(more)) - == inert.hoist.css(Bool.pick(List<&2, List<&2, Tree.Node>>, me, as <> more, more)) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pick(me, _as, _more): - match me: - case True{}: {==} - case False{}: {==} - -# two runs that read as others read that way join as their join does -law inert.hoist.pcat2: - for -x1: List<&2, List<&2, Tree.Node>> - for +x2: List<&2, List<&2, Tree.Node>> - for -y1: List<&2, List<&2, Tree.Node>> - for +y2: List<&2, List<&2, Tree.Node>> - for ix: {x1 == inert.hoist.css(x2) : List<&2, List<&2, Tree.Node>>} - for iy: {y1 == inert.hoist.css(y2) : List<&2, List<&2, Tree.Node>>} - {List.concat(&2, List<&2, Tree.Node>, [x1, y1]) == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, - y2])) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pcat2(x1, x2, y1, y2, ix, iy): - %Equal.sym(List<&2, List<&2, Tree.Node>>, x1, inert.hoist.css(x2), ix) : - {List.concat(&2, List<&2, Tree.Node>, [_, y1]) == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, - y2])) : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, y1, inert.hoist.css(y2), iy) : - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(x2), _]) == inert.hoist.css(List.concat(&2, List<&2, - Tree.Node>, [x2, y2])) : List<&2, List<&2, Tree.Node>>} - inert.hoist.cat2(x2, y2) - -# three runs that read as others read that way join as their join does -law inert.hoist.pcat3: - for -x1: List<&2, List<&2, Tree.Node>> - for +x2: List<&2, List<&2, Tree.Node>> - for -y1: List<&2, List<&2, Tree.Node>> - for +y2: List<&2, List<&2, Tree.Node>> - for -z1: List<&2, List<&2, Tree.Node>> - for +z2: List<&2, List<&2, Tree.Node>> - for ix: {x1 == inert.hoist.css(x2) : List<&2, List<&2, Tree.Node>>} - for iy: {y1 == inert.hoist.css(y2) : List<&2, List<&2, Tree.Node>>} - for iz: {z1 == inert.hoist.css(z2) : List<&2, List<&2, Tree.Node>>} - {List.concat(&2, List<&2, Tree.Node>, [x1, y1, z1]) == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, y2, - z2])) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pcat3(x1, x2, y1, y2, z1, z2, ix, iy, iz): - +rhs = inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, y2, z2])) - %Equal.sym(List<&2, List<&2, Tree.Node>>, x1, inert.hoist.css(x2), ix) : {List.concat(&2, List<&2, Tree.Node>, [_, - y1, z1]) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, y1, inert.hoist.css(y2), iy) : {List.concat(&2, List<&2, Tree.Node>, - [inert.hoist.css(x2), _, z1]) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, z1, inert.hoist.css(z2), iz) : - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(x2), inert.hoist.css(y2), _]) == rhs : List<&2, List<&2, - Tree.Node>>} - inert.hoist.cat3(x2, y2, z2) - -# a name then a group, both read that way: a self-call as it was, and the -# calls under the group and after it read that way -law inert.hoist.pushes_lg: - for +b: Bool - for +t: String - for +o: String - for +self: String - for +kids: Tree.Node - for +r2: Tree.Node - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +es: {D1.inert.marked(self) == False{} : Bool} - for ik: {Hoist.pushes(D1.inert.node(kids), self) == inert.hoist.css(Hoist.pushes(kids, self)) : List<&2, List<&2, - Tree.Node>>} - for ir: {Hoist.pushes(D1.inert.node(r2), self) == inert.hoist.css(Hoist.pushes(r2, self)) : List<&2, List<&2, - Tree.Node>>} - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(D1.inert.keep(b, t), self)), - Calls.args(D1.inert.node(kids)) <> List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(D1.inert.node(kids), - self), Hoist.pushes(D1.inert.node(r2), self)]), - List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(D1.inert.node(r2), - self)])) - == inert.hoist.css(Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - Calls.args(kids) <> List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(kids, self), Hoist.pushes(r2, self)]), - List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(kids, self), Hoist.pushes(r2, self)]))) : List<&2, List<&2, - Tree.Node>>} - -def inert.hoist.pushes_lg(b, t, o, self, kids, r2, ef, es, ik, ir): - +m = List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(kids, self), Hoist.pushes(r2, self)]) - +im = List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(D1.inert.node(kids), self), - Hoist.pushes(D1.inert.node(r2), self)]) - +rhs = inert.hoist.css(Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - Calls.args(kids) <> m, m)) - %Equal.sym(Bool, String.eq(D1.inert.keep(b, t), self), String.eq(t, self), M1.inert.eq_keep(b, t, self, ef, es)) : - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), _), Calls.args(D1.inert.node(kids)) <> im, - im) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, im, inert.hoist.css(m), - inert.hoist.pcat2(Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(kids, self), - Hoist.pushes(D1.inert.node(r2), self), Hoist.pushes(r2, self), ik, ir)) : - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - Calls.args(D1.inert.node(kids)) <> _, _) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, Tree.Node>, Calls.args(D1.inert.node(kids)), inert.nodes(Calls.args(kids)), inert.args(kids)) : - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - _ <> inert.hoist.css(m), inert.hoist.css(m)) - == rhs : List<&2, List<&2, Tree.Node>>} - inert.hoist.pick(Bool.and(String.eq(o, "("), String.eq(t, self)), Calls.args(kids), m) - -# the self-calls under a node read that way are its self-calls, each argument -# read that way: a string is never the def's name -law inert.hoist.pushes: - for nn: Tree.Node - for +self: String - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.pushes(D1.inert.node(nn), self) == inert.hoist.css(Hoist.pushes(nn, self)) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pushes(nn, self, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.hoist.pushes_lg(D1.inert.blanks(k), t, o, self, kids, r2, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), es, - inert.hoist.pushes(kids, self, es, inert.ok_h(gp, r2, eg)), - inert.hoist.pushes(r2, self, es, inert.ok_t(gp, r2, eg))) - case Tree.Leaf{x}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.hoist.pcat2(Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(kids, self), - Hoist.pushes(D1.inert.node(rest), self), - Hoist.pushes(rest, self), inert.hoist.pushes(kids, self, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es2 = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.hoist.pcat3(Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(kids, self), - Hoist.pushes(D1.inert.node(body), self), - Hoist.pushes(body, self), Hoist.pushes(D1.inert.node(rest), self), Hoist.pushes(rest, self), - inert.hoist.pushes(kids, self, es, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2)), - inert.hoist.pushes(body, self, es, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2)), - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e))) - case Tree.NNil{}: inert.hoist.pushes(rest, self, es, e) - case Tree.NCons{x, y}: inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a chain read that way is exactly the name as it was: only a name's text is read -law inert.hoist.lone_eq: - for nn: Tree.Node - for +name: String - {Hoist.lone_eq(D1.inert.node(nn), name) == Hoist.lone_eq(nn, name) : Bool} - -def inert.hoist.lone_eq(nn, _name): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# every self-call, its arguments read that way, passes the name at the -# argument as it did -law inert.hoist.all_same: - for cs: List<&2, List<&2, Tree.Node>> - for +ii: Nat - for +name: String - {Hoist.all_same(inert.hoist.css(cs), ii, name) == Hoist.all_same(cs, ii, name) : Bool} - -def inert.hoist.all_same(cs, ii, name): - match cs: - case Nil{}: {==} - case Con{+as, +rest}: - +lhs = Hoist.all_same(inert.hoist.css(Con{as, rest}), ii, name) - %inert.hoist.all_same(rest, ii, name) : - {lhs == Bool.and(Hoist.lone_eq(Calls.arg(as, ii), name), _) : Bool} - %inert.hoist.lone_eq(Calls.arg(as, ii), name) : - {lhs == Bool.and(_, Hoist.all_same(inert.hoist.css(rest), ii, name)) : Bool} - %inert.arg(as, ii) : - {lhs == Bool.and(Hoist.lone_eq(_, name), Hoist.all_same(inert.hoist.css(rest), ii, name)) : Bool} - {==} - -# the parameters every self-call passes unchanged, parameters and calls read -# that way -law inert.hoist.carried.go: - for ps: List<&2, Tree.Node> - for +cs: List<&2, List<&2, Tree.Node>> - for +ii: Nat - for +acc: List<&2, String> - {Hoist.carried.go(inert.nodes(ps), inert.hoist.css(cs), ii, acc) == Hoist.carried.go(ps, cs, ii, acc) : List<&2, - String>} - -def inert.hoist.carried.go(ps, cs, ii, acc): - match ps: - case Nil{}: {==} - case Con{+p, +rest}: - +nm = Calls.param_name(p) - +lhs = Hoist.carried.go(inert.nodes(Con{p, rest}), inert.hoist.css(cs), ii, acc) - %inert.hoist.all_same(cs, ii, nm) : - {lhs == Hoist.carried.go(rest, cs, (ii + 1n : Nat), Bool.pick(List<&2, String>, - Bool.and(Bool.not(String.is_empty(nm)), - _), nm <> acc, acc)) : List<&2, String>} - %inert.hoist.carried.go(rest, cs, (ii + 1n : Nat), - Bool.pick(List<&2, String>, Bool.and(Bool.not(String.is_empty(nm)), Hoist.all_same(inert.hoist.css(cs), ii, - nm)), - nm <> acc, acc)) : - {lhs == _ : List<&2, String>} - %Equal.sym(String, Calls.param_name(D1.inert.node(p)), nm, inert.param_name(p)) : - {Hoist.carried.go(inert.nodes(rest), inert.hoist.css(cs), (ii + 1n : Nat), - Bool.pick(List<&2, String>, Bool.and(Bool.not(String.is_empty(_)), Hoist.all_same(inert.hoist.css(cs), ii, - _)), - _ <> acc, acc)) - == Hoist.carried.go(inert.nodes(rest), inert.hoist.css(cs), (ii + 1n : Nat), - Bool.pick(List<&2, String>, Bool.and(Bool.not(String.is_empty(nm)), Hoist.all_same(inert.hoist.css(cs), - ii, nm)), - nm <> acc, acc)) : List<&2, String>} - {==} - -# parameters passed through unchanged, calls and signature read that way -law inert.hoist.carried_of: - for cs: List<&2, List<&2, Tree.Node>> - for +sig: Tree.Node - {Hoist.carried_of(inert.hoist.css(cs), D1.inert.node(sig)) == Hoist.carried_of(cs, sig) : List<&2, String>} - -def inert.hoist.carried_of(cs, sig): - match cs: - case Nil{}: {==} - case Con{+h, +t}: - +rhs = Hoist.carried_of(Con{h, t}, sig) - %Equal.sym(Tree.Node, Calls.params.of(D1.inert.node(sig)), D1.inert.node(Calls.params.of(sig)), - inert.params.of(sig)) : - {Hoist.carried.go(Calls.args(_), inert.hoist.css(h <> t), 0n, []) == rhs : List<&2, String>} - %Equal.sym(List<&2, Tree.Node>, Calls.args(D1.inert.node(Calls.params.of(sig))), inert.nodes(Calls.params(sig)), - inert.args(Calls.params.of(sig))) : - {Hoist.carried.go(_, inert.hoist.css(h <> t), 0n, []) == rhs : List<&2, String>} - inert.hoist.carried.go(Calls.params(sig), h <> t, 0n, []) - -# the parameters a def carries, its signature and body read that way -law inert.hoist.carried: - for +sig: Tree.Node - for +body: Tree.Node - for +self: String - for +es: {D1.inert.marked(self) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - {Hoist.carried(D1.inert.node(sig), D1.inert.node(body), self) == Hoist.carried(sig, body, self) : List<&2, - String>} - -def inert.hoist.carried(sig, body, self, es, eb): - %Equal.sym(List<&2, List<&2, Tree.Node>>, Hoist.pushes(D1.inert.node(body), self), - inert.hoist.css(Hoist.pushes(body, self)), - inert.hoist.pushes(body, self, es, eb)) : {Hoist.carried_of(_, D1.inert.node(sig)) == Hoist.carried(sig, body, - self) : List<&2, String>} - inert.hoist.carried_of(Hoist.pushes(body, self), sig) - -# two ands whose sides read alike read alike -law inert.hoist.and2: - for -a1: Bool - for -a2: Bool - for -b1: Bool - for -b2: Bool - for ea: {a1 == a2 : Bool} - for eb: {b1 == b2 : Bool} - {Bool.and(a1, b1) == Bool.and(a2, b2) : Bool} - -def inert.hoist.and2(a1, _a2, b1, b2, ea, eb): - %ea : {Bool.and(a1, b1) == Bool.and(_, b2) : Bool} - %eb : {Bool.and(a1, b1) == Bool.and(a1, _) : Bool} - {==} - -# two ors whose sides read alike read alike -law inert.hoist.or2: - for -a1: Bool - for -a2: Bool - for -b1: Bool - for -b2: Bool - for ea: {a1 == a2 : Bool} - for eb: {b1 == b2 : Bool} - {Bool.or(a1, b1) == Bool.or(a2, b2) : Bool} - -def inert.hoist.or2(a1, _a2, b1, b2, ea, eb): - %ea : {Bool.or(a1, b1) == Bool.or(_, b2) : Bool} - %eb : {Bool.or(a1, b1) == Bool.or(a1, _) : Bool} - {==} - -# a token read that way names a carried parameter as it did: only a name's -# text is read -law inert.hoist.name_ok: - for k: Lex.TokKind - for +t: String - for +carried: List<&2, String> - {Hoist.name_ok(k, D1.inert.keep(D1.inert.blanks(k), t), carried) == Hoist.name_ok(k, t, carried) : Bool} - -def inert.hoist.name_ok(k, _t, _carried): - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# a token after an operator, read that way, is closed as it was -law inert.hoist.leaf_ok: - for skip: Bool - for tok: Lex.Tok - for +carried: List<&2, String> - {Hoist.leaf_ok(skip, D1.inert.tok(tok), carried) == Hoist.leaf_ok(skip, tok, carried) : Bool} - -def inert.hoist.leaf_ok(skip, tok, carried): - match skip: - case True{}: {==} - case False{}: - match tok: - case Lex.Tok{k, +t, l, c}: inert.hoist.name_ok(k, t, carried) - -# a token before a group, read that way, is closed as it was -law inert.hoist.value_ok: - for app: Bool - for k: Lex.TokKind - for +t: String - for +carried: List<&2, String> - {Hoist.value_ok(app, k, D1.inert.keep(D1.inert.blanks(k), t), carried) == Hoist.value_ok(app, k, t, carried) - : Bool} - -def inert.hoist.value_ok(app, k, t, carried): - match app: - case True{}: {==} - case False{}: inert.hoist.name_ok(k, t, carried) - -# a token then a group, read that way: closed as it was, the group and the -# rest too -law inert.hoist.closed_lg: - for k: Lex.TokKind - for +t: String - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +carried: List<&2, String> - for ik: {Hoist.closed(D1.inert.node(kids), carried) == Hoist.closed(kids, carried) : Bool} - for ir: {Hoist.closed(D1.inert.node(r2), carried) == Hoist.closed(r2, carried) : Bool} - {Bool.and(Hoist.value_ok(String.eq(o, "("), k, D1.inert.keep(D1.inert.blanks(k), t), carried), - Bool.and(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(D1.inert.node(r2), carried))) - == Bool.and(Hoist.value_ok(String.eq(o, "("), k, t, carried), Bool.and(Hoist.closed(kids, carried), - Hoist.closed(r2, carried))) : Bool} - -def inert.hoist.closed_lg(k, t, o, kids, r2, carried, ik, ir): - inert.hoist.and2(Hoist.value_ok(String.eq(o, "("), k, D1.inert.keep(D1.inert.blanks(k), t), carried), - Hoist.value_ok(String.eq(o, "("), k, t, carried), Bool.and(Hoist.closed(D1.inert.node(kids), carried), - Hoist.closed(D1.inert.node(r2), carried)), Bool.and(Hoist.closed(kids, carried), Hoist.closed(r2, carried)), - inert.hoist.value_ok(String.eq(o, "("), k, t, carried), - inert.hoist.and2(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(kids, carried), - Hoist.closed(D1.inert.node(r2), carried), - Hoist.closed(r2, carried), ik, ir)) - -# a chain read that way is closed over the carried parameters as it was: a -# string is never a value name -law inert.hoist.closed: - for nn: Tree.Node - for +carried: List<&2, String> - {Hoist.closed(D1.inert.node(nn), carried) == Hoist.closed(nn, carried) : Bool} - -def inert.hoist.closed(nn, carried): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TName{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Stmt{x, y, z}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.NNil{}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.NCons{x, y}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Leaf{x}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Group{x, y, z}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Stmt{x, y, z}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.NNil{}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Lex.TUpper{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TUpper{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TDotted{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TDotted{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TWild{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TWild{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TKey{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TKey{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TNum{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TNum{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TStr{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TStr{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TChar{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TChar{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TComment{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TComment{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TSpace{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TSpace{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TNewline{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TNewline{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TOp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: inert.hoist.closed(rest, carried) - case Tree.Leaf{+tok2}: - inert.hoist.and2(Hoist.leaf_ok(String.eq(t, "~"), D1.inert.tok(tok2), carried), - Hoist.leaf_ok(String.eq(t, "~"), tok2, carried), Hoist.closed(D1.inert.node(r2), - carried), Hoist.closed(r2, carried), inert.hoist.leaf_ok(String.eq(t, "~"), tok2, - carried), inert.hoist.closed(r2, carried)) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TColon{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TColon{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TEq{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TEq{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TBind{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TBind{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TArrow{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TArrow{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TLam{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TLam{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TAll{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TAll{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TAmp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TAmp{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TOpen{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TOpen{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TClose{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TClose{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TComma{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TComma{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.Group{o, +kids, cl}: - inert.hoist.and2(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(kids, carried), - Hoist.closed(D1.inert.node(rest), carried), - Hoist.closed(rest, carried), inert.hoist.closed(kids, carried), inert.hoist.closed(rest, carried)) - case Tree.Stmt{sk, +kids, +body}: - inert.hoist.and2(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(kids, carried), - Bool.and(Hoist.closed(D1.inert.node(body), carried), Hoist.closed(D1.inert.node(rest), carried)), - Bool.and(Hoist.closed(body, carried), Hoist.closed(rest, carried)), inert.hoist.closed(kids, carried), - inert.hoist.and2(Hoist.closed(D1.inert.node(body), carried), Hoist.closed(body, carried), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), inert.hoist.closed(body, - carried), - inert.hoist.closed(rest, carried))) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a chain read that way holds a top-level token of an unmarked text as it did -law inert.hoist.top_mark: - for nn: Tree.Node - for +want: String - for +ew: {D1.inert.marked(want) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.top_mark(D1.inert.node(nn), want) == Hoist.top_mark(nn, want) : Bool} - -def inert.hoist.top_mark(nn, want, ew, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - inert.or2(String.eq(D1.inert.keep(D1.inert.blanks(k), t), want), String.eq(t, want), - Hoist.top_mark(D1.inert.node(rest), want), Hoist.top_mark(rest, want), - M1.inert.eq_keep(D1.inert.blanks(k), t, want, inert.ok_h(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e), ew), - inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e))) - case Tree.Group{o, gk, cl}: inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.Group{o, gk, cl}, rest, e)) - case Tree.Stmt{sk, sks, sb}: inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.Stmt{sk, sks, sb}, rest, e)) - case Tree.NNil{}: inert.hoist.top_mark(rest, want, ew, e) - case Tree.NCons{x, y}: inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a chain read that way holds as many top-level tokens of an unmarked text as -# it did -law inert.hoist.top_count: - for nn: Tree.Node - for +want: String - for +got: Nat - for +ew: {D1.inert.marked(want) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.top_count(D1.inert.node(nn), want, got) == Hoist.top_count(nn, want, got) : Nat} - -def inert.hoist.top_count(nn, want, got, ew, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +lhs = Hoist.top_count(D1.inert.node(Tree.NCons{lf, rest}), want, got) - %inert.hoist.top_count(rest, want, Bool.pick(Nat, String.eq(t, want), (got + 1n : Nat), got), ew, - inert.ok_t(lf, rest, e)) : {lhs == _ : Nat} - %M1.inert.eq_keep(D1.inert.blanks(k), t, want, inert.ok_h(lf, rest, e), ew) : - {lhs == Hoist.top_count(D1.inert.node(rest), want, Bool.pick(Nat, _, (got + 1n : Nat), got)) : Nat} - {==} - case Tree.Group{o, gk, cl}: - inert.hoist.top_count(rest, want, got, ew, inert.ok_t(Tree.Group{o, gk, cl}, rest, e)) - case Tree.Stmt{sk, sks, sb}: - inert.hoist.top_count(rest, want, got, ew, inert.ok_t(Tree.Stmt{sk, sks, sb}, rest, e)) - case Tree.NNil{}: inert.hoist.top_count(rest, want, got, ew, e) - case Tree.NCons{x, y}: inert.hoist.top_count(rest, want, got, ew, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a bracket's kids read that way are an array as they were -law inert.hoist.is_array: - for +kids: Tree.Node - for +e: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.is_array(D1.inert.node(kids)) == Hoist.is_array(kids) : Bool} - -def inert.hoist.is_array(kids, e): - +lhs = Hoist.is_array(D1.inert.node(kids)) - %inert.hoist.top_mark(kids, ":", {==}, e) : - {lhs == Bool.and(_, Bool.or(Hoist.top_mark(kids, "*"), Hoist.top_mark(kids, "^"))) : Bool} - %inert.hoist.top_mark(kids, "*", {==}, e) : - {lhs == Bool.and(Hoist.top_mark(D1.inert.node(kids), ":"), Bool.or(_, Hoist.top_mark(kids, "^"))) : Bool} - %inert.hoist.top_mark(kids, "^", {==}, e) : - {lhs == Bool.and(Hoist.top_mark(D1.inert.node(kids), ":"), Bool.or(Hoist.top_mark(D1.inert.node(kids), "*"), - _)) : Bool} - {==} - -# a chain read that way is sized small as it was: an operator's and a -# number's text are read, never a string's -law inert.hoist.small_size: - for nn: Tree.Node - {Hoist.small_size(D1.inert.node(nn)) == Hoist.small_size(nn) : Bool} - -def inert.hoist.small_size(nn): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, op, l, c}: - match k: - case Lex.TName{}: inert.hoist.small_size(rest) - case Lex.TUpper{}: inert.hoist.small_size(rest) - case Lex.TDotted{}: inert.hoist.small_size(rest) - case Lex.TWild{}: inert.hoist.small_size(rest) - case Lex.TKey{}: inert.hoist.small_size(rest) - case Lex.TNum{}: inert.hoist.small_size(rest) - case Lex.TStr{}: inert.hoist.small_size(rest) - case Lex.TChar{}: inert.hoist.small_size(rest) - case Lex.TComment{}: inert.hoist.small_size(rest) - case Lex.TSpace{}: inert.hoist.small_size(rest) - case Lex.TNewline{}: inert.hoist.small_size(rest) - case Lex.TOp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Leaf{tok2}: - match tok2: - case Lex.Tok{k2, t2, l2, c2}: - match k2: - case Lex.TName{}: inert.hoist.small_size(rest) - case Lex.TUpper{}: inert.hoist.small_size(rest) - case Lex.TDotted{}: inert.hoist.small_size(rest) - case Lex.TWild{}: inert.hoist.small_size(rest) - case Lex.TKey{}: inert.hoist.small_size(rest) - case Lex.TNum{}: - Equal.cong(Bool, Bool, z => Bool.or(Bool.or(Bool.and(String.eq(op, "*"), - Hoist.small_num(t2)), Bool.and(String.eq(op, "^"), List.contains(~String, - ~String.eq, ["0", "1", "2", "3", "0n", "1n", "2n", "3n"], t2))), z), - Hoist.small_size(D1.inert.node(r2)), Hoist.small_size(r2), - inert.hoist.small_size(r2)) - case Lex.TStr{}: inert.hoist.small_size(rest) - case Lex.TChar{}: inert.hoist.small_size(rest) - case Lex.TComment{}: inert.hoist.small_size(rest) - case Lex.TSpace{}: inert.hoist.small_size(rest) - case Lex.TNewline{}: inert.hoist.small_size(rest) - case Lex.TOp{}: inert.hoist.small_size(rest) - case Lex.TColon{}: inert.hoist.small_size(rest) - case Lex.TEq{}: inert.hoist.small_size(rest) - case Lex.TBind{}: inert.hoist.small_size(rest) - case Lex.TArrow{}: inert.hoist.small_size(rest) - case Lex.TLam{}: inert.hoist.small_size(rest) - case Lex.TAll{}: inert.hoist.small_size(rest) - case Lex.TAmp{}: inert.hoist.small_size(rest) - case Lex.TOpen{}: inert.hoist.small_size(rest) - case Lex.TClose{}: inert.hoist.small_size(rest) - case Lex.TComma{}: inert.hoist.small_size(rest) - case Tree.Group{x, y, z}: inert.hoist.small_size(rest) - case Tree.Stmt{x, y, z}: inert.hoist.small_size(rest) - case Tree.NNil{}: inert.hoist.small_size(rest) - case Tree.NCons{x, y}: inert.hoist.small_size(rest) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TColon{}: inert.hoist.small_size(rest) - case Lex.TEq{}: inert.hoist.small_size(rest) - case Lex.TBind{}: inert.hoist.small_size(rest) - case Lex.TArrow{}: inert.hoist.small_size(rest) - case Lex.TLam{}: inert.hoist.small_size(rest) - case Lex.TAll{}: inert.hoist.small_size(rest) - case Lex.TAmp{}: inert.hoist.small_size(rest) - case Lex.TOpen{}: inert.hoist.small_size(rest) - case Lex.TClose{}: inert.hoist.small_size(rest) - case Lex.TComma{}: inert.hoist.small_size(rest) - case Tree.Group{o, gk, cl}: inert.hoist.small_size(rest) - case Tree.Stmt{sk, sks, sb}: inert.hoist.small_size(rest) - case Tree.NNil{}: inert.hoist.small_size(rest) - case Tree.NCons{x, y}: inert.hoist.small_size(rest) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a table's kids read that way are as wide as they were -law inert.hoist.wide_shape: - for arr: Bool - for +kids: Tree.Node - for +e: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.wide_shape(arr, D1.inert.node(kids)) == Hoist.wide_shape(arr, kids) : Bool} - -def inert.hoist.wide_shape(arr, kids, e): - match arr: - case True{}: - Equal.cong(Bool, Bool, z => Bool.not(z), Hoist.small_size(D1.inert.node(kids)), Hoist.small_size(kids), - inert.hoist.small_size(kids)) - case False{}: - Equal.cong(Nat, Bool, z => Nat.is_gt(z, 7n), Hoist.top_count(D1.inert.node(kids), ",", 0n), - Hoist.top_count(kids, ",", 0n), - inert.hoist.top_count(kids, ",", 0n, {==}, e)) - -# a chain read that way is the number it was: only a number's text is read -law inert.hoist.num_text: - for nn: Tree.Node - {Hoist.num_text(D1.inert.node(nn)) == Hoist.num_text(nn) : String} - -def inert.hoist.num_text(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a count read that way is wide as it was -law inert.hoist.count_wide: - for +aa: Tree.Node - {Hoist.count_wide(D1.inert.node(aa)) == Hoist.count_wide(aa) : Bool} - -def inert.hoist.count_wide(aa): - Equal.cong(String, Bool, z => Bool.not(Hoist.small_num(z)), Hoist.num_text(D1.inert.node(aa)), Hoist.num_text(aa), - inert.hoist.num_text(aa)) - -# the last of the arguments read that way is the last read that way -law inert.hoist.last_node: - for as: List<&2, Tree.Node> - {Hoist.last_node(inert.nodes(as)) == D1.inert.node(Hoist.last_node(as)) : Tree.Node} - -def inert.hoist.last_node(as): - match as: - case Nil{}: {==} - case Con{h, +rest}: - match rest: - case Nil{}: {==} - case Con{x, y}: inert.hoist.last_node(rest) - -# a builder's count read that way is wide as it was -law inert.hoist.call_arm: - for rep: Bool - for +as: List<&2, Tree.Node> - for +end: Tree.Node - for +range: Bool - {Hoist.call_arm(rep, inert.nodes(as), D1.inert.node(end), range) == Hoist.call_arm(rep, as, end, range) : Bool} - -def inert.hoist.call_arm(rep, as, end, range): - match rep: - case True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), 2n), D1.inert.node(Calls.arg(as, 2n)), inert.arg(as, 2n)) : - {Hoist.count_wide(_) == Hoist.count_wide(Calls.arg(as, 2n)) : Bool} - inert.hoist.count_wide(Calls.arg(as, 2n)) - case False{}: - Equal.cong(Bool, Bool, z => Bool.pick(Bool, range, z, True{}), Hoist.count_wide(D1.inert.node(end)), - Hoist.count_wide(end), - inert.hoist.count_wide(end)) - -# a builder call read that way is wide as it was: a string names no builder -law inert.hoist.call_wide: - for +b: Bool - for +t: String - for +kids: Tree.Node - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Hoist.call_wide(D1.inert.keep(b, t), D1.inert.node(kids)) == Hoist.call_wide(t, kids) : Bool} - -def inert.hoist.call_wide(b, t, kids, ef): - +kt = D1.inert.keep(b, t) - +lhs = Hoist.call_wide(kt, D1.inert.node(kids)) - +aa = Calls.args(kids) - +rp = Bool.or(String.eq(kt, "List.replicate"), String.eq(kt, "Array.new")) - +rg = String.eq(kt, "List.range") - %M1.inert.eq_keep(b, t, "List.replicate", ef, {==}) : - {lhs == Hoist.call_arm(Bool.or(_, String.eq(t, "Array.new")), aa, Hoist.last_node(aa), String.eq(t, "List.range")) - : Bool} - %M1.inert.eq_keep(b, t, "Array.new", ef, {==}) : - {lhs == Hoist.call_arm(Bool.or(String.eq(kt, "List.replicate"), _), aa, Hoist.last_node(aa), - String.eq(t, "List.range")) : Bool} - %M1.inert.eq_keep(b, t, "List.range", ef, {==}) : {lhs == Hoist.call_arm(rp, aa, Hoist.last_node(aa), _) : Bool} - %inert.hoist.call_arm(rp, aa, Hoist.last_node(aa), rg) : {lhs == _ : Bool} - %inert.hoist.last_node(aa) : {lhs == Hoist.call_arm(rp, inert.nodes(aa), _, rg) : Bool} - %inert.args(kids) : {lhs == Hoist.call_arm(rp, _, Hoist.last_node(_), rg) : Bool} - {==} - -# a table's cells read that way are as wide as they were -law inert.hoist.wide_of: - for nn: Tree.Node - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.wide_of(D1.inert.node(nn)) == Hoist.wide_of(nn) : Bool} - -def inert.hoist.wide_of(nn, e): - match nn: - case Tree.NCons{h, +r}: - match h: - case Tree.Group{go, +kids, cl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - match r: - case Tree.NNil{}: - +ek = M1.inert.and_l(D1.inert.ok(kids), True{}, e) - +lhs = Hoist.wide_shape(Hoist.is_array(D1.inert.node(kids)), D1.inert.node(kids)) - %inert.hoist.wide_shape(Hoist.is_array(kids), kids, ek) : {lhs == _ : Bool} - %inert.hoist.is_array(kids, ek) : {lhs == Hoist.wide_shape(_, D1.inert.node(kids)) : Bool} - {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - match r2: - case Tree.NNil{}: - inert.hoist.call_wide(D1.inert.blanks(k), t, gkids, - inert.ok_h(Tree.Leaf{Lex.Tok{k, t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, - gkids, gcl}, Tree.NNil{}}, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a def's body read that way is one wide table as it was -law inert.hoist.wide_one: - for body: Tree.Node - for +name: String - for +acc: List<&2, String> - for +e: {D1.inert.ok(body) == True{} : Bool} - {Hoist.wide_one(D1.inert.node(body), name, acc) == Hoist.wide_one(body, name, acc) : List<&2, String>} - -def inert.hoist.wide_one(body, name, acc, e): - match body: - case Tree.NCons{h, +r}: - match h: - case Tree.Stmt{sk, +kids, +inn}: - match r: - case Tree.NNil{}: - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(inn), M1.inert.and_l(Bool.and(D1.inert.ok(kids), - D1.inert.ok(inn)), True{}, e)) - +lhs = Hoist.wide_one(D1.inert.node(Tree.NCons{Tree.Stmt{sk, kids, inn}, Tree.NNil{}}), name, acc) - %inert.table.built(kids, ek) : {lhs == Bool.pick(List<&2, String>, Bool.and(_, Hoist.wide_of(kids)), - name <> acc, acc) - : List<&2, String>} - %inert.hoist.wide_of(kids, ek) : {lhs == Bool.pick(List<&2, String>, - Bool.and(Table.built(D1.inert.node(kids)), _), name <> acc, - acc) : List<&2, String>} - {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# the defs read that way whose body is one wide table are the ones they were -law inert.hoist.wide_defs: - for ds: List<&2, Calls.Def> - for +acc: List<&2, String> - for +e: {inert.dok(ds) == True{} : Bool} - {Hoist.wide_defs(inert.defs(ds), acc) == Hoist.wide_defs(ds, acc) : List<&2, String>} - -def inert.hoist.wide_defs(ds, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +lhs = Hoist.wide_defs(inert.defs(Calls.Def{name, sig, body} <> rest), acc) - %inert.hoist.wide_defs(rest, Hoist.wide_one(body, name, acc), inert.dok.rest(name, sig, body, rest, e)) : - {lhs == _ : List<&2, String>} - %inert.hoist.wide_one(body, name, acc, inert.dok.body(name, sig, body, rest, e)) : - {lhs == Hoist.wide_defs(inert.defs(rest), _) : List<&2, String>} - {==} - -# a call's name read that way picks the slot it did: a string names no get or set -law inert.hoist.slot_of: - for +b: Bool - for +t: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Hoist.slot_of(D1.inert.keep(b, t)) == Hoist.slot_of(t) : Hoist.Slot} - -def inert.hoist.slot_of(b, t, ef): - +kt = D1.inert.keep(b, t) - +lhs = Hoist.slot_of(kt) - %M1.inert.eq_keep(b, t, "List.get", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(_, String.eq(t, "List.set")), - String.eq(t, "String.get"), Bool.or(String.eq(t, "Array.get"), String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "List.set", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), _), - String.eq(t, "String.get"), Bool.or(String.eq(t, "Array.get"), String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "String.get", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), - String.eq(kt, "List.set")), _, Bool.or(String.eq(t, "Array.get"), String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "Array.get", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), - String.eq(kt, "List.set")), String.eq(kt, "String.get"), Bool.or(_, String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "Array.set", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), - String.eq(kt, "List.set")), String.eq(kt, "String.get"), Bool.or(String.eq(kt, "Array.get"), _)) : Hoist.Slot} - {==} - -# a bracket group read that way is a closed wide table as it was -law inert.hoist.table_group: - for brack: Bool - for +kids: Tree.Node - for +carried: List<&2, String> - for +e: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.table_group(brack, D1.inert.node(kids), carried) == Hoist.table_group(brack, kids, carried) : Bool} - -def inert.hoist.table_group(brack, kids, carried, e): - match brack: - case False{}: {==} - case True{}: - +ia = Hoist.is_array(D1.inert.node(kids)) - +lhs = Hoist.group_yes(ia, D1.inert.node(kids), carried) - %inert.hoist.is_array(kids, e) : {lhs == Hoist.group_yes(_, kids, carried) : Bool} - %inert.hoist.top_mark(kids, ",", {==}, e) : {lhs == Bool.and(Bool.or(_, ia), Bool.and(Hoist.closed(kids, carried), - Hoist.wide_shape(ia, kids))) : Bool} - %inert.hoist.closed(kids, carried) : {lhs == Bool.and(Bool.or(Hoist.top_mark(D1.inert.node(kids), ","), ia), - Bool.and(_, - Hoist.wide_shape(ia, kids))) : Bool} - %inert.hoist.wide_shape(ia, kids, e) : {lhs == Bool.and(Bool.or(Hoist.top_mark(D1.inert.node(kids), ","), ia), - Bool.and(Hoist.closed(D1.inert.node(kids), carried), _)) : Bool} - {==} - -# a builder's closed arguments read that way are closed as they were -law inert.hoist.tb2: - for o: Bool - for w: Bool - for +kids: Tree.Node - for +carried: List<&2, String> - {Hoist.table_built(o, w, D1.inert.node(kids), carried) == Hoist.table_built(o, w, kids, carried) : Bool} - -def inert.hoist.tb2(o, w, kids, carried): - match o w: - case True{} True{}: inert.hoist.closed(kids, carried) - case True{} False{}: {==} - case False{} True{}: {==} - case False{} False{}: {==} - -# a builder's arguments read that way are a closed wide table as they were -law inert.hoist.tb: - for +o: Bool - for +t: String - for +kids: Tree.Node - for +carried: List<&2, String> - for +ef: {D1.inert.fits.go(False{}, t) == True{} : Bool} - {Hoist.table_built(o, Hoist.call_wide(t, D1.inert.node(kids)), D1.inert.node(kids), carried) - == Hoist.table_built(o, Hoist.call_wide(t, kids), kids, carried) : Bool} - -def inert.hoist.tb(o, t, kids, carried, ef): - +lhs = Hoist.table_built(o, Hoist.call_wide(t, D1.inert.node(kids)), D1.inert.node(kids), carried) - %inert.hoist.call_wide(False{}, t, kids, ef) : {lhs == Hoist.table_built(o, _, kids, carried) : Bool} - inert.hoist.tb2(o, Hoist.call_wide(t, D1.inert.node(kids)), kids, carried) - -# a call read that way builds a table as it did: a string names no builder -law inert.hoist.table_call: - for app: Bool - for k: Lex.TokKind - for +t: String - for +kids: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - {Hoist.table_call(app, k, D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), self, wides, carried) - == Hoist.table_call(app, k, t, kids, self, wides, carried) : Bool} - -def inert.hoist.table_call(app, k, t, kids, self, wides, carried, ef): - match app: - case False{}: {==} - case True{}: - match k: - case Lex.TName{}: inert.hoist.tb(Hoist.builder(Lex.TName{}, t, self, wides), t, kids, carried, ef) - case Lex.TUpper{}: inert.hoist.tb(Hoist.builder(Lex.TUpper{}, t, self, wides), t, kids, carried, ef) - case Lex.TDotted{}: inert.hoist.tb(Hoist.builder(Lex.TDotted{}, t, self, wides), t, kids, carried, ef) - case Lex.TWild{}: inert.hoist.tb(Hoist.builder(Lex.TWild{}, t, self, wides), t, kids, carried, ef) - case Lex.TKey{}: inert.hoist.tb(Hoist.builder(Lex.TKey{}, t, self, wides), t, kids, carried, ef) - case Lex.TNum{}: inert.hoist.tb(Hoist.builder(Lex.TNum{}, t, self, wides), t, kids, carried, ef) - case Lex.TStr{}: {==} - case Lex.TChar{}: inert.hoist.tb(Hoist.builder(Lex.TChar{}, t, self, wides), t, kids, carried, ef) - case Lex.TComment{}: {==} - case Lex.TSpace{}: inert.hoist.tb(Hoist.builder(Lex.TSpace{}, t, self, wides), t, kids, carried, ef) - case Lex.TNewline{}: inert.hoist.tb(Hoist.builder(Lex.TNewline{}, t, self, wides), t, kids, carried, ef) - case Lex.TOp{}: inert.hoist.tb(Hoist.builder(Lex.TOp{}, t, self, wides), t, kids, carried, ef) - case Lex.TColon{}: inert.hoist.tb(Hoist.builder(Lex.TColon{}, t, self, wides), t, kids, carried, ef) - case Lex.TEq{}: inert.hoist.tb(Hoist.builder(Lex.TEq{}, t, self, wides), t, kids, carried, ef) - case Lex.TBind{}: inert.hoist.tb(Hoist.builder(Lex.TBind{}, t, self, wides), t, kids, carried, ef) - case Lex.TArrow{}: inert.hoist.tb(Hoist.builder(Lex.TArrow{}, t, self, wides), t, kids, carried, ef) - case Lex.TLam{}: inert.hoist.tb(Hoist.builder(Lex.TLam{}, t, self, wides), t, kids, carried, ef) - case Lex.TAll{}: inert.hoist.tb(Hoist.builder(Lex.TAll{}, t, self, wides), t, kids, carried, ef) - case Lex.TAmp{}: inert.hoist.tb(Hoist.builder(Lex.TAmp{}, t, self, wides), t, kids, carried, ef) - case Lex.TOpen{}: inert.hoist.tb(Hoist.builder(Lex.TOpen{}, t, self, wides), t, kids, carried, ef) - case Lex.TClose{}: inert.hoist.tb(Hoist.builder(Lex.TClose{}, t, self, wides), t, kids, carried, ef) - case Lex.TComma{}: inert.hoist.tb(Hoist.builder(Lex.TComma{}, t, self, wides), t, kids, carried, ef) - -# a call named by a string is no table, whatever it says -law inert.hoist.tc_TStr: - for app: Bool - for -t1: String - for -t2: String - for -k1: Tree.Node - for -k2: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for -n1: Tree.Node - for -n2: Tree.Node - for +path: String - {Hoist.one_yes(Hoist.table_call(app, Lex.TStr{}, t1, k1, self, wides, carried), n1, path) - == Hoist.one_yes(Hoist.table_call(app, Lex.TStr{}, t2, k2, self, wides, carried), n2, path) : List<&2, F.Finding>} - -def inert.hoist.tc_TStr(app, _t1, _t2, _k1, _k2, _self, _wides, _carried, _n1, _n2, _path): - match app: - case True{}: {==} - case False{}: {==} - -# a call named by a comment is no table, whatever it says -law inert.hoist.tc_TComment: - for app: Bool - for -t1: String - for -t2: String - for -k1: Tree.Node - for -k2: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for -n1: Tree.Node - for -n2: Tree.Node - for +path: String - {Hoist.one_yes(Hoist.table_call(app, Lex.TComment{}, t1, k1, self, wides, carried), n1, path) - == Hoist.one_yes(Hoist.table_call(app, Lex.TComment{}, t2, k2, self, wides, carried), n2, path) : List<&2, - F.Finding>} - -def inert.hoist.tc_TComment(app, _t1, _t2, _k1, _k2, _self, _wides, _carried, _n1, _n2, _path): - match app: - case True{}: {==} - case False{}: {==} - -# a finding that is there or not, on two nodes it cites alike -law inert.hoist.one: - for yes: Bool - for -n1: Tree.Node - for -n2: Tree.Node - for +path: String - for ec: {Hoist.cite(n1, path) == Hoist.cite(n2, path) : List<&2, F.Finding>} - {Hoist.one_yes(yes, n1, path) == Hoist.one_yes(yes, n2, path) : List<&2, F.Finding>} - -def inert.hoist.one(yes, _n1, _n2, _path, ec): - match yes: - case True{}: ec - case False{}: {==} - -# a table read that way is the table it was, cited where it was: a table -# starts with a bracket or a builder's name, never a string -law inert.hoist.yes: - for nn: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.one_yes(Hoist.is_table(D1.inert.node(nn), self, wides, carried), D1.inert.node(nn), path) - == Hoist.one_yes(Hoist.is_table(nn, self, wides, carried), nn, path) : List<&2, F.Finding>} - -def inert.hoist.yes(nn, self, wides, carried, path, e): - match nn: - case Tree.NCons{h, +r}: - match h: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl}, Tree.NNil{}}) - +n2 = {Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl}, Tree.NNil{}} : Tree.Node} - +tg = Hoist.table_group(String.eq(o, "["), D1.inert.node(kids), carried) - %inert.hoist.table_group(String.eq(o, "["), kids, carried, M1.inert.and_l(D1.inert.ok(kids), True{}, - e)) : {Hoist.one_yes(tg, n1, path) == Hoist.one_yes(_, n2, path) : List<&2, F.Finding>} - inert.hoist.one(tg, n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TName{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TName{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TName{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TUpper{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TUpper{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TUpper{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TUpper{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TDotted{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TDotted{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TDotted{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TDotted{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TWild{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TWild{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TWild{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TWild{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TKey{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TKey{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TKey{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TKey{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNum{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TNum{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TNum{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TNum{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TStr{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - inert.hoist.tc_TStr(String.eq(o, "("), D1.inert.cut(t), t, D1.inert.node(gkids), - gkids, self, wides, carried, - D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}), - Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TChar{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TChar{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TChar{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TChar{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComment{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - inert.hoist.tc_TComment(String.eq(o, "("), D1.inert.cut(t), t, - D1.inert.node(gkids), gkids, self, wides, carried, - D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}), - Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TSpace{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TSpace{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TSpace{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TSpace{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNewline{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TNewline{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TNewline{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TNewline{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOp{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TOp{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TOp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TOp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TColon{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TColon{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TColon{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TColon{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TEq{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TEq{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TEq{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TEq{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TBind{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TBind{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TBind{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TBind{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TArrow{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TArrow{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TArrow{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TArrow{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TLam{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TLam{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TLam{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TLam{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAll{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TAll{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TAll{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TAll{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAmp{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TAmp{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TAmp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TAmp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOpen{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TOpen{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TOpen{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TOpen{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TClose{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TClose{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TClose{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TClose{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComma{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TComma{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TComma{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TComma{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a let's table cited when it is indexed: a choice on the index of the table's -# finding -law inert.hoist.use_pick: - for tab: Bool - for used: Bool - for +expr: Tree.Node - for +path: String - {Hoist.let_use(tab, used, expr, path) == Bool.pick(List<&2, F.Finding>, used, Hoist.one_yes(tab, expr, path), - []) : List<&2, F.Finding>} - -def inert.hoist.use_pick(tab, used, _expr, _path): - match tab used: - case True{} True{}: {==} - case True{} False{}: {==} - case False{} True{}: {==} - case False{} False{}: {==} - -# a let's table read that way, indexed as it was, is cited as it was -law inert.hoist.use: - for +expr: Tree.Node - for +used: Bool - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +e: {D1.inert.ok(expr) == True{} : Bool} - {Hoist.let_use(Hoist.is_table(D1.inert.node(expr), self, wides, carried), used, D1.inert.node(expr), path) - == Hoist.let_use(Hoist.is_table(expr, self, wides, carried), used, expr, path) : List<&2, F.Finding>} - -def inert.hoist.use(expr, used, self, wides, carried, path, e): - +t1 = Hoist.is_table(D1.inert.node(expr), self, wides, carried) - +t2 = Hoist.is_table(expr, self, wides, carried) - Equal.trans(List<&2, F.Finding>, Hoist.let_use(t1, used, D1.inert.node(expr), path), Bool.pick(List<&2, - F.Finding>, used, Hoist.one_yes(t1, D1.inert.node(expr), path), []), - Hoist.let_use(t2, used, expr, path), inert.hoist.use_pick(t1, used, D1.inert.node(expr), path), - Equal.trans(List<&2, F.Finding>, Bool.pick(List<&2, F.Finding>, used, Hoist.one_yes(t1, D1.inert.node(expr), - path), []), - Bool.pick(List<&2, F.Finding>, used, Hoist.one_yes(t2, expr, path), []), Hoist.let_use(t2, used, expr, path), - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, z => Bool.pick(List<&2, F.Finding>, used, z, []), - Hoist.one_yes(t1, D1.inert.node(expr), path), - Hoist.one_yes(t2, expr, path), inert.hoist.yes(expr, self, wides, carried, path, e)), - Equal.sym(List<&2, F.Finding>, Hoist.let_use(t2, used, expr, path), Bool.pick(List<&2, F.Finding>, used, - Hoist.one_yes(t2, expr, path), []), - inert.hoist.use_pick(t2, used, expr, path)))) - -# a chain read that way is exactly the name as it was: only a name's text is read -law inert.hoist.wrapped_inside: - for nn: Tree.Node - for +binder: String - {Hoist.wrapped_inside(D1.inert.node(nn), binder) == Hoist.wrapped_inside(nn, binder) : Bool} - -def inert.hoist.wrapped_inside(nn, _binder): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a paren's kids read that way are the name as they were -law inert.hoist.wrapped_open: - for paren: Bool - for +kids: Tree.Node - for +binder: String - {Hoist.wrapped_open(paren, D1.inert.node(kids), binder) == Hoist.wrapped_open(paren, kids, binder) : Bool} - -def inert.hoist.wrapped_open(paren, kids, binder): - match paren: - case True{}: inert.hoist.wrapped_inside(kids, binder) - case False{}: {==} - -# a chain read that way is the name, or the name in parentheses, as it was -law inert.hoist.wrapped: - for nn: Tree.Node - for +binder: String - {Hoist.wrapped(D1.inert.node(nn), binder) == Hoist.wrapped(nn, binder) : Bool} - -def inert.hoist.wrapped(nn, binder): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - match r: - case Tree.NNil{}: inert.hoist.wrapped_open(String.eq(o, "("), kids, binder) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a get's or set's collection argument, the arguments read that way, is the -# name as it was -law inert.hoist.uses_arg: - for ss: Hoist.Slot - for +as: List<&2, Tree.Node> - for +binder: String - {Hoist.uses_arg(ss, inert.nodes(as), binder) == Hoist.uses_arg(ss, as, binder) : Bool} - -def inert.hoist.uses_arg(ss, as, binder): - match ss: - case Hoist.Slot{on, +at}: - match on: - case False{}: {==} - case True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), at), D1.inert.node(Calls.arg(as, at)), inert.arg(as, at)) : - {Hoist.wrapped(_, binder) == Hoist.wrapped(Calls.arg(as, at), binder) : Bool} - inert.hoist.wrapped(Calls.arg(as, at), binder) - -# a name then a group, read that way, indexes the name as it did: a string is -# never the name nor a get or set -law inert.hoist.uses_here: - for brack: Bool - for call: Bool - for +b: Bool - for +t: String - for +kids: Tree.Node - for +binder: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +eb: {D1.inert.marked(binder) == False{} : Bool} - {Hoist.uses_here(brack, call, D1.inert.keep(b, t), D1.inert.node(kids), binder) == Hoist.uses_here(brack, call, - t, kids, binder) - : Bool} - -def inert.hoist.uses_here(brack, call, b, t, kids, binder, ef, eb): - match brack: - case True{}: M1.inert.eq_keep(b, t, binder, ef, eb) - case False{}: - match call: - case False{}: {==} - case True{}: - +lhs = Hoist.uses_arg(Hoist.slot_of(D1.inert.keep(b, t)), Calls.args(D1.inert.node(kids)), binder) - %inert.hoist.uses_arg(Hoist.slot_of(t), Calls.args(kids), binder) : {lhs == _ : Bool} - %inert.args(kids) : {lhs == Hoist.uses_arg(Hoist.slot_of(t), _, binder) : Bool} - %inert.hoist.slot_of(b, t, ef) : {lhs == Hoist.uses_arg(_, Calls.args(D1.inert.node(kids)), binder) : Bool} - {==} - -# a name then a group: the index here, under the group and after it read as -# they were -law inert.hoist.uses_lg: - for +b: Bool - for +t: String - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +binder: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +eb: {D1.inert.marked(binder) == False{} : Bool} - for ik: {Hoist.uses_name(D1.inert.node(kids), binder) == Hoist.uses_name(kids, binder) : Bool} - for ir: {Hoist.uses_name(D1.inert.node(r2), binder) == Hoist.uses_name(r2, binder) : Bool} - {Bool.or(Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), D1.inert.keep(b, t), D1.inert.node(kids), binder), - Bool.or(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(D1.inert.node(r2), binder))) - == Bool.or(Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder), - Bool.or(Hoist.uses_name(kids, binder), Hoist.uses_name(r2, binder))) : Bool} - -def inert.hoist.uses_lg(b, t, o, kids, r2, binder, ef, eb, ik, ir): - inert.hoist.or2(Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), D1.inert.keep(b, t), D1.inert.node(kids), - binder), - Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder), - Bool.or(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(D1.inert.node(r2), binder)), - Bool.or(Hoist.uses_name(kids, binder), Hoist.uses_name(r2, binder)), - inert.hoist.uses_here(String.eq(o, "["), String.eq(o, "("), b, t, kids, binder, ef, eb), - inert.hoist.or2(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(kids, binder), - Hoist.uses_name(D1.inert.node(r2), binder), - Hoist.uses_name(r2, binder), ik, ir)) - -# a chain read that way indexes the name as it did -law inert.hoist.uses_name: - for nn: Tree.Node - for +binder: String - for +eb: {D1.inert.marked(binder) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.uses_name(D1.inert.node(nn), binder) == Hoist.uses_name(nn, binder) : Bool} - -def inert.hoist.uses_name(nn, binder, eb, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.hoist.uses_lg(D1.inert.blanks(k), t, o, kids, r2, binder, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), eb, - inert.hoist.uses_name(kids, binder, eb, inert.ok_h(gp, r2, eg)), - inert.hoist.uses_name(r2, binder, eb, inert.ok_t(gp, r2, eg))) - case Tree.Leaf{x}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.hoist.or2(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(kids, binder), - Hoist.uses_name(D1.inert.node(rest), binder), - Hoist.uses_name(rest, binder), inert.hoist.uses_name(kids, binder, eb, inert.ok_h(Tree.Group{o, kids, cl}, - rest, e)), - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.hoist.or2(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(kids, binder), - Bool.or(Hoist.uses_name(D1.inert.node(body), binder), Hoist.uses_name(D1.inert.node(rest), binder)), - Bool.or(Hoist.uses_name(body, binder), Hoist.uses_name(rest, binder)), - inert.hoist.uses_name(kids, binder, eb, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.hoist.or2(Hoist.uses_name(D1.inert.node(body), binder), Hoist.uses_name(body, binder), - Hoist.uses_name(D1.inert.node(rest), binder), Hoist.uses_name(rest, binder), - inert.hoist.uses_name(body, binder, eb, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e)))) - case Tree.NNil{}: inert.hoist.uses_name(rest, binder, eb, e) - case Tree.NCons{x, y}: inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a let's name and table, the table read that way -def inert.hoist.got(mm: Maybe<&2, Hoist.Got>) -> Maybe<&2, Hoist.Got>: - match mm: - case None{}: None{} - case Some{Hoist.Got{nm, expr}}: Some{Hoist.Got{nm, D1.inert.node(expr)}} - -# a let's name plain and its table's leaves fitting, or no let -def inert.hoist.gok(mm: Maybe<&2, Hoist.Got>) -> Bool: - match mm: - case None{}: True{} - case Some{Hoist.Got{nm, expr}}: Bool.and(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr)) - -# a Bool that is False has a negation that holds -law inert.hoist.not_f: - for -x: Bool - for e: {x == False{} : Bool} - {Bool.not(x) == True{} : Bool} - -def inert.hoist.not_f(x, e): - %Equal.sym(Bool, x, False{}, e) : {Bool.not(_) == True{} : Bool} - {==} - -# the chain after `=`, read that way, is the table it was -law inert.hoist.got_at: - for seen: Bool - for bad: Bool - for +nm: String - for +expr: Tree.Node - {Hoist.got_at(seen, bad, nm, D1.inert.node(expr)) == inert.hoist.got(Hoist.got_at(seen, bad, nm, - expr)) : Maybe<&2, Hoist.Got>} - -def inert.hoist.got_at(seen, bad, _nm, _expr): - match seen: - case False{}: {==} - case True{}: - match bad: - case True{}: {==} - case False{}: {==} - -# the chain after `=` of a plain name fits -law inert.hoist.got_at_ok: - for seen: Bool - for bad: Bool - for +nm: String - for +expr: Tree.Node - for +en: {D1.inert.marked(nm) == False{} : Bool} - for +ee: {D1.inert.ok(expr) == True{} : Bool} - {inert.hoist.gok(Hoist.got_at(seen, bad, nm, expr)) == True{} : Bool} - -def inert.hoist.got_at_ok(seen, bad, nm, expr, en, ee): - match seen: - case False{}: {==} - case True{}: - match bad: - case True{}: {==} - case False{}: - M1.inert.and_both(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr), - inert.hoist.not_f(D1.inert.marked(nm), en), ee) - -# a let read that way binds the name it did to its table read that way: only -# a name's text is read -law inert.hoist.eat: - for nn: Tree.Node - for +seen: Bool - for +bad: Bool - for +nm: String - {Hoist.eat(D1.inert.node(nn), seen, bad, nm) == inert.hoist.got(Hoist.eat(nn, seen, bad, nm)) : Maybe<&2, - Hoist.Got>} - -def inert.hoist.eat(nn, seen, bad, nm): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: inert.hoist.eat(rest, True{}, Bool.or(bad, seen), t) - case Lex.TUpper{}: inert.hoist.eat(rest, seen, True{}, nm) - case Lex.TDotted{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TWild{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TKey{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TNum{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TStr{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TChar{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TComment{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TSpace{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TNewline{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TOp{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TColon{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TEq{}: inert.hoist.got_at(seen, bad, nm, rest) - case Lex.TBind{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TArrow{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TLam{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TAll{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TAmp{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TOpen{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TClose{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TComma{}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.Group{o, gk, cl}: inert.hoist.eat(rest, seen, True{}, nm) - case Tree.Stmt{sk, sks, sb}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.NNil{}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.NCons{x, y}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a let of a source as the lexer makes it binds a plain name to leaves that fit -law inert.hoist.eat_ok: - for nn: Tree.Node - for +seen: Bool - for +bad: Bool - for +nm: String - for +en: {D1.inert.marked(nm) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {inert.hoist.gok(Hoist.eat(nn, seen, bad, nm)) == True{} : Bool} - -def inert.hoist.eat_ok(nn, seen, bad, nm, en, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - inert.hoist.eat_ok(rest, True{}, Bool.or(bad, seen), t, inert.not_true(D1.inert.marked(t), - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest, e)), - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest, e)) - case Lex.TUpper{}: - inert.hoist.eat_ok(rest, seen, True{}, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Lex.TDotted{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Lex.TWild{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Lex.TKey{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Lex.TNum{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Lex.TStr{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Lex.TChar{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Lex.TComment{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Lex.TSpace{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Lex.TNewline{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Lex.TOp{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, - e)) - case Lex.TColon{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Lex.TEq{}: - inert.hoist.got_at_ok(seen, bad, nm, rest, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Lex.TBind{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Lex.TArrow{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Lex.TLam{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Lex.TAll{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Lex.TAmp{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Lex.TOpen{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Lex.TClose{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Lex.TComma{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.Group{o, gk, cl}: - inert.hoist.eat_ok(rest, seen, True{}, nm, en, inert.ok_t(Tree.Group{o, gk, cl}, rest, e)) - case Tree.Stmt{sk, sks, sb}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Stmt{sk, sks, sb}, rest, e)) - case Tree.NNil{}: inert.hoist.eat_ok(rest, seen, bad, nm, en, e) - case Tree.NCons{x, y}: inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# the let's body and what follows, read that way, index the name as they did -law inert.hoist.uses_or: - for +body: Tree.Node - for +rest: Tree.Node - for +binder: String - for +eb: {D1.inert.marked(binder) == False{} : Bool} - for +ebd: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - {Hoist.uses_or(D1.inert.node(body), D1.inert.node(rest), binder) == Hoist.uses_or(body, rest, binder) : Bool} - -def inert.hoist.uses_or(body, rest, binder, eb, ebd, er): - inert.hoist.or2(Hoist.uses_name(D1.inert.node(body), binder), Hoist.uses_name(body, binder), - Hoist.uses_name(D1.inert.node(rest), binder), - Hoist.uses_name(rest, binder), inert.hoist.uses_name(body, binder, eb, ebd), - inert.hoist.uses_name(rest, binder, eb, er)) - -# a plain let read that way is judged as it was -law inert.hoist.let_of: - for mm: Maybe<&2, Hoist.Got> - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +eg: {inert.hoist.gok(mm) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - {Hoist.let_of(inert.hoist.got(mm), D1.inert.node(body), D1.inert.node(rest), self, wides, carried, path) - == Hoist.let_of(mm, body, rest, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.let_of(mm, body, rest, self, wides, carried, path, eg, eb, er): - match mm: - case None{}: {==} - case Some{Hoist.Got{+nm, +expr}}: - +en = inert.not_true(D1.inert.marked(nm), M1.inert.and_l(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr), eg)) - +ee = M1.inert.and_r(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr), eg) - +lhs = Hoist.let_use(Hoist.is_table(D1.inert.node(expr), self, wides, carried), - Hoist.uses_or(D1.inert.node(body), D1.inert.node(rest), nm), D1.inert.node(expr), - path) - %inert.hoist.uses_or(body, rest, nm, en, eb, er) : - {lhs == Hoist.let_use(Hoist.is_table(expr, self, wides, carried), _, expr, path) : List<&2, F.Finding>} - inert.hoist.use(expr, Hoist.uses_or(D1.inert.node(body), D1.inert.node(rest), nm), self, wides, carried, - path, ee) - -# a hot let read that way is judged as it was -law inert.hoist.let_hit: - for hot: Bool - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - {Hoist.let_hit(hot, D1.inert.node(kids), D1.inert.node(body), D1.inert.node(rest), self, wides, carried, path) - == Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.let_hit(hot, kids, body, rest, self, wides, carried, path, ek, eb, er): - match hot: - case False{}: {==} - case True{}: - %Equal.sym(Maybe<&2, Hoist.Got>, Hoist.eat(D1.inert.node(kids), False{}, False{}, ""), - inert.hoist.got(Hoist.eat(kids, False{}, False{}, "")), inert.hoist.eat(kids, False{}, False{}, "")) : - {Hoist.let_of(_, D1.inert.node(body), D1.inert.node(rest), self, wides, carried, path) - == Hoist.let_of(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) : List<&2, - F.Finding>} - inert.hoist.let_of(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path, - inert.hoist.eat_ok(kids, False{}, False{}, "", {==}, ek), eb, er) - -# a get's or set's collection argument, the arguments read that way, is the -# table it was -law inert.hoist.arg_table: - for ss: Hoist.Slot - for +as: List<&2, Tree.Node> - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +ea: {inert.noks(as) == True{} : Bool} - {Hoist.arg_table(ss, inert.nodes(as), self, wides, carried, path) - == Hoist.arg_table(ss, as, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.arg_table(ss, as, self, wides, carried, path, ea): - match ss: - case Hoist.Slot{on, +at}: - match on: - case False{}: {==} - case True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), at), D1.inert.node(Calls.arg(as, at)), inert.arg(as, at)) : - {Hoist.one_yes(Hoist.is_table(_, self, wides, carried), _, path) - == Hoist.one_yes(Hoist.is_table(Calls.arg(as, at), self, wides, carried), Calls.arg(as, at), - path) : List<&2, F.Finding>} - inert.hoist.yes(Calls.arg(as, at), self, wides, carried, path, inert.ok.nth(as, at, ea)) - -# a get or set read that way, its table passed inline, is judged as it was -law inert.hoist.call_table: - for hot: Bool - for open: Bool - for +b: Bool - for +t: String - for +kids: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.call_table(hot, open, D1.inert.keep(b, t), D1.inert.node(kids), self, wides, carried, path) - == Hoist.call_table(hot, open, t, kids, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.call_table(hot, open, b, t, kids, self, wides, carried, path, ef, ek): - match hot: - case False{}: {==} - case True{}: - match open: - case False{}: {==} - case True{}: - +lhs = Hoist.arg_table(Hoist.slot_of(D1.inert.keep(b, t)), Calls.args(D1.inert.node(kids)), self, wides, - carried, path) - %inert.hoist.arg_table(Hoist.slot_of(t), Calls.args(kids), self, wides, carried, path, - inert.ok.args.go(kids, Tree.NNil{}, [], ek, {==}, {==})) : {lhs == _ : List<&2, F.Finding>} - %inert.args(kids) : {lhs == Hoist.arg_table(Hoist.slot_of(t), _, self, wides, carried, path) : List<&2, - F.Finding>} - %inert.hoist.slot_of(b, t, ef) : - {lhs == Hoist.arg_table(_, Calls.args(D1.inert.node(kids)), self, wides, carried, path) : List<&2, - F.Finding>} - {==} - -# four finding lists that read alike join alike -law inert.hoist.cat4: - for -a1: List<&2, F.Finding> - for -a2: List<&2, F.Finding> - for -b1: List<&2, F.Finding> - for -b2: List<&2, F.Finding> - for -c1: List<&2, F.Finding> - for -c2: List<&2, F.Finding> - for -d1: List<&2, F.Finding> - for -d2: List<&2, F.Finding> - for ea: {a1 == a2 : List<&2, F.Finding>} - for eb: {b1 == b2 : List<&2, F.Finding>} - for ec: {c1 == c2 : List<&2, F.Finding>} - for ed: {d1 == d2 : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a2, b2, c2, d2]) : List<&2, F.Finding>} - -def inert.hoist.cat4(a1, _a2, b1, b2, c1, c2, d1, d2, ea, eb, ec, ed): - %ea : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [_, b2, c2, d2]) : List<&2, - F.Finding>} - %eb : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a1, _, c2, d2]) : List<&2, - F.Finding>} - %ec : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a1, b1, _, d2]) : List<&2, - F.Finding>} - %ed : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a1, b1, c1, _]) : List<&2, - F.Finding>} - {==} - -# a name then a group: its inline table, the group and the rest walked as -# they were -law inert.hoist.walk_lg: - for +k: Lex.TokKind - for +t: String - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Hoist.walk(D1.inert.node(kids), self, wides, carried, path, Hoist.warm(hot, k)) - == Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - for ir: {Hoist.walk(D1.inert.node(r2), self, wides, carried, path, Hoist.warm(hot, k)) - == Hoist.walk(r2, self, wides, carried, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Hoist.call_table(Hoist.warm(hot, k), String.eq(o, "("), - D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, Hoist.warm(hot, k)), - Hoist.walk(D1.inert.node(r2), self, wides, carried, path, Hoist.warm(hot, k))]) - == List.concat(&2, F.Finding, [Hoist.call_table(Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, wides, - carried, path), Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)), - Hoist.walk(r2, self, wides, carried, path, Hoist.warm(hot, k))]) : List<&2, F.Finding>} - -def inert.hoist.walk_lg(k, t, o, kids, r2, self, wides, carried, path, hot, ef, ek, ik, ir): - +w = Hoist.warm(hot, k) - inert.cat3(Hoist.call_table(w, String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), - self, wides, - carried, path), Hoist.call_table(w, String.eq(o, "("), t, kids, self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, w), Hoist.walk(kids, self, wides, carried, path, w), - Hoist.walk(D1.inert.node(r2), self, wides, carried, path, w), Hoist.walk(r2, self, wides, carried, path, w), - inert.hoist.call_table(w, String.eq(o, "("), D1.inert.blanks(k), t, kids, self, wides, carried, path, ef, ek), - ik, ir) - -# a case arm read that way: hot as it was, and its pattern, body and the rest -# walked as they were -law inert.hoist.walk_case: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for ik: {Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}) - == Hoist.walk(kids, self, wides, carried, path, False{}) : List<&2, F.Finding>} - for ib: {Hoist.walk(D1.inert.node(body), self, wides, carried, path, Bool.and(hot, - Calls.calls(D1.inert.node(body), self))) - == Hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))) : List<&2, F.Finding>} - for ir: {Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot) == Hoist.walk(rest, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}), - Hoist.walk(D1.inert.node(body), self, wides, carried, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot)]) - == List.concat(&2, F.Finding, [Hoist.walk(kids, self, wides, carried, path, False{}), - Hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(body, self))), - Hoist.walk(rest, self, wides, carried, path, hot)]) : List<&2, F.Finding>} - -def inert.hoist.walk_case(kids, body, rest, self, wides, carried, path, hot, es, eb, ik, ib, ir): - +arm = Bool.and(hot, Calls.calls(D1.inert.node(body), self)) - +lhs = List.concat(&2, F.Finding, [Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}), - Hoist.walk(D1.inert.node(body), self, wides, carried, path, arm), Hoist.walk(D1.inert.node(rest), self, wides, - carried, path, hot)]) - %inert.calls(body, self, es, eb) : {lhs == List.concat(&2, F.Finding, [Hoist.walk(kids, self, wides, carried, path, - False{}), Hoist.walk(body, self, wides, carried, path, Bool.and(hot, _)), Hoist.walk(rest, self, wides, carried, - path, hot)]) : List<&2, F.Finding>} - inert.cat3(Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}), Hoist.walk(kids, self, wides, - carried, path, - False{}), Hoist.walk(D1.inert.node(body), self, wides, carried, path, arm), Hoist.walk(body, self, wides, - carried, path, arm), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot), Hoist.walk(rest, self, wides, carried, path, - hot), ik, ib, ir) - -# any other statement read that way: its let, its kids, body and the rest -# walked as they were -law inert.hoist.walk_st: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - for ik: {Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot) == Hoist.walk(kids, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - for ib: {Hoist.walk(D1.inert.node(body), self, wides, carried, path, hot) == Hoist.walk(body, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - for ir: {Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot) == Hoist.walk(rest, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Hoist.let_hit(hot, D1.inert.node(kids), D1.inert.node(body), D1.inert.node(rest), - self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot), Hoist.walk(D1.inert.node(body), self, wides, - carried, path, hot), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot)]) - == List.concat(&2, F.Finding, [Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, hot), Hoist.walk(body, self, wides, carried, path, hot), - Hoist.walk(rest, self, wides, carried, path, hot)]) : List<&2, F.Finding>} - -def inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, ik, ib, ir): - inert.hoist.cat4(Hoist.let_hit(hot, D1.inert.node(kids), D1.inert.node(body), D1.inert.node(rest), self, - wides, carried, path), - Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot), Hoist.walk(kids, self, wides, carried, path, - hot), - Hoist.walk(D1.inert.node(body), self, wides, carried, path, hot), Hoist.walk(body, self, wides, carried, path, - hot), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot), Hoist.walk(rest, self, wides, carried, path, - hot), - inert.hoist.let_hit(hot, kids, body, rest, self, wides, carried, path, ek, eb, er), ik, ib, ir) - -# hoist's walk reads no string: a string names no builder, no get or set, no -# carried parameter and no let, and a table never starts with one -law inert.hoist.walk: - for nn: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.walk(D1.inert.node(nn), self, wides, carried, path, hot) == Hoist.walk(nn, self, wides, carried, path, - hot) : List<&2, F.Finding>} - -def inert.hoist.walk(nn, self, wides, carried, path, hot, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.hoist.walk_lg(k, t, o, kids, r2, self, wides, carried, path, hot, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), inert.ok_h(gp, r2, eg), - inert.hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k), es, inert.ok_h(gp, - r2, - eg)), - inert.hoist.walk(r2, self, wides, carried, path, Hoist.warm(hot, k), es, inert.ok_t(gp, r2, - eg))) - case Tree.Leaf{x}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot), Hoist.walk(kids, self, wides, - carried, path, - hot), Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot), Hoist.walk(rest, self, wides, - carried, path, hot), - inert.hoist.walk(kids, self, wides, carried, path, hot, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.SDef{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SType{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SLaw{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SImport{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SCase{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_case(kids, body, rest, self, wides, carried, path, hot, es, eb, - inert.hoist.walk(kids, self, wides, carried, path, False{}, es, ek), - inert.hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self)), es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SFor{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SLet{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.STerm{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.NNil{}: inert.hoist.walk(rest, self, wides, carried, path, hot, es, e) - case Tree.NCons{x, y}: - inert.hoist.walk(rest, self, wides, carried, path, hot, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# hoist over the defs read that way reports what it reports over the defs -law inert.hoist.go: - for ds: List<&2, Calls.Def> - for +wide: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Hoist.check.go(inert.defs(ds), wide, path, acc) == Hoist.check.go(ds, wide, path, acc) : List<&2, F.Finding>} - -def inert.hoist.go(ds, wide, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +isg = D1.inert.node(sig) - +cr = Hoist.carried(isg, ib, name) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - isg)))), [], - _u => Hoist.walk(ib, name, wide, cr, path, True{})) - %inert.hoist.carried(sig, body, name, en, eb) : {Hoist.check.go(inert.defs(rest), wide, path, lst <> acc) - == Hoist.check.go(rest, wide, path, Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(body, name), - Bool.not(Calls.exempt(path, sig)))), [], _u => Hoist.walk(body, name, wide, _, path, - True{})) <> acc) : List<&2, F.Finding>} - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, isg)))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - Hoist.walk(ib, name, wide, cr, path, True{}), Hoist.walk(body, name, wide, cr, path, True{}), - inert.gate(path, name, sig, body, en, eb), inert.hoist.walk(body, name, wide, cr, path, True{}, en, eb)) : - {Hoist.check.go(inert.defs(rest), wide, path, lst <> acc) == Hoist.check.go(rest, wide, path, - _ <> acc) : List<&2, F.Finding>} - inert.hoist.go(rest, wide, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -# hoist over the defs read that way, their wide tables read that way too -law inert.hoist.all: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Hoist.check.go(inert.defs(ds), Hoist.wide_defs(inert.defs(ds), []), path, []) - == Hoist.check.go(ds, Hoist.wide_defs(ds, []), path, []) : List<&2, F.Finding>} - -def inert.hoist.all(ds, path, e): - %inert.hoist.wide_defs(ds, [], e) : {Hoist.check.go(inert.defs(ds), Hoist.wide_defs(inert.defs(ds), []), path, []) - == Hoist.check.go(ds, _, path, []) : List<&2, F.Finding>} - inert.hoist.go(ds, Hoist.wide_defs(inert.defs(ds), []), path, [], e) - -def Laws.inert_hoist(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Hoist.check.go(Calls.defs(z), Hoist.wide_defs(Calls.defs(z), []), path, []), tree, tree2, - D1.inert.node(tree), D1.inert.node(tree2), - inert.lift(z => Hoist.check.go(z, Hoist.wide_defs(z, []), path, []), tree, inert.hoist.all(Calls.defs(tree), path, - inert.dok.of(tree, ok))), - inert.lift(z => Hoist.check.go(z, Hoist.wide_defs(z, []), path, []), tree2, inert.hoist.all(Calls.defs(tree2), path, - inert.dok.of(tree2, ok2))), - e) - -# wrap -# ---- - -# a token's share of the width reads a string's kind only, and a comment has -# none -law inert.wrap.cost: - for k: Lex.TokKind - for +t: String - {Wrap.check.cost(k, t) == Wrap.check.cost(k, D1.inert.keep(D1.inert.blanks(k), t)) : U32} - -def inert.wrap.cost(k, _t): - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: + case Tree.Leaf{+tok}: + %inert.canon_tok(tok) : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == _ <> inert.canons(b) + : List<&2, Lex.Tok>} + %e : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == inert.canon(D1.inert.tok(tok)) <> _ + : List<&2, Lex.Tok>} {==} + case Tree.Group{+open, kids, close}: + inert.canon_close(close, Tree.leaves.go(D1.inert.node(kids), open <> a), Tree.leaves.go(kids, open <> b), + inert.canon_leaves(kids, open <> a, open <> b, Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, + z => inert.canon(open) <> z, inert.canons(a), inert.canons(b), e))) + case Tree.Stmt{sk, kids, body}: + inert.canon_leaves(body, Tree.leaves.go(D1.inert.node(kids), a), Tree.leaves.go(kids, b), + inert.canon_leaves(kids, a, b, e)) + case Tree.NNil{}: e + case Tree.NCons{h, t}: + inert.canon_leaves(t, Tree.leaves.go(D1.inert.node(h), a), Tree.leaves.go(h, b), inert.canon_leaves(h, a, b, e)) -# the width walk reads no comment or string -law inert.wrap.width: - for ts: List<&2, Lex.Tok> - for +acc: U32 - for +upto: U32 - for +seen: Bool - {Wrap.check.width.go(D1.inert.toks(ts), acc, upto, seen) == Wrap.check.width.go(ts, acc, upto, seen) : U32} +# reversing, by where they stand +law inert.canon_rev: + for xs: List<&2, Lex.Tok> + for +acc: List<&2, Lex.Tok> + {inert.canons(List.reverse.go(&2, Lex.Tok, xs, acc)) == List.reverse.go(&2, Lex.Tok, inert.canons(xs), + inert.canons(acc)) : List<&2, Lex.Tok>} -def inert.wrap.width(ts, acc, upto, seen): - match ts: - case Nil{}: - {==} - case Con{Lex.Tok{+k, +t, l, c}, +rest}: - +nx = Wrap.check.add(acc, k, t) - +co = Wrap.check.is_colon(k) - %inert.wrap.cost(k, t) : {Wrap.check.width.go(D1.inert.toks(rest), (acc + _ : U32), - Bool.pick(U32, co, (acc + _ : U32), upto), Bool.or(co, seen)) - == Wrap.check.width.go(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) : U32} - inert.wrap.width(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) +def inert.canon_rev(xs, acc): + match xs: + case Nil{}: {==} + case Con{+h, t}: inert.canon_rev(t, h <> acc) -# the line test reads kinds only -law inert.wrap.broken: +# the lines of tokens are the lines of where they stand +law inert.canon_lines: for ts: List<&2, Lex.Tok> - {Wrap.check.broken(D1.inert.toks(ts)) == Wrap.check.broken(ts) : Bool} + for +acc: List<&2, U32> + {Unused.lines(ts, acc) == Unused.lines(inert.canons(ts), acc) : List<&2, U32>} -def inert.wrap.broken(ts): +def inert.canon_lines(ts, acc): match ts: - case Nil{}: - {==} - case Con{Lex.Tok{+k, t, l, c}, +rest}: - %inert.wrap.broken(rest) : {Lazy.or_else(Lex.is_nl(k), _u => Wrap.check.broken(D1.inert.toks(rest))) - == Lazy.or_else(Lex.is_nl(k), _u => _) : Bool} - {==} - -# a string moves nothing, whatever the walk's phase: its text is read only -# for the angles a `>` run closes, and a string is no such run -law inert.wrap.str: - for +wk: Wrap.Walk - for +t: String - for +l: U32 - for +c: U32 - for glued: Bool - {Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, D1.inert.cut(t), l, c}, glued) - == Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, t, l, c}, glued) : Wrap.Walk} - -def inert.wrap.str(wk, _t, _l, _c, _glued): - match wk: - case Wrap.Walk{mode, paren, bracket, brace, angle, shut, open, on, pline, pend, pcol, spans}: - match mode: - case Wrap.Seek{}: - {==} - case Wrap.Args{}: - {==} - case Wrap.Past{}: - {==} - case Wrap.Shut{}: - {==} - case Wrap.Loose{}: - {==} - -# one token of the header moves the walk as it did: a comment moves nothing, -# a string as above, and any other token keeps its text -law inert.wrap.step: - for k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +wk: Wrap.Walk - for glued: Bool - {Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) == Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) - : Wrap.Walk} - -def inert.wrap.step(k, t, l, c, wk, glued): - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - inert.wrap.str(wk, t, l, c, glued) - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} + case Nil{}: {==} + case Con{h, +t}: + match h: + case Lex.Tok{k, x, +l, c}: + Equal.cong(List<&2, U32>, List<&2, U32>, z => l <> z, Unused.lines(t, acc), + Unused.lines(inert.canons(t), acc), inert.canon_lines(t, acc)) -# the shape walk reads no comment or string -law inert.wrap.walk: - for ts: List<&2, Lex.Tok> - for +wk: Wrap.Walk - for +glued: Bool - {Wrap.check.walk(D1.inert.toks(ts), wk, glued) == Wrap.check.walk(ts, wk, glued) : Wrap.Walk} +# the lines of a node's leaves read that way are the lines they were +law inert.unused.lines: + for +kids: Tree.Node + for +acc: List<&2, U32> + {Unused.lines(Tree.leaves(D1.inert.node(kids)), acc) == Unused.lines(Tree.leaves(kids), acc) : List<&2, U32>} -def inert.wrap.walk(ts, wk, glued): - match ts: - case Nil{}: - {==} - case Con{Lex.Tok{+k, +t, +l, +c}, +rest}: - +s1 = Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) - +s2 = Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) - Equal.trans(Wrap.Walk, Wrap.check.walk(D1.inert.toks(rest), s1, Lex.is_name(k)), - Wrap.check.walk(rest, s1, Lex.is_name(k)), Wrap.check.walk(rest, s2, Lex.is_name(k)), - inert.wrap.walk(rest, s1, Lex.is_name(k)), - Equal.cong(Wrap.Walk, Wrap.Walk, w => Wrap.check.walk(rest, w, Lex.is_name(k)), s1, s2, - inert.wrap.step(k, t, l, c, wk, glued))) +def inert.unused.lines(kids, acc): + +li = Tree.leaves.go(D1.inert.node(kids), []) + +lo = Tree.leaves.go(kids, []) + +ec = Equal.trans(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), List.reverse.go(&2, Lex.Tok, + inert.canons(li), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), inert.canon_rev(li, []), + Equal.trans(List<&2, Lex.Tok>, List.reverse.go(&2, Lex.Tok, inert.canons(li), []), List.reverse.go(&2, Lex.Tok, + inert.canons(lo), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), + Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => List.reverse.go(&2, Lex.Tok, z, []), inert.canons(li), + inert.canons(lo), inert.canon_leaves(kids, [], [], {==})), + Equal.sym(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), List.reverse.go(&2, Lex.Tok, + inert.canons(lo), []), inert.canon_rev(lo, [])))) + Equal.trans(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, li, []), acc), + Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), + Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), + inert.canon_lines(List.reverse.go(&2, Lex.Tok, li, []), acc), + Equal.trans(List<&2, U32>, Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), + Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), + Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), + Equal.cong(List<&2, Lex.Tok>, List<&2, U32>, z => Unused.lines(z, acc), + inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), ec), + Equal.sym(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), + Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), + inert.canon_lines(List.reverse.go(&2, Lex.Tok, lo, []), acc)))) -# a header, broken or not, is judged alike with its comments and strings cut -law inert.wrap.header: - for b: Bool - for +ts: List<&2, Lex.Tok> - for +line: U32 - for +path: String - for +more: List<&2, F.Finding> - {Wrap.check.header(b, line, D1.inert.toks(ts), path, more) == Wrap.check.header(b, line, ts, path, more) - : List<&2, F.Finding>} +# a node's `:` test, the node read that way, is the one it was +law inert.unused.colon: + for nn: Tree.Node + {Unused.colon(D1.inert.node(nn)) == Unused.colon(nn) : Bool} -def inert.wrap.header(b, ts, line, path, more): - match b: - case True{}: - Equal.cong(Wrap.Walk, List<&2, F.Finding>, wk => Wrap.check.place(Wrap.check.jam_span(wk), - Wrap.check.empty_span(wk), Wrap.check.shut_span(wk), Wrap.check.loose_span(wk), line, path, more), - Wrap.check.walk(D1.inert.toks(ts), Wrap.check.start(), False{}), - Wrap.check.walk(ts, Wrap.check.start(), False{}), inert.wrap.walk(ts, Wrap.check.start(), False{})) - case False{}: - Equal.cong(U32, List<&2, F.Finding>, ww => Lazy.stop(List<&2, F.Finding>, U32.is_gt(ww, 120), - F.Finding{path, line, 120, 0, "wrap", - "This def header is longer than 120 characters; wrap it with one parameter per line."} <> more, - _u => more), Wrap.check.width.go(D1.inert.toks(ts), 0, 0, False{}), Wrap.check.width.go(ts, 0, 0, False{}), - inert.wrap.width(ts, 0, 0, False{})) +def inert.unused.colon(nn): + match nn: + case Tree.Leaf{tok}: + match tok: + case Lex.Tok{k, t, l, c}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} -# a def header's tokens are judged alike with their comments and strings cut -law inert.wrap.lexed: - for +ts: List<&2, Lex.Tok> - for +line: U32 - for +path: String - for +more: List<&2, F.Finding> - {Wrap.check.lexed(line, D1.inert.toks(ts), path, more) == Wrap.check.lexed(line, ts, path, more) - : List<&2, F.Finding>} +# a statement's `:` ending, its chain read that way, is the one it was +law inert.unused.ends: + for kids: Tree.Node + for last: Bool + {Unused.foreign.ends(D1.inert.node(kids), last) == Unused.foreign.ends(kids, last) : Bool} -def inert.wrap.lexed(ts, line, path, more): - %Equal.sym(Bool, Wrap.check.broken(D1.inert.toks(ts)), Wrap.check.broken(ts), inert.wrap.broken(ts)) : - {Wrap.check.header(_, line, D1.inert.toks(ts), path, more) - == Wrap.check.header(Wrap.check.broken(ts), line, ts, path, more) : List<&2, F.Finding>} - inert.wrap.header(Wrap.check.broken(ts), ts, line, path, more) +def inert.unused.ends(kids, _last): + match kids: + case Tree.NCons{+h, +t}: + Equal.trans(Bool, Unused.foreign.ends(D1.inert.node(t), Unused.colon(D1.inert.node(h))), + Unused.foreign.ends(D1.inert.node(t), Unused.colon(h)), Unused.foreign.ends(t, Unused.colon(h)), + Equal.cong(Bool, Bool, z => Unused.foreign.ends(D1.inert.node(t), z), Unused.colon(D1.inert.node(h)), + Unused.colon(h), inert.unused.colon(h)), + inert.unused.ends(t, Unused.colon(h))) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# one item as wrap reads it: its kind and line, and its header's tokens -def inert.wrap.head( - it: Outline.Item, - ts: List<&2, Lex.Tok>, - +path: String, - +more: List<&2, F.Finding> -) -> List<&2, F.Finding>: - match it: - case Outline.Item{kk, _n, +line, _s, _d, _p}: - Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => Wrap.check.lexed(line, ts, path, more)) +# a body's opening import, the body read that way, is the one it was +law inert.unused.opens: + for body: Tree.Node + for open: Bool + {Unused.foreign.opens(D1.inert.node(body), open) == Unused.foreign.opens(body, open) : Bool} -# wrap over the items read that way: each item with its header's tokens -def inert.wrap.go( - hs: List<&2, Outline.Item>, - ss: List<&2, List<&2, Lex.Tok>>, - +path: String -) -> List<&2, F.Finding>: - match hs ss: - case Nil{} ss: - Nil{} - case Con{h, t} Nil{}: - Nil{} - case Con{h, t} Con{ts, st}: - inert.wrap.head(h, ts, path, inert.wrap.go(t, st, path)) +def inert.unused.opens(body, open): + match body: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, b}: + match open: + case True{}: + Equal.trans(Bool, + Unused.foreign.opens(D1.inert.node(rest), + Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), + Unused.foreign.opens(D1.inert.node(rest), Bool.not(Unused.foreign.ends(kids, False{}))), + Unused.foreign.opens(rest, Bool.not(Unused.foreign.ends(kids, False{}))), + Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(rest), Bool.not(z)), + Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), + inert.unused.ends(kids, False{})), + inert.unused.opens(rest, Bool.not(Unused.foreign.ends(kids, False{})))) + case False{}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# wrap's walk over the items is its walk over them read that way -law inert.wrap.items: - for items: List<&2, Outline.Item> - for +path: String - {Wrap.check.go(items, path) == inert.wrap.go(Laws.inert.items(items), Laws.inert.sigs(items), path) - : List<&2, F.Finding>} +# a def's lines onto the lines after it, the tree read that way, are the ones +# they were +law inert.unused.def: + for +kids: Tree.Node + for +body: Tree.Node + for +rest: Tree.Node + for +ih: {Unused.foreign(D1.inert.node(rest)) == Unused.foreign(rest) : List<&2, U32>} + {Bool.pick(List<&2, U32>, + Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), + Unused.lines(Tree.leaves(D1.inert.node(kids)), Unused.foreign(D1.inert.node(rest))), + Unused.foreign(D1.inert.node(rest))) + == Bool.pick(List<&2, U32>, Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))), + Unused.lines(Tree.leaves(kids), Unused.foreign(rest)), Unused.foreign(rest)) : List<&2, U32>} -def inert.wrap.items(items, path): - match items: - case Nil{}: - {==} - case Con{Outline.Item{+kk, n, +line, +sig, d, p}, +rest}: - +more = Wrap.check.go(rest, path) - +ts = Lex.tokens(sig) - %inert.wrap.items(rest, path) : {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, - _u => Wrap.check.lexed(line, ts, path, more)) - == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), _, - _u => Wrap.check.lexed(line, D1.inert.toks(ts), path, _)) : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, Wrap.check.lexed(line, D1.inert.toks(ts), path, more), - Wrap.check.lexed(line, ts, path, more), inert.wrap.lexed(ts, line, path, more)) : - {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, - _u => Wrap.check.lexed(line, ts, path, more)) - == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => _) : List<&2, F.Finding>} - {==} +def inert.unused.def(kids, body, rest, ih): + +fi = Unused.foreign(D1.inert.node(rest)) + +fo = Unused.foreign(rest) + +ra = Unused.lines(Tree.leaves(D1.inert.node(kids)), fi) + +rb = Unused.lines(Tree.leaves(kids), fo) + +c1 = Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))) + +c2 = Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))) + +ec = Equal.trans(Bool, c1, + Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(kids, False{}))), c2, + Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(body), Bool.not(z)), + Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), + inert.unused.ends(kids, False{})), + inert.unused.opens(body, Bool.not(Unused.foreign.ends(kids, False{})))) + +ea = Equal.trans(List<&2, U32>, ra, Unused.lines(Tree.leaves(kids), fi), rb, inert.unused.lines(kids, fi), + Equal.cong(List<&2, U32>, List<&2, U32>, z => Unused.lines(Tree.leaves(kids), z), fi, fo, ih)) + Equal.trans(List<&2, U32>, Bool.pick(List<&2, U32>, c1, ra, fi), Bool.pick(List<&2, U32>, c2, ra, fi), + Bool.pick(List<&2, U32>, c2, rb, fo), + Equal.cong(Bool, List<&2, U32>, z => Bool.pick(List<&2, U32>, z, ra, fi), c1, c2, ec), + M2.unused.pick(c2, ra, rb, fi, fo, ea, ih)) -def Laws.inert_wrap(path, _text, _toks, _tree, _bound, items, _text2, _toks2, _tree2, _bound2, items2, ei, es): - +i1 = Laws.inert.items(items) - +s1 = Laws.inert.sigs(items) - %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items, path), inert.wrap.go(i1, s1, path), - inert.wrap.items(items, path)) : {_ == Wrap.check.go(items2, path) : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items2, path), - inert.wrap.go(Laws.inert.items(items2), Laws.inert.sigs(items2), path), inert.wrap.items(items2, path)) : - {inert.wrap.go(i1, s1, path) == _ : List<&2, F.Finding>} - %ei : {inert.wrap.go(i1, s1, path) == inert.wrap.go(_, Laws.inert.sigs(items2), path) : List<&2, F.Finding>} - %es : {inert.wrap.go(i1, s1, path) == inert.wrap.go(i1, _, path) : List<&2, F.Finding>} - {==} +# the foreign defs' header lines, the tree read that way, are the ones they were +law inert.unused.foreign: + for root: Tree.Node + {Unused.foreign(D1.inert.node(root)) == Unused.foreign(root) : List<&2, U32>} -# scan reads no string: a string is never a callee or a lone name, so each -# walk and each call reads the same over a tree read that way +def inert.unused.foreign(root): + match root: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, +body}: + match sk: + case Tree.SDef{}: inert.unused.def(kids, body, rest, inert.unused.foreign(rest)) + case Tree.SType{}: inert.unused.foreign(rest) + case Tree.SLaw{}: inert.unused.foreign(rest) + case Tree.SImport{}: inert.unused.foreign(rest) + case Tree.SCase{}: inert.unused.foreign(rest) + case Tree.SFor{}: inert.unused.foreign(rest) + case Tree.SLet{}: inert.unused.foreign(rest) + case Tree.STerm{}: inert.unused.foreign(rest) + case Tree.Leaf{x}: inert.unused.foreign(rest) + case Tree.Group{x, y, z}: inert.unused.foreign(rest) + case Tree.NNil{}: inert.unused.foreign(rest) + case Tree.NCons{x, y}: inert.unused.foreign(rest) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# a lone name read that way is the name it was -law inert.scan.name_tok: - for tok: Lex.Tok - {Scan.name_tok(D1.inert.tok(tok)) == Scan.name_tok(tok) : Maybe<&2, String>} +# does a binder of this kind count, its note read as unused reads it +def inert.unused.rep(kk: Bind.BindKind, +note: String, +line: U32, +fl: List<&2, U32>) -> Bool: + match kk: + case Bind.KLocal{}: + True{} + case Bind.KParam{}: + Bool.and(Bool.not(String.starts_with(note, "-")), Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) + case other: + False{} -def inert.scan.name_tok(tok): - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} +# unused over binders as it reads them +def inert.unused.go( + binds: List<&2, Bind.Bind>, + +hh: Unused.Hits, + +uses: List<&2, Bind.Use>, + +fl: List<&2, U32>, + +path: String +) -> List<&2, F.Finding>: + match binds: + case Nil{}: + Nil{} + case Con{Bind.Bind{+name, +line, +col, +kind, +note}, rest}: + +more = inert.unused.go(rest, hh, uses, fl, path) + +hit = Bool.and(inert.unused.rep(kind, note, line, fl), Bool.not(String.starts_with(name, "_"))) + Bool.pick(List<&2, F.Finding>, Lazy.and_then(hit, _u => Bool.not(Unused.seen(hh, uses, line, col))), + F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", + Unused.what(kind) ++ " " ++ name ++ " is never used."} <> more, more) -# an argument read that way is the lone name it was -law inert.scan.name_of: - for nn: Tree.Node - {Scan.name_of(D1.inert.node(nn)) == Scan.name_of(nn) : Maybe<&2, String>} +# a leading `-`, kept as unused reads a note +law inert.unused.dash: + for s: Bool + {String.starts_with(Bool.pick(String, s, "-", ""), "-") == s : Bool} -def inert.scan.name_of(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: - inert.scan.name_tok(tok) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} +def inert.unused.dash(s): + match s: + case True{}: {==} + case False{}: {==} -# the walked argument of arguments read that way is the lone name it was -law inert.scan.arg_name: - for +as: List<&2, Tree.Node> - for +at: Nat - {Scan.name_of(Calls.arg(inert.nodes(as), at)) == Scan.name_of(Calls.arg(as, at)) : Maybe<&2, String>} +# a binder kept as unused reads it +law inert.unused.one: + for kind: Bind.BindKind + for +note: String + for +line: U32 + for +fl: List<&2, U32> + {Unused.reportable(kind, note, line, fl) == inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), + "-", ""), line, fl) : Bool} -def inert.scan.arg_name(as, at): - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), at), D1.inert.node(Calls.arg(as, at)), inert.arg(as, at)) : - {Scan.name_of(_) == Scan.name_of(Calls.arg(as, at)) : Maybe<&2, String>} - inert.scan.name_of(Calls.arg(as, at)) +def inert.unused.one(kind, note, line, fl): + match kind: + case Bind.KParam{}: + %Equal.sym(Bool, String.starts_with(Bool.pick(String, String.starts_with(note, "-"), "-", ""), "-"), + String.starts_with(note, "-"), inert.unused.dash(String.starts_with(note, "-"))) : + {Bool.and(Bool.not(String.starts_with(note, "-")), + Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) == Bool.and(Bool.not(_), Bool.not(List.contains(~U32, + ~U32.is_eq, fl, line))) : Bool} + {==} + case Bind.KItem{}: {==} + case Bind.KCtor{}: {==} + case Bind.KTypeParam{}: {==} + case Bind.KField{}: {==} + case Bind.KLocal{}: {==} + case Bind.KPat{}: {==} + case Bind.KFor{}: {==} + case Bind.KTypeVar{}: {==} -# the first carried name among arguments read that way is the one it was -law inert.scan.found: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +carried: List<&2, String> - {Scan.found(ss, inert.nodes(as), carried) == Scan.found(ss, as, carried) : Maybe<&2, String>} +# unused over binders is unused over the binders as it reads them +law inert.unused.via: + for binds: List<&2, Bind.Bind> + for +hh: Unused.Hits + for +uses: List<&2, Bind.Use> + for +fl: List<&2, U32> + for +path: String + {Unused.check.go(binds, hh, uses, fl, path) == inert.unused.go(Laws.inert.ubinds(binds), hh, uses, fl, path) + : List<&2, F.Finding>} -def inert.scan.found(ss, as, carried): - match ss: - case Nil{}: - {==} - case Con{+at, +rest}: - %inert.scan.arg_name(as, at) : {Scan.first(Scan.keep_in(Scan.name_of(Calls.arg(inert.nodes(as), at)), carried), - Scan.found(rest, inert.nodes(as), carried)) == Scan.first(Scan.keep_in(_, carried), Scan.found(rest, as, - carried)) : Maybe<&2, String>} - %inert.scan.found(rest, as, carried) : {Scan.first(Scan.keep_in(Scan.name_of(Calls.arg(inert.nodes(as), at)), - carried), Scan.found(rest, inert.nodes(as), carried)) == Scan.first(Scan.keep_in(Scan.name_of(Calls.arg( - inert.nodes(as), at)), carried), _) : Maybe<&2, String>} +def inert.unused.via(binds, hh, uses, fl, path): + match binds: + case Nil{}: {==} + case Con{Bind.Bind{+name, +line, +col, +kind, +note}, +rest}: + +mo = Unused.check.go(rest, hh, uses, fl, path) + +x = {F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", + Unused.what(kind) ++ " " ++ name ++ " is never used."} : F.Finding} + +lhs = Unused.check.go(Con{Bind.Bind{name, line, col, kind, note}, rest}, hh, uses, fl, path) + %inert.unused.via(rest, hh, uses, fl, path) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and( + inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), "-", ""), line, fl), + Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> _, _) + : List<&2, F.Finding>} + %inert.unused.one(kind, note, line, fl) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and(_, + Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> mo, mo) + : List<&2, F.Finding>} {==} -# a named call whose arguments are read that way reports what it did -law inert.scan.on_name: - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String +# unused over binders and lines that read alike +law inert.unused.cong: + for -x1: List<&2, Bind.Bind> + for -x2: List<&2, Bind.Bind> + for -f1: List<&2, U32> + for -f2: List<&2, U32> + for +hh: Unused.Hits + for +uses: List<&2, Bind.Use> for +path: String - {Scan.on_name(hot, open, tt, line, col, D1.inert.node(kids), self, ws, carried, lead, path) - == Scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) : List<&2, F.Finding>} - -def inert.scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path): - +g = Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self))))) - +lhs = Scan.on_name(hot, open, tt, line, col, D1.inert.node(kids), self, ws, carried, lead, path) - %inert.scan.found(Scan.slots(tt, ws), Calls.args(kids), carried) : {lhs == Lazy.stop(List<&2, F.Finding>, g, [], - _u => Scan.cite(_, tt, line, col, self, lead, path)) : List<&2, F.Finding>} - %inert.args(kids) : {lhs == Lazy.stop(List<&2, F.Finding>, g, [], - _u => Scan.cite(Scan.found(Scan.slots(tt, ws), _, carried), tt, line, col, self, lead, path)) - : List<&2, F.Finding>} + for e1: {x1 == x2 : List<&2, Bind.Bind>} + for e2: {f1 == f2 : List<&2, U32>} + {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x2, hh, uses, f2, path) : List<&2, F.Finding>} + +def inert.unused.cong(x1, _x2, f1, f2, hh, uses, path, e1, e2): + %e1 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(_, hh, uses, f2, path) : List<&2, F.Finding>} + %e2 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x1, hh, uses, _, path) : List<&2, F.Finding>} {==} -# a call read that way reports what it did: only a name is a callee, and a -# name is never cut -law inert.scan.call_hit: - for kk: Lex.TokKind - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - {Scan.call_hit(kk, hot, open, D1.inert.keep(D1.inert.blanks(kk), tt), line, col, D1.inert.node(kids), self, - ws, carried, lead, path) - == Scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path) : List<&2, F.Finding>} +def Laws.inert_unused( + path, + _text, + _toks, + tree, + binds, + uses, + _scopes, + _items, + _text2, + _toks2, + tree2, + binds2, + _scopes2, + _items2, + et, + eb +): + +f1 = Unused.foreign(tree) + +f2 = Unused.foreign(tree2) + +ef = Equal.trans(List<&2, U32>, f1, Unused.foreign(D1.inert.node(tree)), f2, + Equal.sym(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), f1, inert.unused.foreign(tree)), + Equal.trans(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), Unused.foreign(D1.inert.node(tree2)), f2, + Equal.cong(Tree.Node, List<&2, U32>, z => Unused.foreign(z), D1.inert.node(tree), D1.inert.node(tree2), et), + inert.unused.foreign(tree2))) + +hh = Unused.index(uses) + Equal.trans(List<&2, F.Finding>, Unused.check.go(binds, hh, uses, f1, path), + inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), Unused.check.go(binds2, hh, uses, f2, path), + inert.unused.via(binds, hh, uses, f1, path), + Equal.trans(List<&2, F.Finding>, inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), + inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), Unused.check.go(binds2, hh, uses, f2, path), + inert.unused.cong(Laws.inert.ubinds(binds), Laws.inert.ubinds(binds2), f1, f2, hh, uses, path, eb, ef), + Equal.sym(List<&2, F.Finding>, Unused.check.go(binds2, hh, uses, f2, path), + inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), inert.unused.via(binds2, hh, uses, f2, path)))) -def inert.scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path): - match kk: +# Inert text, hoist (laws/CodeRulesIgnoreCommentsAndStrings.bend): a def's self-calls, its carried +# parameters, the tables it builds and the names it indexes, each read the +# way the lemmas above read a tree. + +# hoist +# ----- + +# a token's share of the width reads a string's kind only, and a comment has +# none +law inert.wrap.cost: + for k: Lex.TokKind + for +t: String + {Wrap.check.cost(k, t) == Wrap.check.cost(k, D1.inert.keep(D1.inert.blanks(k), t)) : U32} + +def inert.wrap.cost(k, _t): + match k: case Lex.TName{}: - inert.scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) + {==} case Lex.TUpper{}: {==} case Lex.TDotted{}: - inert.scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) + {==} case Lex.TWild{}: {==} case Lex.TKey{}: @@ -13448,368 +7216,86 @@ def inert.scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, l case Lex.TComma{}: {==} -# a name then a group read that way: its call, the group and the rest walked -# as they were -law inert.scan.walk_lg: - for +k: Lex.TokKind - for +t: String - for +o: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +r2: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - for ik: {Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, Hoist.warm(hot, k)) == Scan.walk(kids, self, - ws, carried, lead, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - for ir: {Scan.walk(D1.inert.node(r2), self, ws, carried, lead, path, Hoist.warm(hot, k)) == Scan.walk(r2, self, ws, - carried, lead, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), - D1.inert.keep(D1.inert.blanks(k), t), - l, c, D1.inert.node(kids), self, ws, carried, lead, path), Scan.walk(D1.inert.node(kids), self, ws, carried, - lead, path, Hoist.warm(hot, k)), Scan.walk(D1.inert.node(r2), self, ws, carried, lead, path, Hoist.warm(hot, - k))]) - == List.concat(&2, F.Finding, [Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, - carried, lead, - path), Scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, - k)), Scan.walk(r2, self, ws, carried, lead, path, Hoist.warm(hot, k))]) : List<&2, F.Finding>} - -def inert.scan.walk_lg(k, t, o, l, c, kids, r2, self, ws, carried, lead, path, hot, ik, ir): - +w = Hoist.warm(hot, k) - inert.cat3(Scan.call_hit(k, w, String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), l, c, - D1.inert.node(kids), self, - ws, carried, lead, path), Scan.call_hit(k, w, String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, path), - Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, w), Scan.walk(kids, self, ws, carried, lead, path, - w), - Scan.walk(D1.inert.node(r2), self, ws, carried, lead, path, w), Scan.walk(r2, self, ws, carried, lead, path, w), - inert.scan.call_hit(k, w, String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, path), ik, ir) - -# a case arm read that way: hot as it was, and its pattern, body and the rest -# walked as they were -law inert.scan.walk_case: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for ik: {Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}) == Scan.walk(kids, self, ws, - carried, lead, path, False{}) : List<&2, F.Finding>} - for ib: {Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, Bool.and(hot, - Calls.calls(D1.inert.node(body), - self))) == Scan.walk(body, self, ws, carried, lead, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))) : List<&2, F.Finding>} - for ir: {Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot) == Scan.walk(rest, self, ws, carried, - lead, path, hot) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}), - Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))), Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot)]) - == List.concat(&2, F.Finding, [Scan.walk(kids, self, ws, carried, lead, path, False{}), Scan.walk(body, self, ws, - carried, lead, path, Bool.and(hot, Calls.calls(body, - self))), Scan.walk(rest, self, ws, carried, lead, path, hot)]) - : List<&2, F.Finding>} - -def inert.scan.walk_case(kids, body, rest, self, ws, carried, lead, path, hot, es, eb, ik, ib, ir): - +arm = Bool.and(hot, Calls.calls(D1.inert.node(body), self)) - +lhs = List.concat(&2, F.Finding, [Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}), - Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, arm), Scan.walk(D1.inert.node(rest), self, ws, - carried, lead, path, hot)]) - %inert.calls(body, self, es, eb) : {lhs == List.concat(&2, F.Finding, [Scan.walk(kids, self, ws, carried, lead, path, - False{}), - Scan.walk(body, self, ws, carried, lead, path, Bool.and(hot, _)), Scan.walk(rest, self, ws, carried, lead, path, - hot)]) : List<&2, F.Finding>} - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}), Scan.walk(kids, self, ws, - carried, lead, path, False{}), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, arm), - Scan.walk(body, self, ws, carried, lead, path, arm), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, carried, lead, - path, hot), ik, ib, ir) - -# scan's walk reads no string: a string is never a callee or a lone name -law inert.scan.walk: - for nn: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Scan.walk(D1.inert.node(nn), self, ws, carried, lead, path, hot) == Scan.walk(nn, self, ws, carried, lead, path, - hot) : List<&2, F.Finding>} +# the width walk reads no comment or string +law inert.wrap.width: + for ts: List<&2, Lex.Tok> + for +acc: U32 + for +upto: U32 + for +seen: Bool + {Wrap.check.width.go(D1.inert.toks(ts), acc, upto, seen) == Wrap.check.width.go(ts, acc, upto, seen) : U32} -def inert.scan.walk(nn, self, ws, carried, lead, path, hot, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.scan.walk_lg(k, t, o, l, c, kids, r2, self, ws, carried, lead, path, hot, - inert.scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, k), es, inert.ok_h(gp, - r2, eg)), - inert.scan.walk(r2, self, ws, carried, lead, path, Hoist.warm(hot, k), es, inert.ok_t(gp, - r2, eg))) - case Tree.Leaf{x}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, ws, - carried, lead, path, hot), Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.SDef{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SType{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SLaw{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SImport{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SCase{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.scan.walk_case(kids, body, rest, self, ws, carried, lead, path, hot, es, eb, - inert.scan.walk(kids, self, ws, carried, lead, path, False{}, es, ek), - inert.scan.walk(body, self, ws, carried, lead, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self)), es, eb), - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, er)) - case Tree.SFor{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SLet{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.STerm{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.NNil{}: - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, e) - case Tree.NCons{x, y}: - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: +def inert.wrap.width(ts, acc, upto, seen): + match ts: + case Nil{}: {==} + case Con{Lex.Tok{+k, +t, l, c}, +rest}: + +nx = Wrap.check.add(acc, k, t) + +co = Wrap.check.is_colon(k) + %inert.wrap.cost(k, t) : {Wrap.check.width.go(D1.inert.toks(rest), (acc + _ : U32), + Bool.pick(U32, co, (acc + _ : U32), upto), Bool.or(co, seen)) + == Wrap.check.width.go(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) : U32} + inert.wrap.width(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) -# three lists of numbers that read alike join alike -law scan.ncat3: - for -a1: List<&2, Nat> - for -a2: List<&2, Nat> - for -b1: List<&2, Nat> - for -b2: List<&2, Nat> - for -c1: List<&2, Nat> - for -c2: List<&2, Nat> - for ea: {a1 == a2 : List<&2, Nat>} - for eb: {b1 == b2 : List<&2, Nat>} - for ec: {c1 == c2 : List<&2, Nat>} - {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [a2, b2, c2]) : List<&2, Nat>} - -def scan.ncat3(a1, _a2, b1, b2, c1, c2, ea, eb, ec): - %ea : {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [_, b2, c2]) : List<&2, Nat>} - %eb : {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [a1, _, c2]) : List<&2, Nat>} - %ec : {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [a1, b1, _]) : List<&2, Nat>} - {==} - -# two lists of numbers that read alike join alike -law scan.ncat2: - for -a1: List<&2, Nat> - for -a2: List<&2, Nat> - for -b1: List<&2, Nat> - for -b2: List<&2, Nat> - for ea: {a1 == a2 : List<&2, Nat>} - for eb: {b1 == b2 : List<&2, Nat>} - {List.concat(&2, Nat, [a1, b1]) == List.concat(&2, Nat, [a2, b2]) : List<&2, Nat>} - -def scan.ncat2(a1, _a2, b1, b2, ea, eb): - %ea : {List.concat(&2, Nat, [a1, b1]) == List.concat(&2, Nat, [_, b2]) : List<&2, Nat>} - %eb : {List.concat(&2, Nat, [a1, b1]) == List.concat(&2, Nat, [a1, _]) : List<&2, Nat>} - {==} - -# the parameters at walked arguments read that way are the ones they were -law inert.scan.params_at: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +ps: List<&2, String> - {Scan.params_at(ss, inert.nodes(as), ps) == Scan.params_at(ss, as, ps) : List<&2, Nat>} +# the line test reads kinds only +law inert.wrap.broken: + for ts: List<&2, Lex.Tok> + {Wrap.check.broken(D1.inert.toks(ts)) == Wrap.check.broken(ts) : Bool} -def inert.scan.params_at(ss, as, ps): - match ss: +def inert.wrap.broken(ts): + match ts: case Nil{}: {==} - case Con{+at, +rest}: - %inert.scan.arg_name(as, at) : {List.append(&2, Nat, - Scan.opt(Scan.index_in(Scan.name_of(Calls.arg(inert.nodes(as), - at)), ps)), Scan.params_at(rest, inert.nodes(as), ps)) == List.append(&2, Nat, Scan.opt(Scan.index_in(_, ps)), - Scan.params_at(rest, as, ps)) : List<&2, Nat>} - %inert.scan.params_at(rest, as, ps) : {List.append(&2, Nat, Scan.opt(Scan.index_in(Scan.name_of(Calls.arg( - inert.nodes(as), at)), ps)), Scan.params_at(rest, inert.nodes(as), ps)) == List.append(&2, Nat, - Scan.opt(Scan.index_in(Scan.name_of(Calls.arg(inert.nodes(as), at)), ps)), _) : List<&2, Nat>} + case Con{Lex.Tok{+k, t, l, c}, +rest}: + %inert.wrap.broken(rest) : {Lazy.or_else(Lex.is_nl(k), _u => Wrap.check.broken(D1.inert.toks(rest))) + == Lazy.or_else(Lex.is_nl(k), _u => _) : Bool} {==} -# a named call's walked parameters, its arguments read that way -law inert.scan.pass_name: - for +open: Bool - for +tt: String - for +kids: Tree.Node - for +ps: List<&2, String> - for +ws: List<&2, Scan.Walk> - {Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), - Calls.args(D1.inert.node(kids)), ps)) - == Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), Calls.args(kids), - ps)) : List<&2, Nat>} - -def inert.scan.pass_name(open, tt, kids, ps, ws): - +lhs = Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), - Calls.args(D1.inert.node(kids)), ps)) - %inert.scan.params_at(Scan.slots(tt, ws), Calls.args(kids), ps) : {lhs == Lazy.stop(List<&2, Nat>, Bool.not(open), [], - _u => _) : List<&2, Nat>} - %inert.args(kids) : {lhs == Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), _, - ps)) - : List<&2, Nat>} - {==} +# a string moves nothing, whatever the walk's phase: its text is read only +# for the angles a `>` run closes, and a string is no such run +law inert.wrap.str: + for +wk: Wrap.Walk + for +t: String + for +l: U32 + for +c: U32 + for glued: Bool + {Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, D1.inert.cut(t), l, c}, glued) + == Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, t, l, c}, glued) : Wrap.Walk} -# a call read that way passes what it passed: only a name is a callee -law inert.scan.pass_hit: - for kk: Lex.TokKind - for +open: Bool - for +tt: String - for +kids: Tree.Node - for +ps: List<&2, String> - for +ws: List<&2, Scan.Walk> - {Scan.pass_hit(kk, open, D1.inert.keep(D1.inert.blanks(kk), tt), D1.inert.node(kids), ps, ws) - == Scan.pass_hit(kk, open, tt, kids, ps, ws) : List<&2, Nat>} +def inert.wrap.str(wk, _t, _l, _c, _glued): + match wk: + case Wrap.Walk{mode, paren, bracket, brace, angle, shut, open, on, pline, pend, pcol, spans}: + match mode: + case Wrap.Seek{}: + {==} + case Wrap.Args{}: + {==} + case Wrap.Past{}: + {==} + case Wrap.Shut{}: + {==} + case Wrap.Loose{}: + {==} -def inert.scan.pass_hit(kk, open, tt, kids, ps, ws): - match kk: +# one token of the header moves the walk as it did: a comment moves nothing, +# a string as above, and any other token keeps its text +law inert.wrap.step: + for k: Lex.TokKind + for +t: String + for +l: U32 + for +c: U32 + for +wk: Wrap.Walk + for glued: Bool + {Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) == Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) + : Wrap.Walk} + +def inert.wrap.step(k, t, l, c, wk, glued): + match k: case Lex.TName{}: - inert.scan.pass_name(open, tt, kids, ps, ws) + {==} case Lex.TUpper{}: {==} case Lex.TDotted{}: - inert.scan.pass_name(open, tt, kids, ps, ws) + {==} case Lex.TWild{}: {==} case Lex.TKey{}: @@ -13817,7 +7303,7 @@ def inert.scan.pass_hit(kk, open, tt, kids, ps, ws): case Lex.TNum{}: {==} case Lex.TStr{}: - {==} + inert.wrap.str(wk, t, l, c, glued) case Lex.TChar{}: {==} case Lex.TComment{}: @@ -13849,280 +7335,126 @@ def inert.scan.pass_hit(kk, open, tt, kids, ps, ws): case Lex.TComma{}: {==} -# what a body read that way passes is what it passed -law inert.scan.passes: - for nn: Tree.Node - for +ps: List<&2, String> - for +ws: List<&2, Scan.Walk> - {Scan.passes(D1.inert.node(nn), ps, ws) == Scan.passes(nn, ps, ws) : List<&2, Nat>} - -def inert.scan.passes(nn, ps, ws): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - scan.ncat3(Scan.pass_hit(k, String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), - D1.inert.node(kids), ps, ws), Scan.pass_hit(k, String.eq(o, "("), t, kids, ps, ws), - Scan.passes(D1.inert.node(kids), ps, ws), Scan.passes(kids, ps, ws), - Scan.passes(D1.inert.node(r2), ps, ws), Scan.passes(r2, ps, ws), - inert.scan.pass_hit(k, String.eq(o, "("), t, kids, ps, ws), - inert.scan.passes(kids, ps, ws), inert.scan.passes(r2, ps, ws)) - case Tree.Leaf{x}: - inert.scan.passes(rest, ps, ws) - case Tree.Stmt{x, y, z}: - inert.scan.passes(rest, ps, ws) - case Tree.NNil{}: - inert.scan.passes(rest, ps, ws) - case Tree.NCons{x, y}: - inert.scan.passes(rest, ps, ws) - case Tree.Leaf{x}: - inert.scan.passes(rest, ps, ws) - case Tree.Group{x, y, z}: - inert.scan.passes(rest, ps, ws) - case Tree.Stmt{x, y, z}: - inert.scan.passes(rest, ps, ws) - case Tree.NNil{}: - inert.scan.passes(rest, ps, ws) - case Tree.Group{o, +kids, cl}: - scan.ncat2(Scan.passes(D1.inert.node(kids), ps, ws), Scan.passes(kids, ps, ws), - Scan.passes(D1.inert.node(rest), ps, ws), - Scan.passes(rest, ps, ws), inert.scan.passes(kids, ps, ws), inert.scan.passes(rest, ps, ws)) - case Tree.Stmt{sk, +kids, +body}: - scan.ncat3(Scan.passes(D1.inert.node(kids), ps, ws), Scan.passes(kids, ps, ws), - Scan.passes(D1.inert.node(body), ps, ws), - Scan.passes(body, ps, ws), Scan.passes(D1.inert.node(rest), ps, ws), Scan.passes(rest, ps, ws), - inert.scan.passes(kids, ps, ws), inert.scan.passes(body, ps, ws), inert.scan.passes(rest, ps, ws)) - case Tree.NNil{}: - inert.scan.passes(rest, ps, ws) - case Tree.NCons{x, y}: - inert.scan.passes(rest, ps, ws) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# a parameter read that way moves as it did -law inert.scan.moves: - for +pp: Tree.Node - for +carried: List<&2, String> - {Scan.moves(D1.inert.node(pp), carried) == Scan.moves(pp, carried) : Bool} - -def inert.scan.moves(pp, carried): - +lhs = Scan.moves(D1.inert.node(pp), carried) - %inert.is_live(pp) : {lhs == Bool.and(_, Bool.not(List.contains(~String, ~String.eq, carried, Calls.param_name(pp)))) - : Bool} - %inert.param_name(pp) : {lhs == Bool.and(Calls.is_live(D1.inert.node(pp)), Bool.not(List.contains(~String, - ~String.eq, - carried, _))) : Bool} - {==} - -# a parameter read that way is a count or a text as it was -law inert.scan.place: - for +pp: Tree.Node - for +ii: Nat - {Scan.place(D1.inert.node(pp), ii) == Scan.place(pp, ii) : Maybe<&2, Nat>} - -def inert.scan.place(pp, ii): - %inert.type_head(pp, False{}) : {Scan.place(D1.inert.node(pp), ii) == Bool.pick(Maybe<&2, Nat>, - Bool.or(String.eq(_, "Nat"), String.eq(_, "String")), None{}, Some{ii}) : Maybe<&2, Nat>} - {==} - -# the parameter shrunk, among parameters read that way, sits where it sat -law inert.scan.own.go: - for ps: List<&2, Tree.Node> - for +carried: List<&2, String> - for +ii: Nat - {Scan.own.go(inert.nodes(ps), carried, ii) == Scan.own.go(ps, carried, ii) : Maybe<&2, Nat>} +# the shape walk reads no comment or string +law inert.wrap.walk: + for ts: List<&2, Lex.Tok> + for +wk: Wrap.Walk + for +glued: Bool + {Wrap.check.walk(D1.inert.toks(ts), wk, glued) == Wrap.check.walk(ts, wk, glued) : Wrap.Walk} -def inert.scan.own.go(ps, carried, ii): - match ps: +def inert.wrap.walk(ts, wk, glued): + match ts: case Nil{}: {==} - case Con{+p, +rest}: - +lhs = Scan.own.go(inert.nodes(p <> rest), carried, ii) - %inert.scan.moves(p, carried) : {lhs == Lazy.stop(Maybe<&2, Nat>, _, Scan.place(p, ii), - _u => Scan.own.go(rest, carried, 1n+ii)) : Maybe<&2, Nat>} - %inert.scan.place(p, ii) : {lhs == Lazy.stop(Maybe<&2, Nat>, Scan.moves(D1.inert.node(p), carried), _, - _u => Scan.own.go(rest, carried, 1n+ii)) : Maybe<&2, Nat>} - %inert.scan.own.go(rest, carried, 1n+ii) : {lhs == Lazy.stop(Maybe<&2, Nat>, Scan.moves(D1.inert.node(p), - carried), Scan.place(D1.inert.node(p), ii), _u => _) : Maybe<&2, Nat>} - {==} + case Con{Lex.Tok{+k, +t, +l, +c}, +rest}: + +s1 = Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) + +s2 = Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) + Equal.trans(Wrap.Walk, Wrap.check.walk(D1.inert.toks(rest), s1, Lex.is_name(k)), + Wrap.check.walk(rest, s1, Lex.is_name(k)), Wrap.check.walk(rest, s2, Lex.is_name(k)), + inert.wrap.walk(rest, s1, Lex.is_name(k)), + Equal.cong(Wrap.Walk, Wrap.Walk, w => Wrap.check.walk(rest, w, Lex.is_name(k)), s1, s2, + inert.wrap.step(k, t, l, c, wk, glued))) -# the name of the parameter shrunk, among parameters read that way, is the one it was -law inert.scan.lead.go: - for ps: List<&2, Tree.Node> - for +carried: List<&2, String> - {Scan.lead.go(inert.nodes(ps), carried) == Scan.lead.go(ps, carried) : String} +# a header, broken or not, is judged alike with its comments and strings cut +law inert.wrap.header: + for b: Bool + for +ts: List<&2, Lex.Tok> + for +line: U32 + for +path: String + for +more: List<&2, F.Finding> + {Wrap.check.header(b, line, D1.inert.toks(ts), path, more) == Wrap.check.header(b, line, ts, path, more) + : List<&2, F.Finding>} -def inert.scan.lead.go(ps, carried): - match ps: - case Nil{}: - {==} - case Con{+p, +rest}: - +lhs = Scan.lead.go(inert.nodes(p <> rest), carried) - %inert.scan.moves(p, carried) : {lhs == Lazy.stop(String, _, Calls.param_name(p), _u => Scan.lead.go(rest, - carried)) : String} - %inert.param_name(p) : {lhs == Lazy.stop(String, Scan.moves(D1.inert.node(p), carried), _, - _u => Scan.lead.go(rest, - carried)) : String} - %inert.scan.lead.go(rest, carried) : {lhs == Lazy.stop(String, Scan.moves(D1.inert.node(p), carried), - Calls.param_name(D1.inert.node(p)), _u => _) : String} - {==} +def inert.wrap.header(b, ts, line, path, more): + match b: + case True{}: + Equal.cong(Wrap.Walk, List<&2, F.Finding>, wk => Wrap.check.place(Wrap.check.jam_span(wk), + Wrap.check.empty_span(wk), Wrap.check.shut_span(wk), Wrap.check.loose_span(wk), line, path, more), + Wrap.check.walk(D1.inert.toks(ts), Wrap.check.start(), False{}), + Wrap.check.walk(ts, Wrap.check.start(), False{}), inert.wrap.walk(ts, Wrap.check.start(), False{})) + case False{}: + Equal.cong(U32, List<&2, F.Finding>, ww => Lazy.stop(List<&2, F.Finding>, U32.is_gt(ww, 120), + F.Finding{path, line, 120, 0, "wrap", + "This def header is longer than 120 characters; wrap it with one parameter per line."} <> more, + _u => more), Wrap.check.width.go(D1.inert.toks(ts), 0, 0, False{}), Wrap.check.width.go(ts, 0, 0, False{}), + inert.wrap.width(ts, 0, 0, False{})) -# a signature read that way has its parameters read that way -law inert.scan.params: - for +sig: Tree.Node - {Calls.params(D1.inert.node(sig)) == inert.nodes(Calls.params(sig)) : List<&2, Tree.Node>} +# a def header's tokens are judged alike with their comments and strings cut +law inert.wrap.lexed: + for +ts: List<&2, Lex.Tok> + for +line: U32 + for +path: String + for +more: List<&2, F.Finding> + {Wrap.check.lexed(line, D1.inert.toks(ts), path, more) == Wrap.check.lexed(line, ts, path, more) + : List<&2, F.Finding>} -def inert.scan.params(sig): - %Equal.sym(Tree.Node, Calls.params.of(D1.inert.node(sig)), D1.inert.node(Calls.params.of(sig)), - inert.params.of(sig)) : {Calls.args(_) == inert.nodes(Calls.params(sig)) : List<&2, Tree.Node>} - inert.args(Calls.params.of(sig)) +def inert.wrap.lexed(ts, line, path, more): + %Equal.sym(Bool, Wrap.check.broken(D1.inert.toks(ts)), Wrap.check.broken(ts), inert.wrap.broken(ts)) : + {Wrap.check.header(_, line, D1.inert.toks(ts), path, more) + == Wrap.check.header(Wrap.check.broken(ts), line, ts, path, more) : List<&2, F.Finding>} + inert.wrap.header(Wrap.check.broken(ts), ts, line, path, more) -# the parameter a def read that way shrinks is the one it shrank -law inert.scan.own: - for +sig: Tree.Node - for +body: Tree.Node - for +name: String - for +en: {D1.inert.marked(name) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - {Scan.own(D1.inert.node(sig), D1.inert.node(body), name) == Scan.own(sig, body, name) : Maybe<&2, Nat>} - -def inert.scan.own(sig, body, name, en, eb): - +isg = D1.inert.node(sig) - +ib = D1.inert.node(body) - +lhs = Scan.own(isg, ib, name) - %inert.calls(body, name, en, eb) : {lhs == Lazy.stop(Maybe<&2, Nat>, Bool.not(_), None{}, - _u => Scan.own.go(Calls.params(sig), Hoist.carried(sig, body, name), 0n)) : Maybe<&2, Nat>} - %inert.hoist.carried(sig, body, name, en, eb) : {lhs == Lazy.stop(Maybe<&2, Nat>, Bool.not(Calls.calls(ib, name)), - None{}, _u => Scan.own.go(Calls.params(sig), _, 0n)) : Maybe<&2, Nat>} - %inert.scan.own.go(Calls.params(sig), Hoist.carried(isg, ib, name), 0n) : {lhs == Lazy.stop(Maybe<&2, Nat>, - Bool.not(Calls.calls(ib, name)), None{}, _u => _) : Maybe<&2, Nat>} - %inert.scan.params(sig) : {lhs == Lazy.stop(Maybe<&2, Nat>, Bool.not(Calls.calls(ib, name)), None{}, - _u => Scan.own.go(_, Hoist.carried(isg, ib, name), 0n)) : Maybe<&2, Nat>} - {==} +# one item as wrap reads it: its kind and line, and its header's tokens +def inert.wrap.head( + it: Outline.Item, + ts: List<&2, Lex.Tok>, + +path: String, + +more: List<&2, F.Finding> +) -> List<&2, F.Finding>: + match it: + case Outline.Item{kk, _n, +line, _s, _d, _p}: + Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => Wrap.check.lexed(line, ts, path, more)) -# a def read that way records the walks it recorded -law inert.scan.walks.go: - for ds: List<&2, Calls.Def> - for +ws: List<&2, Scan.Walk> - for +e: {inert.dok(ds) == True{} : Bool} - {Scan.walks.go(inert.defs(ds), ws) == Scan.walks.go(ds, ws) : List<&2, Scan.Walk>} +# wrap over the items read that way: each item with its header's tokens +def inert.wrap.go( + hs: List<&2, Outline.Item>, + ss: List<&2, List<&2, Lex.Tok>>, + +path: String +) -> List<&2, F.Finding>: + match hs ss: + case Nil{} ss: + Nil{} + case Con{h, t} Nil{}: + Nil{} + case Con{h, t} Con{ts, st}: + inert.wrap.head(h, ts, path, inert.wrap.go(t, st, path)) -def inert.scan.walks.go(ds, ws, e): - match ds: - case Nil{}: - {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +isg = D1.inert.node(sig) - +ib = D1.inert.node(body) - +lhs = Scan.walks.go(inert.defs(Calls.Def{name, sig, body} <> rest), ws) - %inert.scan.own(sig, body, name, en, eb) : {lhs == Scan.walks.go(rest, Scan.tag(List.append(&2, Nat, - Scan.opt(_), Scan.passes(body, Calls.names(sig), ws)), name, ws)) : List<&2, Scan.Walk>} - %inert.scan.passes(body, Calls.names(sig), ws) : {lhs == Scan.walks.go(rest, Scan.tag(List.append(&2, Nat, - Scan.opt(Scan.own(isg, ib, name)), _), name, ws)) : List<&2, Scan.Walk>} - %inert.names(sig) : {lhs == Scan.walks.go(rest, Scan.tag(List.append(&2, Nat, - Scan.opt(Scan.own(isg, ib, name)), Scan.passes(ib, _, ws)), name, ws)) : List<&2, Scan.Walk>} - inert.scan.walks.go(rest, Scan.tag(List.append(&2, Nat, Scan.opt(Scan.own(isg, ib, name)), - Scan.passes(ib, Calls.names(isg), ws)), name, ws), inert.dok.rest(name, sig, body, rest, e)) - -# a def read that way is walked as it was -law inert.scan.run: - for +sig: Tree.Node - for +body: Tree.Node - for +name: String - for +ws: List<&2, Scan.Walk> +# wrap's walk over the items is its walk over them read that way +law inert.wrap.items: + for items: List<&2, Outline.Item> for +path: String - for +en: {D1.inert.marked(name) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - {Scan.run(D1.inert.node(sig), D1.inert.node(body), name, ws, path) == Scan.run(sig, body, name, ws, - path) : List<&2, F.Finding>} - -def inert.scan.run(sig, body, name, ws, path, en, eb): - +isg = D1.inert.node(sig) - +ib = D1.inert.node(body) - +cr = Hoist.carried(isg, ib, name) - +lhs = Scan.run(isg, ib, name, ws, path) - %inert.hoist.carried(sig, body, name, en, eb) : {lhs == Scan.walk(body, name, ws, _, Scan.lead.go(Calls.params(sig), - _), path, True{}) : List<&2, F.Finding>} - %inert.scan.lead.go(Calls.params(sig), cr) : {lhs == Scan.walk(body, name, ws, cr, _, path, True{}) - : List<&2, F.Finding>} - %inert.scan.params(sig) : {lhs == Scan.walk(body, name, ws, cr, Scan.lead.go(_, cr), path, True{}) + {Wrap.check.go(items, path) == inert.wrap.go(Laws.inert.items(items), Laws.inert.sigs(items), path) : List<&2, F.Finding>} - inert.scan.walk(body, name, ws, cr, Scan.lead.go(Calls.params(isg), cr), path, True{}, en, eb) - -# scan over the defs read that way reports what it reports over the defs -law inert.scan.go: - for ds: List<&2, Calls.Def> - for +ws: List<&2, Scan.Walk> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Scan.check.go(inert.defs(ds), ws, path, acc) == Scan.check.go(ds, ws, path, acc) : List<&2, F.Finding>} -def inert.scan.go(ds, ws, path, acc, e): - match ds: +def inert.wrap.items(items, path): + match items: case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +isg = D1.inert.node(sig) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - isg)))), [], _u => Scan.run(isg, ib, name, ws, path)) - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, isg)))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - Scan.run(isg, ib, name, ws, path), Scan.run(sig, body, name, ws, path), - inert.gate(path, name, sig, body, en, eb), inert.scan.run(sig, body, name, ws, path, en, eb)) : - {Scan.check.go(inert.defs(rest), ws, path, lst <> acc) == Scan.check.go(rest, ws, path, _ <> acc) - : List<&2, F.Finding>} - inert.scan.go(rest, ws, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -# scan over the defs read that way, their walks read that way too -law inert.scan.on: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Scan.check.on(inert.defs(ds), path) == Scan.check.on(ds, path) : List<&2, F.Finding>} + case Con{Outline.Item{+kk, n, +line, +sig, d, p}, +rest}: + +more = Wrap.check.go(rest, path) + +ts = Lex.tokens(sig) + %inert.wrap.items(rest, path) : {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, + _u => Wrap.check.lexed(line, ts, path, more)) + == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), _, + _u => Wrap.check.lexed(line, D1.inert.toks(ts), path, _)) : List<&2, F.Finding>} + %Equal.sym(List<&2, F.Finding>, Wrap.check.lexed(line, D1.inert.toks(ts), path, more), + Wrap.check.lexed(line, ts, path, more), inert.wrap.lexed(ts, line, path, more)) : + {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, + _u => Wrap.check.lexed(line, ts, path, more)) + == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => _) : List<&2, F.Finding>} + {==} -def inert.scan.on(ds, path, e): - %inert.scan.walks.go(ds, [], e) : {Scan.check.go(inert.defs(ds), Scan.walks(inert.defs(ds)), path, []) - == Scan.check.go(ds, _, path, []) : List<&2, F.Finding>} - inert.scan.go(ds, Scan.walks(inert.defs(ds)), path, [], e) +def Laws.inert_wrap(path, _text, _toks, _tree, _bound, items, _text2, _toks2, _tree2, _bound2, items2, ei, es): + +i1 = Laws.inert.items(items) + +s1 = Laws.inert.sigs(items) + %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items, path), inert.wrap.go(i1, s1, path), + inert.wrap.items(items, path)) : {_ == Wrap.check.go(items2, path) : List<&2, F.Finding>} + %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items2, path), + inert.wrap.go(Laws.inert.items(items2), Laws.inert.sigs(items2), path), inert.wrap.items(items2, path)) : + {inert.wrap.go(i1, s1, path) == _ : List<&2, F.Finding>} + %ei : {inert.wrap.go(i1, s1, path) == inert.wrap.go(_, Laws.inert.sigs(items2), path) : List<&2, F.Finding>} + %es : {inert.wrap.go(i1, s1, path) == inert.wrap.go(i1, _, path) : List<&2, F.Finding>} + {==} -# the rule's law-file gate over the defs read that way -law inert.scan.gated: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => Scan.check.on(inert.defs(ds), path)) - == Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _v => Scan.check.on(ds, path)) - : List<&2, F.Finding>} - -def inert.scan.gated(ds, path, e): - inert.stop(Paths.is_law_file(path), Paths.is_law_file(path), Scan.check.on(inert.defs(ds), path), - Scan.check.on(ds, path), {==}, inert.scan.on(ds, path, e)) - -def Laws.inert_scan(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], - _u => Scan.check.on(Calls.defs(z), path)), tree, tree2, D1.inert.node(tree), D1.inert.node(tree2), - inert.lift(z => Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => Scan.check.on(z, path)), tree, - inert.scan.gated(Calls.defs(tree), path, inert.dok.of(tree, ok))), - inert.lift(z => Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => Scan.check.on(z, path)), tree2, - inert.scan.gated(Calls.defs(tree2), path, inert.dok.of(tree2, ok2))), - e) +# scan reads no string: a string is never a callee or a lone name, so each +# walk and each call reads the same over a tree read that way diff --git a/proofs/ExemptPathsYieldNoFindings.bend b/proofs/ExemptPathsYieldNoFindings.bend index 056df54..ad84162 100644 --- a/proofs/ExemptPathsYieldNoFindings.bend +++ b/proofs/ExemptPathsYieldNoFindings.bend @@ -4,7 +4,6 @@ import ../src/finding.bend as F import ../src/paths.bend as Paths import ../src/lazy/lazy.bend as Lazy import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/scan.bend as Scan import ../laws/ExemptPathsYieldNoFindings.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 @@ -32,9 +31,3 @@ def Laws.untyped_exempt(path, sig, e): # scan # ---- - -def Laws.scan_exempt(path, _text, _toks, tree, _bound, _items, e): - %Equal.sym(Bool, Paths.is_law_file(path), True{}, e) : {Lazy.stop(List<&2, F.Finding>, _, [], - _u => Scan.check.on(Calls.defs(tree), path)) == Nil{} - : List<&2, F.Finding>} - {==} diff --git a/proofs/ForeignReportsMissingBackend.bend b/proofs/ForeignReportsMissingBackend.bend index 33f8419..b59a102 100644 --- a/proofs/ForeignReportsMissingBackend.bend +++ b/proofs/ForeignReportsMissingBackend.bend @@ -4,7 +4,7 @@ import ../src/finding.bend as F import ../src/syntax/bind.bend as Bind import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree -import ../src/rules/correctness/foreign.bend as Foreign +import ../src/rules/pedantic/foreign.bend as Foreign import ../laws/ForeignReportsMissingBackend.bend as Laws # foreign (laws/ForeignReportsMissingBackend.bend): the rule's lanes are the law's imports and diff --git a/proofs/FuelReportsLiteralBudgets.bend b/proofs/FuelReportsLiteralBudgets.bend index 8f4dd6d..5986163 100644 --- a/proofs/FuelReportsLiteralBudgets.bend +++ b/proofs/FuelReportsLiteralBudgets.bend @@ -8,7 +8,7 @@ import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule import ../laws/FuelReportsLiteralBudgets.bend as Laws import ./lemmas/Pick.bend as M1 diff --git a/proofs/HoistReportsTablesInLoops.bend b/proofs/HoistReportsTablesInLoops.bend deleted file mode 100644 index 43281b1..0000000 --- a/proofs/HoistReportsTablesInLoops.bend +++ /dev/null @@ -1,398 +0,0 @@ -# proofs of laws/HoistReportsTablesInLoops.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/hoist.bend as Hoist -import ../laws/HoistReportsTablesInLoops.bend as Laws -import ./lemmas/MatchStatementCounts.bend as M1 -import ./lemmas/Pick.bend as M2 - -# The recursion rule, hoist: each call adds the one the law's call test -# counts, each plain let the one its let test counts, the heat follows the -# same lambdas and case arms, and the def walk joins the per-def lists into -# their sum. - -# a table found or not: one finding or none -law hoist.yes: - for b: Bool - for -aa: Tree.Node - for -path: String - {List.length(&2, F.Finding, Hoist.one_yes(b, aa, path)) == Bool.pick(Nat, b, 1n, 0n) : Nat} - -def hoist.yes(b, _aa, _path): - match b: - case True{}: - {==} - case False{}: - {==} - -# a get or set's collection: one finding when the law's slot test holds -law hoist.slot: - for ss: Hoist.Slot - for +as: List<&2, Tree.Node> - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.arg_table(ss, as, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.slot(ss, as, self, wides, carried), 1n, 0n) : Nat} - -def hoist.slot(ss, as, self, wides, carried, path): - match ss: - case Hoist.Slot{on, at}: - match on: - case False{}: - {==} - case True{}: - hoist.yes(Hoist.is_table(Calls.arg(as, at), self, wides, carried), Calls.arg(as, at), path) - -# a call site: one finding when the law's call test holds -law hoist.call: - for hot: Bool - for open: Bool - for +tt: String - for +kids: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.call_table(hot, open, tt, kids, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.call(hot, open, tt, kids, self, wides, carried), 1n, 0n) : Nat} - -def hoist.call(hot, open, tt, kids, self, wides, carried, path): - match hot: - case False{}: - {==} - case True{}: - match open: - case False{}: - {==} - case True{}: - hoist.slot(Hoist.slot_of(tt), Calls.args(kids), self, wides, carried, path) - -# a let's table and use: one finding when both hold -law hoist.use: - for a: Bool - for b: Bool - for -expr: Tree.Node - for -path: String - {List.length(&2, F.Finding, Hoist.let_use(a, b, expr, path)) == Bool.pick(Nat, Bool.and(a, b), 1n, 0n) : Nat} - -def hoist.use(a, b, _expr, _path): - match a: - case False{}: - {==} - case True{}: - match b: - case True{}: - {==} - case False{}: - {==} - -# a let's left side read: one finding when the law's got test holds -law hoist.got: - for mm: Maybe<&2, Hoist.Got> - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.let_of(mm, body, rest, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.got(mm, body, rest, self, wides, carried), 1n, 0n) : Nat} - -def hoist.got(mm, body, rest, self, wides, carried, path): - match mm: - case None{}: - {==} - case Some{Hoist.Got{+nm, +expr}}: - hoist.use(Hoist.is_table(expr, self, wides, carried), Hoist.uses_or(body, rest, nm), expr, path) - -# a statement: one finding when the law's let test holds -law hoist.let: - for hot: Bool - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n) : Nat} - -def hoist.let(hot, kids, body, rest, self, wides, carried, path): - match hot: - case False{}: - {==} - case True{}: - hoist.got(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) - -# a list joined before another counts as its own count plus the other's -law hoist.front: - for +a: List<&2, F.Finding> - for +b: List<&2, F.Finding> - for -x: Nat - for -y: Nat - for e1: {List.length(&2, F.Finding, a) == x : Nat} - for e2: {List.length(&2, F.Finding, b) == y : Nat} - {List.length(&2, F.Finding, List.append(&2, F.Finding, a, b)) == Nat.add(x, y) : Nat} - -def hoist.front(a, b, x, y, e1, e2): - Equal.trans(Nat, List.length(&2, F.Finding, List.append(&2, F.Finding, a, b)), - Nat.add(List.length(&2, F.Finding, a), List.length(&2, F.Finding, b)), Nat.add(x, y), - M2.esc.app(a, b), M2.esc.add(List.length(&2, F.Finding, a), x, List.length(&2, F.Finding, b), y, e1, e2)) - -# a name token, then a group: its own finding, then its group's and the -# rest's, all at the heat the token leaves -law hoist.site: - for +k: Lex.TokKind - for +t: String - for +o: String - for +kids: Tree.Node - for +rr: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for ih1: {List.length(&2, F.Finding, Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k))) - == Laws.hoist.count(kids, self, wides, carried, Hoist.warm(hot, k)) : Nat} - for ih2: {List.length(&2, F.Finding, Hoist.walk(rr, self, wides, carried, path, Hoist.warm(hot, k))) - == Laws.hoist.count(rr, self, wides, carried, Hoist.warm(hot, k)) : Nat} - {List.length(&2, F.Finding, List.concat(&2, F.Finding, - [Hoist.call_table(Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)), - Hoist.walk(rr, self, wides, carried, path, Hoist.warm(hot, k))])) - == Nat.add(Bool.pick(Nat, Laws.hoist.call(Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, wides, carried), - 1n, 0n), Nat.add(Laws.hoist.count(kids, self, wides, carried, Hoist.warm(hot, k)), - Laws.hoist.count(rr, self, wides, carried, Hoist.warm(hot, k)))) : Nat} - -def hoist.site(k, t, o, kids, rr, self, wides, carried, path, hot, ih1, ih2): - +heat = Hoist.warm(hot, k) - M1.chars.sum(Hoist.call_table(heat, String.eq(o, "("), t, kids, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, heat), Hoist.walk(rr, self, wides, carried, path, heat), - Bool.pick(Nat, Laws.hoist.call(heat, String.eq(o, "("), t, kids, self, wides, carried), 1n, 0n), - Laws.hoist.count(kids, self, wides, carried, heat), Laws.hoist.count(rr, self, wides, carried, heat), - hoist.call(heat, String.eq(o, "("), t, kids, self, wides, carried, path), ih1, ih2) - -# a case: its pattern cold, its arm hot when it calls the def, then the rest -law hoist.arm: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for ih1: {List.length(&2, F.Finding, Hoist.walk(kids, self, wides, carried, path, False{})) - == Laws.hoist.count(kids, self, wides, carried, False{}) : Nat} - for ih2: {List.length(&2, F.Finding, - Hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(body, self)))) - == Laws.hoist.count(body, self, wides, carried, Bool.and(hot, Calls.calls(body, self))) : Nat} - for ih3: {List.length(&2, F.Finding, Hoist.walk(rest, self, wides, carried, path, hot)) - == Laws.hoist.count(rest, self, wides, carried, hot) : Nat} - {List.length(&2, F.Finding, Hoist.walk(Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, body}, rest}, - self, wides, carried, path, hot)) - == Laws.hoist.count(Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, body}, rest}, self, wides, carried, hot) : Nat} - -def hoist.arm(kids, body, rest, self, wides, carried, path, hot, ih1, ih2, ih3): - +arm = Bool.and(hot, Calls.calls(body, self)) - M1.chars.sum(Hoist.walk(kids, self, wides, carried, path, False{}), Hoist.walk(body, self, wides, carried, path, arm), - Hoist.walk(rest, self, wides, carried, path, hot), - Laws.hoist.count(kids, self, wides, carried, False{}), Laws.hoist.count(body, self, wides, carried, arm), - Laws.hoist.count(rest, self, wides, carried, hot), ih1, ih2, ih3) - -# any other statement: its own let finding, then its tokens', its block's -# and the rest's -law hoist.plain: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for ih1: {List.length(&2, F.Finding, Hoist.walk(kids, self, wides, carried, path, hot)) - == Laws.hoist.count(kids, self, wides, carried, hot) : Nat} - for ih2: {List.length(&2, F.Finding, Hoist.walk(body, self, wides, carried, path, hot)) - == Laws.hoist.count(body, self, wides, carried, hot) : Nat} - for ih3: {List.length(&2, F.Finding, Hoist.walk(rest, self, wides, carried, path, hot)) - == Laws.hoist.count(rest, self, wides, carried, hot) : Nat} - {List.length(&2, F.Finding, List.concat(&2, F.Finding, - [Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, hot), Hoist.walk(body, self, wides, carried, path, hot), - Hoist.walk(rest, self, wides, carried, path, hot)])) - == Nat.add(Bool.pick(Nat, Laws.hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n), - Nat.add(Laws.hoist.count(kids, self, wides, carried, hot), - Nat.add(Laws.hoist.count(body, self, wides, carried, hot), Laws.hoist.count(rest, self, wides, carried, hot)))) - : Nat} - -def hoist.plain(kids, body, rest, self, wides, carried, path, hot, ih1, ih2, ih3): - +own = Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path) - +wk = Hoist.walk(kids, self, wides, carried, path, hot) - +wb = Hoist.walk(body, self, wides, carried, path, hot) - +wr = Hoist.walk(rest, self, wides, carried, path, hot) - +ck = Laws.hoist.count(kids, self, wides, carried, hot) - +cb = Laws.hoist.count(body, self, wides, carried, hot) - +cr = Laws.hoist.count(rest, self, wides, carried, hot) - hoist.front(own, List.concat(&2, F.Finding, [wk, wb, wr]), - Bool.pick(Nat, Laws.hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n), - Nat.add(ck, Nat.add(cb, cr)), - hoist.let(hot, kids, body, rest, self, wides, carried, path), - M1.chars.sum(wk, wb, wr, ck, cb, cr, ih1, ih2, ih3)) - -# hoist's walk counts the calls and lets the law counts, at every heat -law hoist.walk: - for nn: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - {List.length(&2, F.Finding, Hoist.walk(nn, self, wides, carried, path, hot)) - == Laws.hoist.count(nn, self, wides, carried, hot) : Nat} - -def hoist.walk(nn, self, wides, carried, path, hot): - match nn: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{+k, +t, l, c}}: - match rest: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{rh, +rr}: - match rh: - case Tree.Group{Lex.Tok{gk, +o, gl, gc}, +kids, cl}: - hoist.site(k, t, o, kids, rr, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)), - hoist.walk(rr, self, wides, carried, path, Hoist.warm(hot, k))) - case Tree.Leaf{tok}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.Stmt{kind, sk, sb}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.NNil{}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.NCons{nh, nt}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.Group{o, +kids, cl}: - M1.index.two(Hoist.walk(kids, self, wides, carried, path, hot), - Hoist.walk(rest, self, wides, carried, path, hot), - Laws.hoist.count(kids, self, wides, carried, hot), Laws.hoist.count(rest, self, wides, carried, hot), - hoist.walk(kids, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SCase{}: - hoist.arm(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, False{}), - hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(body, self))), - hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SDef{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SType{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SLaw{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SImport{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SFor{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SLet{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.STerm{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.NNil{}: - hoist.walk(rest, self, wides, carried, path, hot) - case Tree.NCons{nh, nt}: - hoist.walk(rest, self, wides, carried, path, hot) - -# a def that calls itself and is not exempt gathers its walk, entered hot, -# any other nothing -law hoist.def: - for g: Bool - for +body: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, - Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Hoist.walk(body, self, wides, carried, path, True{}))) - == Bool.pick(Nat, g, Laws.hoist.count(body, self, wides, carried, True{}), 0n) : Nat} - -def hoist.def(g, body, self, wides, carried, path): - match g: - case True{}: - hoist.walk(body, self, wides, carried, path, True{}) - case False{}: - {==} - -# over the defs, the rule's join counts what it gathered and what the law -# counts -law hoist.go: - for ds: List<&2, Calls.Def> - for +wides: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Hoist.check.go(ds, wides, path, acc)) - == Nat.add(M1.index.sum(acc), Laws.hoist.total(ds, wides, path)) : Nat} - -def hoist.go(ds, wides, path, acc): - match ds: - case Nil{}: - M1.index.flat(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +g = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +carried = Hoist.carried(sig, body, name) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Hoist.walk(body, name, wides, carried, path, True{})) - +n = Bool.pick(Nat, g, Laws.hoist.count(body, name, wides, carried, True{}), 0n) - +tot = Laws.hoist.total(rest, wides, path) - Equal.trans(Nat, - List.length(&2, F.Finding, Hoist.check.go(rest, wides, path, x <> acc)), - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - hoist.go(rest, wides, path, x <> acc), - Equal.trans(Nat, - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(List.length(&2, F.Finding, x), tot)), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - M1.index.assoc(M1.index.sum(acc), List.length(&2, F.Finding, x), tot), - Equal.cong(Nat, Nat, z => Nat.add(M1.index.sum(acc), Nat.add(z, tot)), List.length(&2, F.Finding, x), n, - hoist.def(g, body, name, wides, carried, path)))) - -def Laws.hoist_counts(path, _text, _toks, tree, _bound, _items): - +ds = Calls.defs(tree) - hoist.go(ds, Hoist.wide_defs(ds, Nil{}), path, Nil{}) diff --git a/proofs/ImportReachIsClosed.bend b/proofs/ImportReachIsClosed.bend new file mode 100644 index 0000000..8818b3a --- /dev/null +++ b/proofs/ImportReachIsClosed.bend @@ -0,0 +1,4 @@ +# proofs of laws/ImportReachIsClosed.bend +import Base +import ../laws/ImportReachIsClosed.bend as Laws +import ./lemmas/ImportClosure.bend as M1 diff --git a/proofs/IndexReportsGetsInRecursion.bend b/proofs/IndexReportsGetsInRecursion.bend index 1d7a134..40d93fd 100644 --- a/proofs/IndexReportsGetsInRecursion.bend +++ b/proofs/IndexReportsGetsInRecursion.bend @@ -5,15 +5,15 @@ import ../src/finding.bend as F import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/table.bend as Table +import ../src/rules/pedantic/index.bend as Index +import ../src/rules/table.bend as Table import ../laws/IndexReportsGetsInRecursion.bend as Laws import ./lemmas/MatchStatementCounts.bend as M1 import ./lemmas/Nat.bend as M2 # The recursion rule, index: its literal test is the law's, its fire test is -# the law's (a List.get held by a fixed table is exactly one `table` -# reports), each call adds what the law counts, and the def walk joins the +# the law's (a List.get held by a fixed table is exactly one +# src/rules/table.bend calls one), each call adds what the law counts, and the def walk joins the # per-def lists into their sum. # the rule's literal test is the law's diff --git a/proofs/LawNamesEveryItem.bend b/proofs/LawNamesEveryItem.bend index 2ceabc1..f9881f7 100644 --- a/proofs/LawNamesEveryItem.bend +++ b/proofs/LawNamesEveryItem.bend @@ -14,7 +14,7 @@ import ../src/rules/laws/law.bend as Coverage import ../src/rules/imports.bend as Imports import ../src/rules/digest.bend as Digest import ../laws/LawNamesEveryItem.bend as Laws -import ../laws/UnsafeReportsReachableForeignDefs.bend as L1 +import ../laws/ImportReachIsClosed.bend as L1 import ./lemmas/ImportClosure.bend as M1 import ./lemmas/CarriedSearch.bend as CS @@ -368,17 +368,17 @@ def reach.files_eq(fs, cl): match fs: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, laws}, +rest}: match exempt: case True{}: reach.files_eq(rest, cl) case False{}: %reach.files_eq(rest, cl) : - {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, unsafes, + {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, laws} <> rest, cl) == List.append(&2, F.Finding, Laws.reach.gaps(tops, cl, norm, path), _) : List<&2, F.Finding>} %reach.gaps_eq(tops, cl, norm, path) : - {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, unsafes, + {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, laws} <> rest, cl) == List.append(&2, F.Finding, _, Coverage.check.go(rest, cl)) : List<&2, F.Finding>} {==} @@ -395,7 +395,7 @@ def reach.first(fs, ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: match is_laws: case True{}: {==} @@ -424,7 +424,7 @@ def reach.named_eq(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, +says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, +says, deps, laws}, +rest}: match is_laws: case True{}: Equal.cong(List<&2, Digest.Mention>, List<&2, Digest.Mention>, @@ -499,7 +499,7 @@ def reach.items_eq(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, laws}, +rest}: Equal.trans(List<&2, Imports.Edge>, Digest.edges(Laws.reach.nodes(tops, norm, Laws.reach.items(rest))), Coverage.nodes(tops, norm, Digest.edges(Laws.reach.items(rest))), Coverage.nodes(tops, norm, Coverage.graph.items(rest)), @@ -533,7 +533,7 @@ def reach.len_edges(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: Equal.cong(Nat, Nat, n => 1n+n, List.length(&2, Imports.Edge, Digest.edges(rest)), List.length(&2, Digest.Digest, rest), reach.len_edges(rest)) diff --git a/proofs/LspDiagnosticsMatchCli.bend b/proofs/LspDiagnosticsMatchCli.bend index 8bce0d5..9ccdc4a 100644 --- a/proofs/LspDiagnosticsMatchCli.bend +++ b/proofs/LspDiagnosticsMatchCli.bend @@ -9,12 +9,9 @@ import ../src/lint/world.bend as W import ../src/lint/plan.bend as Plan import ../src/src.bend as Src import ../src/role.bend as Role -import ../src/rules/imports.bend as Imports import ../src/noqa.bend as Noqa import ../src/rules/style/noqa.bend as NoqaRule import ../src/syntax/lex.bend as Lex -import ../src/paths.bend as Paths -import ../src/syntax/outline.bend as Outline import ../laws/LspDiagnosticsMatchCli.bend as Laws import ./lemmas/PublishComparisons.bend as M1 @@ -86,18 +83,6 @@ def pub.find_head(d, rest, world): == Plan.conf.near(world, d) : Config.Config} {==} -# `bolt ` runs over that path alone, unless it is a bolt.bend -law pub.cli.named: - for +world: W.World - for +path: String - for h: {W.paths.of(world) == [path] : List<&2, String>} - {Plan.inputs(world) == Plan.sources([path]) : List<&2, String>} - -def pub.cli.named(world, path, h): - e = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) - %e : {Plan.sources(Plan.files.named(_, world)) == Plan.sources([path]) : List<&2, String>} - {==} - # findings put at a path are graded by the CLI as by the config nearest it law pub.grade_near: for +world: W.World @@ -127,54 +112,28 @@ law pub.gs: for +world: W.World for +path: String for +text: String - {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) + for +ms: List<&2, Role.Mark> + {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}]))) + Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}]))) : List<&2, F.Graded>} -# a run of one file gives it its role by name: a LAWS.bend or PROOF.bend -# keeps it, and any other file is reached by no law, so it is neither -law pub.role_one: - for +named: Role.Role - for +nn: String - for deps: List<&2, String> - for +entry: Bool - {Role.role.of(named, Role.of.norm(Role.marks.of([Role.Fact{nn, deps, named, entry}], - Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}])), - Role.reach([Role.Fact{nn, deps, named, entry}], Role.lawful([Role.Fact{nn, deps, named, entry}])), - Role.reach([Role.Fact{nn, deps, named, entry}], Role.code([Role.Fact{nn, deps, named, entry}], - Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}]))))), nn)) - == named : Role.Role} - -def pub.role_one(named, _nn, _deps, _entry): - match named: - case Role.Plain{}: - {==} - case Role.Laws{}: - {==} - case Role.Proof{}: - {==} - -def pub.gs(world, path, text): - +s = Src.of(path, text) - +r = Equal.cong(Role.Role, List<&2, F.Finding>, z => List.append(&2, F.Finding, Rules.on.role(z, s), []), - Role.of(Role.marks([s]), path), Role.of_path(path), - pub.role_one(Role.of_path(path), Imports.norm(path), - Imports.targets(Outline.of_tokens(text, Lex.tokens(text)), Imports.norm(path)), - String.eq(Paths.base(path), "main.bend"))) - a = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), Rules.all(path, text), - Equal.trans(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), - List.append(&2, F.Finding, Rules.all(path, text), []), Rules.all(path, text), r, - pub.app_nil(F.Finding, Rules.all(path, text)))) +def pub.gs(world, path, text, ms): + a = Equal.sym(List<&2, F.Finding>, List.append(&2, F.Finding, Rules.on.role(Role.of(ms, path), Src.of(path, + text)), []), + Rules.on.role(Role.of(ms, path), Src.of(path, text)), + pub.app_nil(F.Finding, Rules.on.role(Role.of(ms, path), Src.of(path, text)))) %a : {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)) + Rules.on.role(Role.of(ms, path), Src.of(path, text))) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, _) : List<&2, F.Graded>} Equal.sym(List<&2, F.Graded>, - Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Rules.all(path, text)), + Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Rules.on.role(Role.of(ms, path), + Src.of(path, text))), Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), - pub.grade_near(world, path, Rules.on.rules(Src.of(path, text)))) + Rules.on.role(Role.of(ms, path), Src.of(path, text))), + pub.grade_near(world, path, Rules.on.rules.role(Role.of(ms, path), Src.of(path, text)))) # the `noqa` findings of the LSP's lint, graded by the config nearest the # path, are graded alike by the CLI @@ -211,23 +170,24 @@ law pub.cli.case: for +world: W.World for +path: String for +text: String + for +ms: List<&2, Role.Mark> for hc: {Plan.is_config(path) == bb : Bool} for ht: {W.text(world, path) == Some{text} : Maybe<&2, String>} - {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) + {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world), ms) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, - Plan.per_file(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), + Plan.per_file.role(ms, Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, fs)) : List<&2, F.Graded>} -def pub.cli.case(bb, world, path, text, hc, ht): +def pub.cli.case(bb, world, path, text, ms, hc, ht): match bb: case True{}: e = Equal.sym(Bool, Plan.is_config(path), True{}, hc) %e : {Bool.pick(List<&2, F.Graded>, _, [], Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == [] : List<&2, F.Graded>} {==} @@ -236,27 +196,27 @@ def pub.cli.case(bb, world, path, text, hc, ht): %e : {Bool.pick(List<&2, F.Graded>, _, [], Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots([path], world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of(Plan.gots([path], world)))), + Plan.per_file.role(ms, Plan.srcs_of(Plan.gots([path], world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} t = Equal.sym(Maybe<&2, String>, W.text(world, path), Some{text}, ht) %t : {Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after(Noqa.of_srcs(Plan.srcs_of([Plan.Got{path, _}])), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of([Plan.Got{path, _}]))), + Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, _}]))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} - g = pub.gs(world, path, text) + g = pub.gs(world, path, text, ms) %g : {Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, _, fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) @@ -265,24 +225,28 @@ def pub.cli.case(bb, world, path, text, hc, ht): rr => List.append(&2, F.Graded, Noqa.keep([Noqa.file(path, Lex.tokens(text))], Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text))), rr), + Rules.on.role(Role.of(ms, path), Src.of(path, text)))), rr), Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Plan.noqa([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)))), + Rules.on.role(Role.of(ms, path), Src.of(path, text))))), Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Plan.noqa([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)))), + Rules.on.role(Role.of(ms, path), Src.of(path, text))))), pub.noqa_near(world, path, text, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)))) + Rules.on.role(Role.of(ms, path), Src.of(path, text))))) def Laws.lint_is_cli(world, path, text, h, ht): - n = Equal.sym(List<&2, String>, Plan.inputs(world), Plan.sources([path]), pub.cli.named(world, path, h)) - %n : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) - == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(_, world))), False{}, - Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, Plan.per_file(Plan.srcs_of(Plan.gots(_, world)))), - fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, fs)) + q = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) + %q : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world), + Server.pub.marks(world)) + == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world))), False{}, + Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, + Plan.per_file.role(Plan.marks.run(_, world, Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), + world))), Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world)))), + fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, fs)) : List<&2, F.Graded>} - pub.cli.case(Plan.is_config(path), world, path, text, {==}, ht) + pub.cli.case(Plan.is_config(path), world, path, text, + Server.pub.marks(world), {==}, ht) diff --git a/proofs/LspKeepsRoleMarks.bend b/proofs/LspKeepsRoleMarks.bend new file mode 100644 index 0000000..975abd0 --- /dev/null +++ b/proofs/LspKeepsRoleMarks.bend @@ -0,0 +1,33 @@ +# proofs of laws/LspKeepsRoleMarks.bend +import Base +import ../src/lazy/lazy.bend as Lazy +import ../src/lsp/docs.bend as Docs +import ../src/lsp/report.bend as Rep +import ../src/role.bend as Role +import ../laws/LspKeepsRoleMarks.bend as Laws +import ./lemmas/PublishComparisons.bend as M1 + +# the document just put at the head of the list is the one a lookup of its +# URI finds: its marks +law keep.marks_head: + for +uri: String + for -text: String + for -ds: List<&2, Rep.Diag> + for -ms: List<&2, Role.Mark> + for -rest: List<&2, Docs.Doc> + {Docs.marks(Docs.Doc{uri, text, ds, ms} <> rest, uri) == ms : List<&2, Role.Mark>} + +def keep.marks_head(uri, _text, _ds, ms, rest): + e = Equal.sym(Bool, String.eq(uri, uri), True{}, M1.pub.seq_refl(uri)) + %e : {Lazy.stop(List<&2, Role.Mark>, _, ms, _u => Docs.marks(rest, uri)) == ms : List<&2, Role.Mark>} + {==} + +def Laws.open_keeps_marks(_ee, uri, text, ds, _rs, ms, docs): + keep.marks_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, ms, + Docs.del(Docs.set(docs, uri, text), uri)) + +def Laws.save_keeps_marks(_ee, uri, ds, _rs, ms, docs): + keep.marks_head(uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, ms, Docs.del(docs, uri)) + +def Laws.change_keeps_marks(_ee, uri, text, _rs, docs): + keep.marks_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri)) diff --git a/proofs/LspPublishesOnDocumentEvents.bend b/proofs/LspPublishesOnDocumentEvents.bend index 86aa97d..62014ba 100644 --- a/proofs/LspPublishesOnDocumentEvents.bend +++ b/proofs/LspPublishesOnDocumentEvents.bend @@ -2,6 +2,8 @@ import Base import ../src/lazy/lazy.bend as Lazy import ../src/lsp/docs.bend as Docs +import ../src/role.bend as Role +import ../src/src.bend as Src import ../src/lsp/report.bend as Rep import ../src/finding.bend as F import ../src/config.bend as Config @@ -18,10 +20,11 @@ law pub.errors_head: for +uri: String for -text: String for -ds: List<&2, Rep.Diag> + for -ms: List<&2, Role.Mark> for -rest: List<&2, Docs.Doc> - {Docs.errors(Docs.Doc{uri, text, ds} <> rest, uri) == ds : List<&2, Rep.Diag>} + {Docs.errors(Docs.Doc{uri, text, ds, ms} <> rest, uri) == ds : List<&2, Rep.Diag>} -def pub.errors_head(uri, _text, ds, rest): +def pub.errors_head(uri, _text, ds, _ms, rest): e = Equal.sym(Bool, String.eq(uri, uri), True{}, M1.pub.seq_refl(uri)) %e : {Lazy.stop(List<&2, Rep.Diag>, _, ds, _u => Docs.errors(rest, uri)) == ds : List<&2, Rep.Diag>} {==} @@ -31,10 +34,11 @@ law pub.get_head: for +uri: String for -text: String for -ds: List<&2, Rep.Diag> + for -ms: List<&2, Role.Mark> for -rest: List<&2, Docs.Doc> - {Docs.get(Docs.Doc{uri, text, ds} <> rest, uri) == Some{text} : Maybe<&2, String>} + {Docs.get(Docs.Doc{uri, text, ds, ms} <> rest, uri) == Some{text} : Maybe<&2, String>} -def pub.get_head(uri, text, _ds, rest): +def pub.get_head(uri, text, _ds, _ms, rest): e = Equal.sym(Bool, String.eq(uri, uri), True{}, M1.pub.seq_refl(uri)) %e : {Lazy.stop(Maybe<&2, String>, _, Some{text}, _u => Docs.get(rest, uri)) == Some{text} : Maybe<&2, String>} {==} @@ -46,12 +50,14 @@ law pub.del.step: for +uri: String for -t: String for -e: List<&2, Rep.Diag> + for -m: List<&2, Role.Mark> for +rest: List<&2, Docs.Doc> for h: {String.eq(u, uri) == bb : Bool} for ih: {Docs.errors(rest, uri) == [] : List<&2, Rep.Diag>} - {Docs.errors(Bool.pick(List<&2, Docs.Doc>, bb, rest, Docs.Doc{u, t, e} <> rest), uri) == [] : List<&2, Rep.Diag>} + {Docs.errors(Bool.pick(List<&2, Docs.Doc>, bb, rest, Docs.Doc{u, t, e, m} <> rest), uri) == [] + : List<&2, Rep.Diag>} -def pub.del.step(bb, u, uri, _t, e, rest, h, ih): +def pub.del.step(bb, u, uri, _t, e, _m, rest, h, ih): match bb: case True{}: ih @@ -70,45 +76,45 @@ def pub.del(docs, uri): match docs: case Nil{}: {==} - case Con{Docs.Doc{+u, t, e}, +r}: - pub.del.step(String.eq(u, uri), u, uri, t, e, Docs.del(r, uri), {==}, pub.del(r, uri)) + case Con{Docs.Doc{+u, t, e, m}, +r}: + pub.del.step(String.eq(u, uri), u, uri, t, e, m, Docs.del(r, uri), {==}, pub.del(r, uri)) -def Laws.open_publishes(_ee, _uri, _text, _ds, _rs, _docs): +def Laws.open_publishes(_ee, _uri, _text, _ds, _rs, _ms, _docs): {==} -def Laws.save_publishes(_ee, _uri, _ds, _rs, _docs): +def Laws.save_publishes(_ee, _uri, _ds, _rs, _ms, _docs): {==} def Laws.change_publishes(_ee, _uri, _text, _rs, _docs): {==} -def Laws.open_keeps(_ee, uri, text, ds, _rs, docs): - (pub.errors_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, +def Laws.open_keeps(_ee, uri, text, ds, _rs, ms, docs): + (pub.errors_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, ms, Docs.del(Docs.set(docs, uri, text), uri)), Equal.trans(Maybe<&2, String>, - Docs.get(Docs.set_errors(Docs.set(docs, uri, text), uri, ds), uri), + Docs.get(Docs.set_checked(Docs.set(docs, uri, text), uri, ds, ms), uri), Some{Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), "")}, Some{text}, - pub.get_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, + pub.get_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, ms, Docs.del(Docs.set(docs, uri, text), uri)), Equal.cong(Maybe<&2, String>, Maybe<&2, String>, m => Some{Maybe.default(&2, String, m, "")}, Docs.get(Docs.set(docs, uri, text), uri), Some{text}, - pub.get_head(uri, text, Docs.errors(docs, uri), Docs.del(docs, uri))))) + pub.get_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri))))) -def Laws.save_keeps(_ee, uri, ds, _rs, docs): - pub.errors_head(uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, Docs.del(docs, uri)) +def Laws.save_keeps(_ee, uri, ds, _rs, ms, docs): + pub.errors_head(uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, ms, Docs.del(docs, uri)) def Laws.change_keeps(_ee, uri, text, _rs, docs): - (pub.errors_head(uri, text, Docs.errors(docs, uri), Docs.del(docs, uri)), - pub.get_head(uri, text, Docs.errors(docs, uri), Docs.del(docs, uri))) + (pub.errors_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri)), + pub.get_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri))) def Laws.close_publishes(_ee, uri, docs): ({==}, pub.del(docs, uri)) -def Laws.config_unlinted(path, h, _text, _rs): +def Laws.config_unlinted(path, h, _text, _rs, _ms): e = Equal.sym(Bool, Plan.is_config(path), True{}, h) %e : {Bool.pick(List<&2, F.Graded>, _, [], Plan.after([Noqa.file(path, Lex.tokens(_text))], False{}, - Rules.graded(Config.nearest(_rs), Rules.all(path, _text)), + Rules.graded(Config.nearest(_rs), Rules.on.role(Role.of(_ms, path), Src.of(path, _text))), fs => Rules.graded(Config.nearest(_rs), fs))) == [] : List<&2, F.Graded>} {==} diff --git a/proofs/NoqaReportsStaleComments.bend b/proofs/NoqaReportsStaleComments.bend index a29b084..ad7cac9 100644 --- a/proofs/NoqaReportsStaleComments.bend +++ b/proofs/NoqaReportsStaleComments.bend @@ -79,15 +79,13 @@ def nqp.used(gs, path, line, code): {==})), nqp.used(rest, path, line, code)) -# the rule's project codes are the spec's: `coverage` and `unsafe` +# the rule's project code is the spec's: `coverage` law nqp.project: for +code: String {NoqaRule.project(code) == Laws.nq.project(code) : Bool} -def nqp.project(code): - M1.nqp.or_else(String.eq(code, Codes.code("coverage")), String.eq(code, Codes.code("unsafe")), String.eq(code, - "L003"), - {==}) +def nqp.project(_code): + {==} # the rule's test of a code is the spec's: judged, and silencing nothing law nqp.bad: diff --git a/proofs/RewalkReportsRepeatedWalks.bend b/proofs/RewalkReportsRepeatedWalks.bend index 00bac6c..3fa3c05 100644 --- a/proofs/RewalkReportsRepeatedWalks.bend +++ b/proofs/RewalkReportsRepeatedWalks.bend @@ -6,7 +6,7 @@ import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk import ../laws/RewalkReportsRepeatedWalks.bend as Laws import ./lemmas/Pick.bend as M1 diff --git a/proofs/RingReportsDropAppendRotation.bend b/proofs/RingReportsDropAppendRotation.bend deleted file mode 100644 index 586a680..0000000 --- a/proofs/RingReportsDropAppendRotation.bend +++ /dev/null @@ -1,157 +0,0 @@ -# proofs of laws/RingReportsDropAppendRotation.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/ring.bend as Ring -import ../laws/RingReportsDropAppendRotation.bend as Laws -import ./lemmas/Pick.bend as M1 - -# a scan's finding counts one when it found a drop, else none -law ring.one: - for mm: Maybe<&2, Lex.Tok> - for +path: String - {List.length(&2, F.Finding, Ring.slide_at(mm, path)) == Laws.ring.hit(mm) : Nat} - -def ring.one(mm, _path): - match mm: - case None{}: - {==} - case Some{Lex.Tok{k, t, l, c}}: - {==} - -# a self-call's arguments, each read against its own slot -law ring.slides: - for as: List<&2, Tree.Node> - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - {List.length(&2, F.Finding, Ring.slides(as, params, path, wins)) == Laws.ring.args(as, params, wins) : Nat} - -def ring.slides(as, params, path, wins): - match as: - case Nil{}: - {==} - case Con{+h, +rest}: - +scan = Ring.plus_scan(h, None{}, Ring.slot_wins(Maybe.default(&2, String, List.head(&2, String, params), ""), - wins)) - +x = Ring.slide_at(scan, path) - +more = Ring.slides(rest, List.tail(&2, String, params), path, wins) - Equal.trans(Nat, List.length(&2, F.Finding, List.append(&2, F.Finding, x, more)), - Nat.add(List.length(&2, F.Finding, x), List.length(&2, F.Finding, more)), - Nat.add(Laws.ring.hit(scan), Laws.ring.args(rest, List.tail(&2, String, params), wins)), - M1.esc.app(x, more), - M1.esc.add(List.length(&2, F.Finding, x), Laws.ring.hit(scan), List.length(&2, F.Finding, more), - Laws.ring.args(rest, List.tail(&2, String, params), wins), ring.one(scan, path), - ring.slides(rest, List.tail(&2, String, params), path, wins))) - -def Laws.ring_walk_counts(nn, self, params, path, wins): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{k, +t, l, c}}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{Lex.Tok{k2, +o, l2, c2}, +kids, cl}: - +me = Bool.and(String.eq(o, "("), String.eq(t, self)) - M1.table.three( - Lazy.stop(List<&2, F.Finding>, Bool.not(me), [], _u => Ring.slides(Calls.args(kids), params, path, - wins)), - Ring.walk(kids, self, params, path, wins), Ring.walk(r2, self, params, path, wins), - Bool.pick(Nat, me, Laws.ring.args(Calls.args(kids), params, wins), 0n), - Laws.ring.count(kids, self, params, wins), Laws.ring.count(r2, self, params, wins), - M1.tail.stop(me, Ring.slides(Calls.args(kids), params, path, wins), - Laws.ring.args(Calls.args(kids), params, wins), - ring.slides(Calls.args(kids), params, path, wins)), - Laws.ring_walk_counts(kids, self, params, path, wins), - Laws.ring_walk_counts(r2, self, params, path, wins)) - case Tree.Leaf{x}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Stmt{a, b, d}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NNil{}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NCons{a, b}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Leaf{x}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Group{a, b, d}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Stmt{a, b, d}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NNil{}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Group{open, +kids, close}: - M1.table.two(Ring.walk(kids, self, params, path, wins), Ring.walk(rest, self, params, path, wins), - Laws.ring.count(kids, self, params, wins), Laws.ring.count(rest, self, params, wins), - Laws.ring_walk_counts(kids, self, params, path, wins), - Laws.ring_walk_counts(rest, self, params, path, wins)) - case Tree.Stmt{kind, +kids, +body}: - M1.table.three(Ring.walk(kids, self, params, path, wins), Ring.walk(body, self, params, path, wins), - Ring.walk(rest, self, params, path, wins), Laws.ring.count(kids, self, params, wins), - Laws.ring.count(body, self, params, wins), Laws.ring.count(rest, self, params, wins), - Laws.ring_walk_counts(kids, self, params, path, wins), - Laws.ring_walk_counts(body, self, params, path, wins), - Laws.ring_walk_counts(rest, self, params, path, wins)) - case Tree.NNil{}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NCons{a, b}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Leaf{x}: - {==} - case Tree.Group{a, b, d}: - {==} - case Tree.Stmt{a, b, d}: - {==} - case Tree.NNil{}: - {==} - -# one def's findings: walked when its guard keeps it, else none -law ring.stop: - for +b: Bool - for +body: Tree.Node - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - {List.length(&2, F.Finding, - Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], _u => Ring.walk(body, self, params, path, wins))) - == Bool.pick(Nat, b, Laws.ring.count(body, self, params, wins), 0n) : Nat} - -def ring.stop(b, body, self, params, path, wins): - match b: - case True{}: - Laws.ring_walk_counts(body, self, params, path, wins) - case False{}: - {==} - -# ring's def walk: the findings gathered so far, then each def's -law ring.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Ring.check.go(ds, path, acc)) == M1.pick.sum(acc, Laws.ring.defs(ds, path)) : Nat} - -def ring.go(ds, path, acc): - match ds: - case Nil{}: - M1.pick.rev(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +keep = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(keep), [], - _u => Ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body))) - +cb = Bool.pick(Nat, keep, Laws.ring.count(body, name, Calls.names(sig), Ring.windows(sig, body)), 0n) - Equal.trans(Nat, List.length(&2, F.Finding, Ring.check.go(rest, path, x <> acc)), - M1.pick.sum(acc, Nat.add(List.length(&2, F.Finding, x), Laws.ring.defs(rest, path))), - M1.pick.sum(acc, Nat.add(cb, Laws.ring.defs(rest, path))), - ring.go(rest, path, x <> acc), - Equal.cong(Nat, Nat, n => M1.pick.sum(acc, Nat.add(n, Laws.ring.defs(rest, path))), - List.length(&2, F.Finding, x), cb, - ring.stop(keep, body, name, Calls.names(sig), path, Ring.windows(sig, body)))) - -def Laws.ring_counts(path, _text, _toks, tree, _bound, _items): - ring.go(Calls.defs(tree), path, Nil{}) diff --git a/proofs/RoleHoist.bend b/proofs/RoleHoist.bend deleted file mode 100644 index bd53b28..0000000 --- a/proofs/RoleHoist.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleHoist.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/hoist.bend as Hoist -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleHoist.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_hoist(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => Hoist.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.hoist_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/RoleIndex.bend b/proofs/RoleIndex.bend index 2b52ae8..cafa016 100644 --- a/proofs/RoleIndex.bend +++ b/proofs/RoleIndex.bend @@ -1,7 +1,7 @@ # proofs of laws/RoleIndex.bend import Base import ../src/src.bend as Src -import ../src/rules/suspicious/index.bend as Index +import ../src/rules/pedantic/index.bend as Index import ../laws/ExemptPathsYieldNoFindings.bend as L1 import ../laws/RoleIndex.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 diff --git a/proofs/RoleRewalk.bend b/proofs/RoleRewalk.bend index 1200c09..732a2e2 100644 --- a/proofs/RoleRewalk.bend +++ b/proofs/RoleRewalk.bend @@ -1,7 +1,7 @@ # proofs of laws/RoleRewalk.bend import Base import ../src/src.bend as Src -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk import ../laws/ExemptPathsYieldNoFindings.bend as L1 import ../laws/RoleRewalk.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 diff --git a/proofs/RoleRing.bend b/proofs/RoleRing.bend deleted file mode 100644 index 7034c1e..0000000 --- a/proofs/RoleRing.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleRing.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/ring.bend as Ring -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleRing.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_ring(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => Ring.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.ring_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/RoleScan.bend b/proofs/RoleScan.bend deleted file mode 100644 index 1aec112..0000000 --- a/proofs/RoleScan.bend +++ /dev/null @@ -1,17 +0,0 @@ -# proofs of laws/RoleScan.bend -import Base -import ../src/finding.bend as F -import ../src/paths.bend as Paths -import ../src/role.bend as Role -import ../src/lazy/lazy.bend as Lazy -import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/scan.bend as Scan -import ../laws/RoleLawFileNamed.bend as L1 -import ../laws/RoleScan.bend as Laws -import ./lemmas/RoleLawFilePicked.bend as M1 - -def Laws.role_scan(path, _text, _toks, tree, _bound, _items): - Equal.cong(Bool, List<&2, F.Finding>, - z => Lazy.stop(List<&2, F.Finding>, z, [], _u => Scan.check.on(Calls.defs(tree), path)), - Role.is_law_file(Role.of_path(path)), Paths.is_law_file(path), - L1.role_law_file_named(path)) diff --git a/proofs/RoleScanExempt.bend b/proofs/RoleScanExempt.bend deleted file mode 100644 index 6a22545..0000000 --- a/proofs/RoleScanExempt.bend +++ /dev/null @@ -1,10 +0,0 @@ -# proofs of laws/RoleScanExempt.bend -import Base -import ../src/role.bend as Role -import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/scan.bend as Scan -import ../laws/RoleScanExempt.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_scan_exempt(rr, e, path, _text, _toks, tree, _bound, _items): - M1.ex.stop(Role.is_law_file(rr), e, _u => Scan.check.on(Calls.defs(tree), path)) diff --git a/proofs/RoleStrict.bend b/proofs/RoleStrict.bend index c23a533..e14632d 100644 --- a/proofs/RoleStrict.bend +++ b/proofs/RoleStrict.bend @@ -1,7 +1,7 @@ # proofs of laws/RoleStrict.bend import Base import ../src/src.bend as Src -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict import ../laws/ExemptPathsYieldNoFindings.bend as L1 import ../laws/RoleStrict.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 diff --git a/proofs/RoleTable.bend b/proofs/RoleTable.bend deleted file mode 100644 index 6b307aa..0000000 --- a/proofs/RoleTable.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleTable.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/table.bend as Table -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleTable.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_table(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => Table.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.table_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/RoleUnit.bend b/proofs/RoleUnit.bend deleted file mode 100644 index 5e70ce6..0000000 --- a/proofs/RoleUnit.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleUnit.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/unit.bend as UnitRule -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleUnit.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_unit(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => UnitRule.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.unit_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/ScanReportsCarriedArgumentWalks.bend b/proofs/ScanReportsCarriedArgumentWalks.bend deleted file mode 100644 index 4aa1d5c..0000000 --- a/proofs/ScanReportsCarriedArgumentWalks.bend +++ /dev/null @@ -1,773 +0,0 @@ -# proofs of laws/ScanReportsCarriedArgumentWalks.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/paths.bend as Paths -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/scan.bend as Scan -import ../laws/ScanReportsCarriedArgumentWalks.bend as Laws -import ./lemmas/MatchStatementCounts.bend as M1 - -# the file's walks of a name are the law's -law scan.file_at: - for ws: List<&2, Scan.Walk> - for +tt: String - {Scan.file_at(ws, tt) == Laws.scan.file(ws, tt) : List<&2, Nat>} - -def scan.file_at(ws, tt): - match ws: - case Nil{}: - {==} - case Con{Scan.Walk{+nm, +at}, +rest}: - %scan.file_at(rest, tt) : {Scan.file_at(Scan.Walk{nm, at} <> rest, tt) - == Bool.pick(List<&2, Nat>, String.eq(nm, tt), at <> _, _) : List<&2, Nat>} - {==} - -def Laws.scan_slots(tt, ws): - %scan.file_at(ws, tt) : {Scan.slots(tt, ws) == List.append(&2, Nat, Laws.scan.base(tt), _) : List<&2, Nat>} - {==} - -# three finding lists, each empty, join empty -law scan.nil3: - for -a: List<&2, F.Finding> - for -b: List<&2, F.Finding> - for -c: List<&2, F.Finding> - for ea: {a == Nil{} : List<&2, F.Finding>} - for eb: {b == Nil{} : List<&2, F.Finding>} - for ec: {c == Nil{} : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [a, b, c]) == Nil{} : List<&2, F.Finding>} - -def scan.nil3(a, b, c, ea, eb, ec): - %Equal.sym(List<&2, F.Finding>, a, Nil{}, ea) : {List.concat(&2, F.Finding, [_, b, c]) == Nil{} - : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, b, Nil{}, eb) : {List.concat(&2, F.Finding, [Nil{}, _, c]) == Nil{} - : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, c, Nil{}, ec) : {List.concat(&2, F.Finding, [Nil{}, Nil{}, _]) == Nil{} - : List<&2, F.Finding>} - {==} - -# two finding lists, each empty, join empty -law scan.nil2: - for -a: List<&2, F.Finding> - for -b: List<&2, F.Finding> - for ea: {a == Nil{} : List<&2, F.Finding>} - for eb: {b == Nil{} : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [a, b]) == Nil{} : List<&2, F.Finding>} - -def scan.nil2(a, b, ea, eb): - %Equal.sym(List<&2, F.Finding>, a, Nil{}, ea) : {List.concat(&2, F.Finding, [_, b]) == Nil{} : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, b, Nil{}, eb) : {List.concat(&2, F.Finding, [Nil{}, _]) == Nil{} - : List<&2, F.Finding>} - {==} - -# no name is kept against no names -law scan.keep_nil: - for mm: Maybe<&2, String> - {Scan.keep_in(mm, []) == None{} : Maybe<&2, String>} - -def scan.keep_nil(mm): - match mm: - case None{}: - {==} - case Some{nm}: - {==} - -# no walked argument is one of no names -law scan.found_nil: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - {Scan.found(ss, as, []) == None{} : Maybe<&2, String>} - -def scan.found_nil(ss, as): - match ss: - case Nil{}: - {==} - case Con{+at, +rest}: - %Equal.sym(Maybe<&2, String>, Scan.keep_in(Scan.name_of(Calls.arg(as, at)), []), None{}, - scan.keep_nil(Scan.name_of(Calls.arg(as, at)))) : {Scan.first(_, Scan.found(rest, as, [])) == None{} - : Maybe<&2, String>} - scan.found_nil(rest, as) - -# a stop that gives nothing either way gives nothing -law scan.stop_nil: - for c: Bool - {Lazy.stop(List<&2, F.Finding>, c, [], _u => Nil{}) == Nil{} : List<&2, F.Finding>} - -def scan.stop_nil(c): - match c: - case True{}: - {==} - case False{}: - {==} - -# a named call reports nothing when nothing is carried -law scan.on_nil: - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +lead: String - for +path: String - {Scan.on_name(hot, open, tt, line, col, kids, self, ws, [], lead, path) == Nil{} : List<&2, F.Finding>} - -def scan.on_nil(hot, open, tt, line, col, kids, self, ws, lead, path): - %Equal.sym(Maybe<&2, String>, Scan.found(Scan.slots(tt, ws), Calls.args(kids), []), None{}, - scan.found_nil(Scan.slots(tt, ws), Calls.args(kids))) : {Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self))))), [], - _u => Scan.cite(_, tt, line, col, self, lead, path)) == Nil{} : List<&2, F.Finding>} - scan.stop_nil(Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self)))))) - -# a call reports nothing when nothing is carried, whatever its callee's kind -law scan.hit_nil: - for kk: Lex.TokKind - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +lead: String - for +path: String - {Scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, [], lead, path) == Nil{} : List<&2, F.Finding>} - -def scan.hit_nil(kk, hot, open, tt, line, col, kids, self, ws, lead, path): - match kk: - case Lex.TName{}: - scan.on_nil(hot, open, tt, line, col, kids, self, ws, lead, path) - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - scan.on_nil(hot, open, tt, line, col, kids, self, ws, lead, path) - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -def Laws.scan_quiet(nn, self, ws, lead, path, hot): - match nn: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}: - match rest: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{rh, +rr}: - match rh: - case Tree.Group{Lex.Tok{gk, +o, gl, gc}, +kids, cl}: - scan.nil3(Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, [], lead, - path), - Scan.walk(kids, self, ws, [], lead, path, Hoist.warm(hot, k)), - Scan.walk(rr, self, ws, [], lead, path, Hoist.warm(hot, k)), - scan.hit_nil(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, lead, path), - Laws.scan_quiet(kids, self, ws, lead, path, Hoist.warm(hot, k)), Laws.scan_quiet(rr, self, ws, - lead, path, Hoist.warm(hot, k))) - case Tree.Leaf{tok}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.Stmt{kind, sk, sb}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.NNil{}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.NCons{nh, nt}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.Group{o, +kids, cl}: - scan.nil2(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SDef{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SType{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SLaw{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SImport{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SCase{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, False{}), Scan.walk(body, self, ws, [], lead, path, - Bool.and(hot, Calls.calls(body, self))), Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, False{}), Laws.scan_quiet(body, self, ws, lead, path, - Bool.and(hot, Calls.calls(body, self))), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SFor{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SLet{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.STerm{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.NNil{}: - Laws.scan_quiet(rest, self, ws, lead, path, hot) - case Tree.NCons{nh, nt}: - Laws.scan_quiet(rest, self, ws, lead, path, hot) - -# whether an answer has a name -def scan.some(mm: Maybe<&2, String>) -> Bool: - match mm: - case None{}: - False{} - case Some{nm}: - True{} - -# a finding exactly when there is a name -law scan.cite_len: - for mm: Maybe<&2, String> - for +tt: String - for +line: U32 - for +col: U32 - for +self: String - for +lead: String - for +path: String - {List.length(&2, F.Finding, Scan.cite(mm, tt, line, col, self, lead, path)) == Bool.pick(Nat, scan.some(mm), 1n, 0n) - : Nat} - -def scan.cite_len(mm, _tt, _line, _col, _self, _lead, _path): - match mm: - case None{}: - {==} - case Some{nm}: - {==} - -# the first of two answers has a name when either has -law scan.first_some: - for aa: Maybe<&2, String> - for bb: Maybe<&2, String> - {scan.some(Scan.first(aa, bb)) == Bool.or(scan.some(aa), scan.some(bb)) : Bool} - -def scan.first_some(aa, _bb): - match aa: - case None{}: - {==} - case Some{nm}: - {==} - -# a kept name is there exactly when the list holds it -law scan.pick_some: - for b: Bool - for +nm: String - {scan.some(Bool.pick(Maybe<&2, String>, b, Some{nm}, None{})) == b : Bool} - -def scan.pick_some(b, _nm): - match b: - case True{}: - {==} - case False{}: - {==} - -# a token kept against the names is the law's named token -law scan.named: - for tok: Lex.Tok - for +carried: List<&2, String> - {scan.some(Scan.keep_in(Scan.name_tok(tok), carried)) == Laws.scan.named(tok, carried) : Bool} - -def scan.named(tok, carried): - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - scan.pick_some(List.contains(~String, ~String.eq, carried, t), t) - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -# an argument kept against the names is the law's lone name -law scan.lone: - for nn: Tree.Node - for +carried: List<&2, String> - {scan.some(Scan.keep_in(Scan.name_of(nn), carried)) == Laws.scan.lone(nn, carried) : Bool} - -def scan.lone(nn, carried): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: - scan.named(tok, carried) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# a found step's two sides are the law's -law scan.found.or: - for +arg: Tree.Node - for rest: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +carried: List<&2, String> - for ih: {scan.some(Scan.found(rest, as, carried)) == Laws.scan.held(rest, as, carried) : Bool} - {Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), scan.some(Scan.found(rest, as, carried))) - == Bool.or(Laws.scan.lone(arg, carried), Laws.scan.held(rest, as, carried)) : Bool} - -def scan.found.or(arg, rest, as, carried, ih): - %scan.lone(arg, carried) : {Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), - scan.some(Scan.found(rest, as, carried))) == Bool.or(_, Laws.scan.held(rest, as, carried)) : Bool} - %ih : {Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), - scan.some(Scan.found(rest, as, carried))) == Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), _) - : Bool} - {==} - -# a walked argument is found exactly when the law's held says so -law scan.found: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +carried: List<&2, String> - {scan.some(Scan.found(ss, as, carried)) == Laws.scan.held(ss, as, carried) : Bool} - -def scan.found(ss, as, carried): - match ss: - case Nil{}: - {==} - case Con{+at, +rest}: - +arg = Calls.arg(as, at) - +lh = Laws.scan.lone(arg, carried) - Equal.trans(Bool, scan.some(Scan.first(Scan.keep_in(Scan.name_of(arg), carried), Scan.found(rest, as, carried))), - Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), scan.some(Scan.found(rest, as, carried))), - Bool.or(lh, Laws.scan.held(rest, as, carried)), - scan.first_some(Scan.keep_in(Scan.name_of(arg), carried), Scan.found(rest, as, carried)), - scan.found.or(arg, rest, as, carried, scan.found(rest, as, carried))) - -# a gated finding list counts when the gate opens and it counts -law scan.gate_len: - for hot: Bool - for open: Bool - for q: Bool - for -x: List<&2, F.Finding> - for -hb: Bool - for ex: {List.length(&2, F.Finding, x) == Bool.pick(Nat, hb, 1n, 0n) : Nat} - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(hot, Bool.and(open, Bool.not(q)))), [], - _u => x)) == Bool.pick(Nat, Bool.and(hot, Bool.and(open, Bool.and(Bool.not(q), hb))), 1n, 0n) : Nat} - -def scan.gate_len(hot, open, q, _x, _hb, ex): - match hot: - case False{}: - {==} - case True{}: - match open: - case False{}: - {==} - case True{}: - match q: - case True{}: - {==} - case False{}: - ex - -# a named call's finding count is the law's -law scan.on_len: - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - {List.length(&2, F.Finding, Scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path)) - == Bool.pick(Nat, Bool.and(hot, Bool.and(open, Bool.and(Bool.not(String.eq(tt, self)), - Laws.scan.held(Scan.slots(tt, ws), Calls.args(kids), carried)))), 1n, 0n) : Nat} - -def scan.on_len(hot, open, tt, line, col, kids, self, ws, carried, lead, path): - +fd = Scan.found(Scan.slots(tt, ws), Calls.args(kids), carried) - +hb = Laws.scan.held(Scan.slots(tt, ws), Calls.args(kids), carried) - scan.gate_len(hot, open, String.eq(tt, self), Scan.cite(fd, tt, line, col, self, lead, path), hb, - Equal.trans(Nat, List.length(&2, F.Finding, Scan.cite(fd, tt, line, col, self, lead, path)), - Bool.pick(Nat, scan.some(fd), 1n, 0n), Bool.pick(Nat, hb, 1n, 0n), - scan.cite_len(fd, tt, line, col, self, lead, path), - Equal.cong(Bool, Nat, b => Bool.pick(Nat, b, 1n, 0n), scan.some(fd), hb, - scan.found(Scan.slots(tt, ws), Calls.args(kids), carried)))) - -# a call's finding count is the law's, whatever its callee's kind -law scan.hit_len: - for kk: Lex.TokKind - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - {List.length(&2, F.Finding, Scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path)) - == Bool.pick(Nat, Laws.scan.call(kk, hot, open, tt, kids, self, ws, carried), 1n, 0n) : Nat} - -def scan.hit_len(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path): - match kk: - case Lex.TName{}: - scan.on_len(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - scan.on_len(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -# scan's walk counts the calls the law counts, at every heat -law scan.walk: - for nn: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - {List.length(&2, F.Finding, Scan.walk(nn, self, ws, carried, lead, path, hot)) - == Laws.scan.count(nn, self, ws, carried, hot) : Nat} - -def scan.walk(nn, self, ws, carried, lead, path, hot): - match nn: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}: - match rest: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{rh, +rr}: - match rh: - case Tree.Group{Lex.Tok{gk, +o, gl, gc}, +kids, cl}: - M1.chars.sum(Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, carried, - lead, path), - Scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, k)), - Scan.walk(rr, self, ws, carried, lead, path, Hoist.warm(hot, k)), - Bool.pick(Nat, Laws.scan.call(k, Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, ws, - carried), 1n, 0n), - Laws.scan.count(kids, self, ws, carried, Hoist.warm(hot, k)), Laws.scan.count(rr, self, ws, - carried, Hoist.warm(hot, k)), - scan.hit_len(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, - path), - scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, k)), - scan.walk(rr, self, ws, carried, lead, path, Hoist.warm(hot, k))) - case Tree.Leaf{tok}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.Stmt{kind, sk, sb}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.NNil{}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.NCons{nh, nt}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.Group{o, +kids, cl}: - M1.index.two(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, carried, lead, - path, - hot), - Laws.scan.count(kids, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SDef{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SType{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SLaw{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SImport{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SCase{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, False{}), Scan.walk(body, self, ws, carried, - lead, path, Bool.and(hot, Calls.calls(body, self))), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, False{}), - Laws.scan.count(body, self, ws, carried, Bool.and(hot, Calls.calls(body, self))), Laws.scan.count(rest, - self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, False{}), scan.walk(body, self, ws, carried, lead, path, - Bool.and(hot, Calls.calls(body, self))), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SFor{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SLet{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.STerm{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.NNil{}: - scan.walk(rest, self, ws, carried, lead, path, hot) - case Tree.NCons{nh, nt}: - scan.walk(rest, self, ws, carried, lead, path, hot) - -# a def that calls itself and is not a proof gathers its walk, entered hot, -# any other nothing -law scan.def: - for g: Bool - for +sig: Tree.Node - for +body: Tree.Node - for +name: String - for +ws: List<&2, Scan.Walk> - for +path: String - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Scan.run(sig, body, name, ws, - path))) - == Bool.pick(Nat, g, Laws.scan.count(body, name, ws, Hoist.carried(sig, body, name), True{}), 0n) : Nat} - -def scan.def(g, sig, body, name, ws, path): - match g: - case True{}: - +carried = Hoist.carried(sig, body, name) - scan.walk(body, name, ws, carried, Scan.lead.go(Calls.params(sig), carried), path, True{}) - case False{}: - {==} - -# over the defs, the rule's join counts what it gathered and what the law -# counts -law scan.go: - for ds: List<&2, Calls.Def> - for +ws: List<&2, Scan.Walk> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Scan.check.go(ds, ws, path, acc)) - == Nat.add(M1.index.sum(acc), Laws.scan.total(ds, ws, path)) : Nat} - -def scan.go(ds, ws, path, acc): - match ds: - case Nil{}: - M1.index.flat(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +g = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Scan.run(sig, body, name, ws, path)) - +n = Bool.pick(Nat, g, Laws.scan.count(body, name, ws, Hoist.carried(sig, body, name), True{}), 0n) - +tot = Laws.scan.total(rest, ws, path) - Equal.trans(Nat, - List.length(&2, F.Finding, Scan.check.go(rest, ws, path, x <> acc)), - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - scan.go(rest, ws, path, x <> acc), - Equal.trans(Nat, - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(List.length(&2, F.Finding, x), tot)), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - M1.index.assoc(M1.index.sum(acc), List.length(&2, F.Finding, x), tot), - Equal.cong(Nat, Nat, z => Nat.add(M1.index.sum(acc), Nat.add(z, tot)), List.length(&2, F.Finding, x), n, - scan.def(g, sig, body, name, ws, path)))) - -def Laws.scan_counts(path, _text, _toks, tree, _bound, _items, e): - +ds = Calls.defs(tree) - %Equal.sym(Bool, Paths.is_law_file(path), False{}, e) : {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, - _, [], _u => Scan.check.on(ds, path))) == Laws.scan.total(ds, Scan.walks(ds), path) : Nat} - scan.go(ds, Scan.walks(ds), path, Nil{}) diff --git a/proofs/ScopeExemptionsFollowPaths.bend b/proofs/ScopeExemptionsFollowPaths.bend index 2c0972d..fec4b1f 100644 --- a/proofs/ScopeExemptionsFollowPaths.bend +++ b/proofs/ScopeExemptionsFollowPaths.bend @@ -12,13 +12,9 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/rewalk.bend as Rewalk -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/rewalk.bend as Rewalk +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/laws/closed.bend as Closed import ../src/rules/suspicious/thunk.bend as Thunk import ../laws/ExemptPathsYieldNoFindings.bend as L1 @@ -82,66 +78,36 @@ def Laws.scope_law_file(path, text, toks, tree, bound, items, text2, toks2, tree scope.con(Pick.check(s), Pick.check(t), L1.pick_exempt(path, text, toks, tree, bound, items, e), L1.pick_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Tail.check(s), Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), - Hoist.check(s), Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Tail.check(t), Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), - Hoist.check(t), Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Tail.check(s), Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Tail.check(t), Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Tail.check(s), Tail.check(t), L1.tail_exempt(path, text, toks, tree, bound, items, e), L1.tail_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), Hoist.check(s), - Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), Hoist.check(t), - Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Concat.check(s), Concat.check(t), L1.concat_exempt(path, text, toks, tree, bound, items, e), L1.concat_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Eager.check(s), Rewalk.check(s), Strict.check(s), Hoist.check(s), Index.check(s), - Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Eager.check(t), Rewalk.check(t), Strict.check(t), Hoist.check(t), Index.check(t), - Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Eager.check(s), Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Eager.check(t), Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Eager.check(s), Eager.check(t), L1.eager_exempt(path, text, toks, tree, bound, items, e), L1.eager_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Rewalk.check(s), Strict.check(s), Hoist.check(s), Index.check(s), Ring.check(s), - Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Rewalk.check(t), Strict.check(t), Hoist.check(t), Index.check(t), Ring.check(t), - Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Rewalk.check(s), Rewalk.check(t), L1.rewalk_exempt(path, text, toks, tree, bound, items, e), L1.rewalk_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Strict.check(s), Hoist.check(s), Index.check(s), Ring.check(s), Table.check(s), - UnitRule.check(s), Thunk.check(s)], - [Strict.check(t), Hoist.check(t), Index.check(t), Ring.check(t), Table.check(t), - UnitRule.check(t), Thunk.check(t)], + [Strict.check(s), Index.check(s), Thunk.check(s)], + [Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Strict.check(s), Strict.check(t), L1.strict_exempt(path, text, toks, tree, bound, items, e), L1.strict_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Hoist.check(s), Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Hoist.check(t), Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], - scope.con(Hoist.check(s), Hoist.check(t), - L1.hoist_exempt(path, text, toks, tree, bound, items, e), - L1.hoist_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Index.check(s), Thunk.check(s)], + [Index.check(t), Thunk.check(t)], scope.con(Index.check(s), Index.check(t), L1.index_exempt(path, text, toks, tree, bound, items, e), L1.index_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], - scope.con(Ring.check(s), Ring.check(t), - L1.ring_exempt(path, text, toks, tree, bound, items, e), - L1.ring_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Table.check(t), UnitRule.check(t), Thunk.check(t)], - scope.con(Table.check(s), Table.check(t), - L1.table_exempt(path, text, toks, tree, bound, items, e), - L1.table_exempt(path, text2, toks2, tree2, bound2, items2, e), - [UnitRule.check(s), Thunk.check(s)], - [UnitRule.check(t), Thunk.check(t)], - scope.con(UnitRule.check(s), UnitRule.check(t), - L1.unit_exempt(path, text, toks, tree, bound, items, e), - L1.unit_exempt(path, text2, toks2, tree2, bound2, items2, e), [Thunk.check(s)], [Thunk.check(t)], scope.con(Thunk.check(s), Thunk.check(t), @@ -149,7 +115,7 @@ def Laws.scope_law_file(path, text, toks, tree, bound, items, text2, toks2, tree L1.thunk_exempt(path, text2, toks2, tree2, bound2, items2, e), Nil{}, Nil{}, - {==})))))))))))) + {==})))))))) # closed reports nothing outside a LAWS.bend law scope.closed: diff --git a/proofs/ScopeLawsGovernPackage.bend b/proofs/ScopeLawsGovernPackage.bend index d1f35f4..efba7e8 100644 --- a/proofs/ScopeLawsGovernPackage.bend +++ b/proofs/ScopeLawsGovernPackage.bend @@ -43,7 +43,7 @@ def scope3.go(ds, cl): match ds: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, _dir, _is_laws, _is_law_file, exempt, +tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{+path, +norm, _dir, _is_laws, _is_law_file, exempt, +tops, _says, _deps, _laws}, +rest}: scope3.go.step(exempt, tops, cl, norm, path, Law.check.go(rest, cl), Laws.under_law(rest, cl), scope3.go(rest, cl)) @@ -78,7 +78,7 @@ def scope3.first(ds, all): match ds: case Nil{}: {==} - case Con{Digest.Digest{_path, _norm, +dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, _norm, +dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _laws}, +rest}: scope3.first.step(is_laws, all, dir, Law.law_dirs(rest), Laws.has_laws(rest), scope3.first(rest, all)) diff --git a/proofs/ScopeProjectRulesRunOnRoot.bend b/proofs/ScopeProjectRulesRunOnRoot.bend index 2ac160b..f5d654d 100644 --- a/proofs/ScopeProjectRulesRunOnRoot.bend +++ b/proofs/ScopeProjectRulesRunOnRoot.bend @@ -5,7 +5,6 @@ import ../src/rules.bend as Rules import ../src/role.bend as Role import ../src/rules/digest.bend as Digest import ../src/rules/laws/law.bend as Law -import ../src/rules/laws/unsafe.bend as Unsafe import ../laws/ScopeProjectRulesRunOnRoot.bend as Laws # with no root but `.`, every directory's package is `.` @@ -29,20 +28,20 @@ def pk.none(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +unsafes, +laws}, + case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +laws}, +tl}: o = Equal.sym(String, Rules.owner.go(Rules.ups(dir), []), "", pk.go_none(Rules.ups(dir))) %o : {Bool.pick(List<&2, Digest.Digest>, String.eq(_, ""), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> Rules.members(tl, [], ""), Rules.members(tl, [], "")) - == Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> tl + == Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> tl : List<&2, Digest.Digest>} Equal.cong(List<&2, Digest.Digest>, List<&2, Digest.Digest>, - xs => Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> xs, + xs => Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> xs, Rules.members(tl, [], ""), tl, pk.none(tl)) def Laws.one_package(s, ss): +ds = Digest.all.role(Role.marks(s <> ss), s <> ss) Equal.cong(List<&2, Digest.Digest>, List<&2, F.Finding>, - mm => List.concat(&2, F.Finding, [Law.check(mm), [], Unsafe.check(ds)]), + mm => List.concat(&2, F.Finding, [Law.check(mm), []]), Rules.members(ds, [], ""), ds, pk.none(ds)) diff --git a/proofs/StrictReportsEagerSelfCalls.bend b/proofs/StrictReportsEagerSelfCalls.bend index ef0e0eb..58485c4 100644 --- a/proofs/StrictReportsEagerSelfCalls.bend +++ b/proofs/StrictReportsEagerSelfCalls.bend @@ -6,7 +6,7 @@ import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict import ../laws/StrictReportsEagerSelfCalls.bend as Laws import ./lemmas/Nat.bend as M1 import ./lemmas/Pick.bend as M2 diff --git a/proofs/StringsReportLongLiteralArms.bend b/proofs/StringsReportLongLiteralArms.bend index 9360abd..0055589 100644 --- a/proofs/StringsReportLongLiteralArms.bend +++ b/proofs/StringsReportLongLiteralArms.bend @@ -5,7 +5,7 @@ import ../src/finding.bend as F import ../src/rules/tokens.bend as T import ../src/syntax/tree.bend as Tree import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/correctness/strings.bend as Strings +import ../src/rules/perf/strings.bend as Strings import ../laws/StringsReportLongLiteralArms.bend as Laws import ../laws/defs/MatchArmRules.bend as D1 import ./lemmas/MatchStatementCounts.bend as M1 diff --git a/proofs/TableReportsFixedTableAccess.bend b/proofs/TableReportsFixedTableAccess.bend deleted file mode 100644 index 7a4f52d..0000000 --- a/proofs/TableReportsFixedTableAccess.bend +++ /dev/null @@ -1,123 +0,0 @@ -# proofs of laws/TableReportsFixedTableAccess.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/table.bend as Table -import ../laws/TableReportsFixedTableAccess.bend as Laws -import ./lemmas/Pick.bend as M1 - -# a call site's own finding: one exactly when the law's site test holds -law table.one: - for +tt: String - for +o: String - for +kids: Tree.Node - for +fixed: List<&2, String> - for +path: String - for +line: U32 - for +col: U32 - {List.length(&2, F.Finding, Table.hit(String.eq(o, "("), tt, kids, fixed, path, line, col)) - == Bool.pick(Nat, Laws.table.site(tt, o, kids, fixed), 1n, 0n) : Nat} - -def table.one(tt, o, kids, fixed, path, line, col): - M1.chars.one(Laws.table.site(tt, o, kids, fixed), - Table.cite(String.eq(tt, "List.get"), path, line, col, U32.from_nat(String.length(tt)))) - -def Laws.table_walk_counts(nn, path, fixed): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{k, +t, +l, +c}}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{Lex.Tok{k2, +o, l2, c2}, +kids, cl}: - M1.table.three(Table.hit(String.eq(o, "("), t, kids, fixed, path, l, c), Table.walk(kids, path, - fixed), - Table.walk(r2, path, fixed), Bool.pick(Nat, Laws.table.site(t, o, kids, fixed), 1n, 0n), - Laws.table.count(kids, fixed), Laws.table.count(r2, fixed), - table.one(t, o, kids, fixed, path, l, c), - Laws.table_walk_counts(kids, path, fixed), Laws.table_walk_counts(r2, path, fixed)) - case Tree.Leaf{x}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Stmt{a, b, d}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NNil{}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NCons{a, b}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Leaf{x}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Group{a, b, d}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Stmt{a, b, d}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NNil{}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Group{open, +kids, close}: - M1.table.two(Table.walk(kids, path, fixed), Table.walk(rest, path, fixed), - Laws.table.count(kids, fixed), Laws.table.count(rest, fixed), - Laws.table_walk_counts(kids, path, fixed), Laws.table_walk_counts(rest, path, fixed)) - case Tree.Stmt{kind, +kids, +body}: - M1.table.three(Table.walk(kids, path, fixed), Table.walk(body, path, fixed), Table.walk(rest, path, fixed), - Laws.table.count(kids, fixed), Laws.table.count(body, fixed), Laws.table.count(rest, fixed), - Laws.table_walk_counts(kids, path, fixed), Laws.table_walk_counts(body, path, fixed), - Laws.table_walk_counts(rest, path, fixed)) - case Tree.NNil{}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NCons{a, b}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Leaf{x}: - {==} - case Tree.Group{a, b, d}: - {==} - case Tree.Stmt{a, b, d}: - {==} - case Tree.NNil{}: - {==} - -# one def's findings: walked when its guard keeps it, else none -law table.stop: - for +b: Bool - for +body: Tree.Node - for +path: String - for +fixed: List<&2, String> - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], _u => Table.walk(body, path, fixed))) - == Bool.pick(Nat, b, Laws.table.count(body, fixed), 0n) : Nat} - -def table.stop(b, body, path, fixed): - match b: - case True{}: - Laws.table_walk_counts(body, path, fixed) - case False{}: - {==} - -# table's def walk: the findings gathered so far, then each def's -law table.go: - for ds: List<&2, Calls.Def> - for +mods: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Table.check.go(ds, mods, path, acc)) == M1.pick.sum(acc, Laws.table.defs(ds, mods, path)) - : Nat} - -def table.go(ds, mods, path, acc): - match ds: - case Nil{}: - M1.pick.rev(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +keep = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(keep), [], _u => Table.walk(body, path, Table.scope(body, mods))) - +cb = Bool.pick(Nat, keep, Laws.table.count(body, Table.scope(body, mods)), 0n) - Equal.trans(Nat, List.length(&2, F.Finding, Table.check.go(rest, mods, path, x <> acc)), - M1.pick.sum(acc, Nat.add(List.length(&2, F.Finding, x), Laws.table.defs(rest, mods, path))), - M1.pick.sum(acc, Nat.add(cb, Laws.table.defs(rest, mods, path))), - table.go(rest, mods, path, x <> acc), - Equal.cong(Nat, Nat, n => M1.pick.sum(acc, Nat.add(n, Laws.table.defs(rest, mods, path))), - List.length(&2, F.Finding, x), cb, table.stop(keep, body, path, Table.scope(body, mods)))) - -def Laws.table_counts(path, _text, _toks, tree, _bound, _items): - table.go(Calls.defs(tree), Table.modules(Calls.defs(tree), []), path, Nil{}) diff --git a/proofs/UnitReportsMultiplyByOne.bend b/proofs/UnitReportsMultiplyByOne.bend deleted file mode 100644 index 5116bd5..0000000 --- a/proofs/UnitReportsMultiplyByOne.bend +++ /dev/null @@ -1,715 +0,0 @@ -# proofs of laws/UnitReportsMultiplyByOne.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/unit.bend as UnitRule -import ../laws/UnitReportsMultiplyByOne.bend as Laws -import ./lemmas/Pick.bend as M1 - -# unit (laws/UnitReportsMultiplyByOne.bend): the rule's literal, operator and call tests are the -# law's, read through whether a maybe holds a token (unit.has), and its walk -# counts the law's count. - -# does the maybe hold a token? -def unit.has(mm: Maybe<&2, Lex.Tok>) -> Bool: - match mm: - case None{}: - False{} - case Some{tok}: - True{} - -# a finding on the maybe's token: one when it holds one -law unit.cite: - for mm: Maybe<&2, Lex.Tok> - for +path: String - {List.length(&2, F.Finding, UnitRule.cite(mm, path)) == Bool.pick(Nat, unit.has(mm), 1n, 0n) : Nat} - -def unit.cite(mm, _path): - match mm: - case None{}: - {==} - case Some{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - {==} - -# a pick between a token and none holds one exactly when it picks the token -law unit.hp: - for b: Bool - for -tok: Lex.Tok - {unit.has(Bool.pick(Maybe<&2, Lex.Tok>, b, Some{tok}, None{})) == b : Bool} - -def unit.hp(b, _tok): - match b: - case True{}: - {==} - case False{}: - {==} - -# the rule's literal one is the law's -law unit.one_is: - for mm: Maybe<&2, Lex.Tok> - {unit.has(UnitRule.one_of(mm)) == Laws.unit.one(mm) : Bool} - -def unit.one_is(mm): - match mm: - case None{}: - {==} - case Some{tok}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - unit.hp(UnitRule.one_text(t), Lex.Tok{Lex.TNum{}, t, l, c}) - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -# the first of two maybes that holds a token: one when either holds one -law unit.either: - for xx: Maybe<&2, Lex.Tok> - for -yy: Maybe<&2, Lex.Tok> - {unit.has(UnitRule.either(xx, yy)) == Bool.or(unit.has(xx), unit.has(yy)) : Bool} - -def unit.either(xx, _yy): - match xx: - case None{}: - {==} - case Some{tok}: - {==} - -# equal Bools give equal ors -law unit.or2: - for -a: Bool - for -b: Bool - for -c: Bool - for -d: Bool - for e1: {a == b : Bool} - for e2: {c == d : Bool} - {Bool.or(a, c) == Bool.or(b, d) : Bool} - -def unit.or2(a, b, c, d, e1, e2): - Equal.trans(Bool, Bool.or(a, c), Bool.or(b, c), Bool.or(b, d), Equal.cong(Bool, Bool, x => Bool.or(x, c), a, b, e1), - Equal.cong(Bool, Bool, x => Bool.or(b, x), c, d, e2)) - -# or commutes -law unit.comm: - for a: Bool - for b: Bool - {Bool.or(a, b) == Bool.or(b, a) : Bool} - -def unit.comm(a, b): - match a: - case True{}: - match b: - case True{}: - {==} - case False{}: - {==} - case False{}: - match b: - case True{}: - {==} - case False{}: - {==} - -# the literal an operator's finding cites is there exactly when the law's -# operator is by one: `*` (s) with one after or before, `/` (d) with one after -law unit.which: - for s: Bool - for d: Bool - for +pp: Maybe<&2, Lex.Tok> - for +nn: Maybe<&2, Lex.Tok> - {unit.has(Bool.pick(Maybe<&2, Lex.Tok>, s, UnitRule.either(UnitRule.one_of(nn), UnitRule.one_of(pp)), - Bool.pick(Maybe<&2, Lex.Tok>, d, UnitRule.one_of(nn), None{}))) - == Bool.pick(Bool, s, Bool.or(Laws.unit.one(nn), Laws.unit.one(pp)), Bool.and(d, Laws.unit.one(nn))) : Bool} - -def unit.which(s, d, pp, nn): - match s: - case True{}: - Equal.trans(Bool, unit.has(UnitRule.either(UnitRule.one_of(nn), UnitRule.one_of(pp))), - Bool.or(unit.has(UnitRule.one_of(nn)), unit.has(UnitRule.one_of(pp))), - Bool.or(Laws.unit.one(nn), Laws.unit.one(pp)), - unit.either(UnitRule.one_of(nn), UnitRule.one_of(pp)), - unit.or2(unit.has(UnitRule.one_of(nn)), Laws.unit.one(nn), unit.has(UnitRule.one_of(pp)), Laws.unit.one(pp), - unit.one_is(nn), unit.one_is(pp))) - case False{}: - match d: - case True{}: - unit.one_is(nn) - case False{}: - {==} - -# the node after, to the rule and to the law -law unit.first_is: - for nn: Tree.Node - {UnitRule.first_tok(nn) == Laws.unit.first(nn) : Maybe<&2, Lex.Tok>} - -def unit.first_is(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# an operator's finding: one exactly when it is by one -law unit.opx: - for +t: String - for +prev: Maybe<&2, Lex.Tok> - for +rest: Tree.Node - for +path: String - {List.length(&2, F.Finding, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)) - == Bool.pick(Nat, Laws.unit.by_one(t, prev, Laws.unit.first(rest)), 1n, 0n) : Nat} - -def unit.opx(t, prev, rest, path): - +ft = UnitRule.first_tok(rest) - +uf = Laws.unit.first(rest) - +wh = UnitRule.which(t, prev, ft) - Equal.trans(Nat, List.length(&2, F.Finding, UnitRule.cite(wh, path)), Bool.pick(Nat, unit.has(wh), 1n, 0n), - Bool.pick(Nat, Laws.unit.by_one(t, prev, uf), 1n, 0n), unit.cite(wh, path), - Equal.cong(Bool, Nat, b => Bool.pick(Nat, b, 1n, 0n), unit.has(wh), Laws.unit.by_one(t, prev, uf), - Equal.trans(Bool, unit.has(wh), Laws.unit.by_one(t, prev, ft), Laws.unit.by_one(t, prev, uf), - unit.which(String.eq(t, "*"), String.eq(t, "/"), prev, ft), - Equal.cong(Maybe<&2, Lex.Tok>, Bool, m => Laws.unit.by_one(t, prev, m), ft, uf, unit.first_is(rest))))) - -# an operator in a region as hot as hot: its finding when hot -law unit.on: - for hot: Bool - for -x: List<&2, F.Finding> - for -b: Bool - for e: {List.length(&2, F.Finding, x) == Bool.pick(Nat, b, 1n, 0n) : Nat} - {List.length(&2, F.Finding, Bool.pick(List<&2, F.Finding>, Bool.and(hot, True{}), x, [])) - == Bool.pick(Nat, Bool.and(hot, b), 1n, 0n) : Nat} - -def unit.on(hot, _x, _b, e): - match hot: - case True{}: - e - case False{}: - {==} - -# a leaf that is no operator: no finding -law unit.off: - for hot: Bool - for -x: List<&2, F.Finding> - {List.length(&2, F.Finding, Bool.pick(List<&2, F.Finding>, Bool.and(hot, False{}), x, [])) - == Bool.pick(Nat, Bool.and(hot, False{}), 1n, 0n) : Nat} - -def unit.off(hot, _x): - match hot: - case True{}: - {==} - case False{}: - {==} - -# a leaf's own finding onto the rest's -law unit.leaf: - for +x: List<&2, F.Finding> - for +more: List<&2, F.Finding> - for +b: Bool - for -m: Nat - for ex: {List.length(&2, F.Finding, x) == Bool.pick(Nat, b, 1n, 0n) : Nat} - for em: {List.length(&2, F.Finding, more) == m : Nat} - {List.length(&2, F.Finding, List.append(&2, F.Finding, x, more)) == Bool.pick(Nat, b, 1n+m, m) : Nat} - -def unit.leaf(x, more, b, m, ex, em): - Equal.trans(Nat, List.length(&2, F.Finding, List.append(&2, F.Finding, x, more)), Nat.add(Bool.pick(Nat, b, 1n, 0n), - m), - Bool.pick(Nat, b, 1n+m, m), M1.pick.join(x, more, Bool.pick(Nat, b, 1n, 0n), m, ex, em), M1.fuel.addpick(b, m)) - -# the rule's argument that is the literal one alone is the law's -law unit.arg_is: - for aa: Tree.Node - {unit.has(UnitRule.arg_one(aa)) == Laws.unit.lone(aa) : Bool} - -def unit.arg_is(aa): - match aa: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: - unit.one_is(Some{tok}) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# the rule's some argument one is the law's -law unit.any_is: - for as: List<&2, Tree.Node> - {unit.has(UnitRule.any_one(as)) == Laws.unit.any(as) : Bool} - -def unit.any_is(as): - match as: - case Nil{}: - {==} - case Con{+h, +rest}: - Equal.trans(Bool, unit.has(UnitRule.either(UnitRule.any_one(rest), UnitRule.arg_one(h))), - Bool.or(unit.has(UnitRule.any_one(rest)), unit.has(UnitRule.arg_one(h))), - Bool.or(Laws.unit.lone(h), Laws.unit.any(rest)), - unit.either(UnitRule.any_one(rest), UnitRule.arg_one(h)), - Equal.trans(Bool, Bool.or(unit.has(UnitRule.any_one(rest)), unit.has(UnitRule.arg_one(h))), - Bool.or(Laws.unit.any(rest), Laws.unit.lone(h)), Bool.or(Laws.unit.lone(h), Laws.unit.any(rest)), - unit.or2(unit.has(UnitRule.any_one(rest)), Laws.unit.any(rest), unit.has(UnitRule.arg_one(h)), - Laws.unit.lone(h), unit.any_is(rest), unit.arg_is(h)), - unit.comm(Laws.unit.any(rest), Laws.unit.lone(h)))) - -# the rule's last argument one is the law's -law unit.last_is: - for as: List<&2, Tree.Node> - {unit.has(UnitRule.last_one(as)) == Laws.unit.last(as) : Bool} - -def unit.last_is(as): - match as: - case Nil{}: - {==} - case Con{h, t}: - match t: - case Nil{}: - unit.arg_is(h) - case Con{x, y}: - unit.last_is(t) - -# a finding on what the maybe holds, as the law's Bool says -law unit.has_is: - for +mm: Maybe<&2, Lex.Tok> - for -b: Bool - for +path: String - for e: {unit.has(mm) == b : Bool} - {List.length(&2, F.Finding, UnitRule.cite(mm, path)) == Bool.pick(Nat, b, 1n, 0n) : Nat} - -def unit.has_is(mm, b, path, e): - Equal.trans(Nat, List.length(&2, F.Finding, UnitRule.cite(mm, path)), Bool.pick(Nat, unit.has(mm), 1n, 0n), - Bool.pick(Nat, b, 1n, 0n), unit.cite(mm, path), Equal.cong(Bool, Nat, x => Bool.pick(Nat, x, 1n, 0n), unit.has(mm), - b, e)) - -# a divide's finding, when the call is a divide -law unit.div: - for db: Bool - for +as: List<&2, Tree.Node> - for +path: String - {List.length(&2, F.Finding, UnitRule.on_div_is(db, as, path)) == Bool.pick(Nat, Bool.and(db, Laws.unit.last(as)), 1n, - 0n) - : Nat} - -def unit.div(db, as, path): - match db: - case True{}: - unit.has_is(UnitRule.last_one(as), Laws.unit.last(as), path, unit.last_is(as)) - case False{}: - {==} - -# a multiply's finding, else a divide's -law unit.opis: - for mb: Bool - for +tt: String - for +as: List<&2, Tree.Node> - for +path: String - {List.length(&2, F.Finding, UnitRule.on_op_is(mb, tt, as, path)) - == Bool.pick(Nat, Bool.pick(Bool, mb, Laws.unit.any(as), - Bool.and(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), - Laws.unit.last(as))), 1n, 0n) - : Nat} - -def unit.opis(mb, tt, as, path): - match mb: - case True{}: - unit.has_is(UnitRule.any_one(as), Laws.unit.any(as), path, unit.any_is(as)) - case False{}: - unit.div(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), as, path) - -# a group's own finding: a `(` call that multiplies or divides by one, when hot -law unit.own: - for call: Bool - for +tt: String - for +kids: Tree.Node - for +path: String - for hot: Bool - {List.length(&2, F.Finding, UnitRule.own_of(call, tt, kids, path, hot)) - == Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(tt, Calls.args(kids)))), 1n, 0n) : Nat} - -def unit.own(call, tt, kids, path, hot): - match call: - case True{}: - match hot: - case True{}: - unit.opis(List.contains(~String, ~String.eq, ["Nat.mul", "U32.mul", "F32.mul"], tt), tt, - Calls.args(kids), path) - case False{}: - {==} - case False{}: - {==} - -# the name a group calls, to the rule and to the law -law unit.callee: - for mm: Maybe<&2, Lex.Tok> - {UnitRule.callee(mm) == Laws.unit.name(mm) : String} - -def unit.callee(mm): - match mm: - case None{}: - {==} - case Some{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - {==} - -def Laws.unit_walk_counts(nn, prev, self, path, hot): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TName{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TName{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TName{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TName{}, t, l, c}}, self, path, hot)) - case Lex.TUpper{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TUpper{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TUpper{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TUpper{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TUpper{}, t, l, c}}, self, path, hot)) - case Lex.TDotted{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TDotted{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TDotted{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TDotted{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TDotted{}, t, l, c}}, self, path, hot)) - case Lex.TWild{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TWild{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TWild{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TWild{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TWild{}, t, l, c}}, self, path, hot)) - case Lex.TKey{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TKey{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TKey{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TKey{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TKey{}, t, l, c}}, self, path, hot)) - case Lex.TNum{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TNum{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TNum{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TNum{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TNum{}, t, l, c}}, self, path, hot)) - case Lex.TStr{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TStr{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TStr{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TStr{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TStr{}, t, l, c}}, self, path, hot)) - case Lex.TChar{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TChar{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TChar{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TChar{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TChar{}, t, l, c}}, self, path, hot)) - case Lex.TComment{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TComment{}, t, l, c}, prev, UnitRule.first_tok(rest), path, - hot), UnitRule.walk(rest, Some{Lex.Tok{Lex.TComment{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TComment{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TComment{}, t, l, c}}, self, path, hot)) - case Lex.TSpace{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TSpace{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TSpace{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TSpace{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TSpace{}, t, l, c}}, self, path, hot)) - case Lex.TNewline{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TNewline{}, t, l, c}, prev, UnitRule.first_tok(rest), path, - hot), UnitRule.walk(rest, Some{Lex.Tok{Lex.TNewline{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TNewline{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TNewline{}, t, l, c}}, self, path, hot)) - case Lex.TOp{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TOp{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, None{}, self, path, hot), - Bool.and(hot, Laws.unit.by_one(t, prev, Laws.unit.first(rest))), Laws.unit.count(rest, None{}, self, - hot), - unit.on(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path), - Laws.unit.by_one(t, prev, Laws.unit.first(rest)), unit.opx(t, prev, rest, path)), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Lex.TColon{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TColon{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TColon{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TColon{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TColon{}, t, l, c}}, self, path, hot)) - case Lex.TEq{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TEq{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TEq{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TEq{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TEq{}, t, l, c}}, self, path, hot)) - case Lex.TBind{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TBind{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TBind{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TBind{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TBind{}, t, l, c}}, self, path, hot)) - case Lex.TArrow{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TArrow{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TArrow{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TArrow{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TArrow{}, t, l, c}}, self, path, hot)) - case Lex.TLam{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TLam{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TLam{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TLam{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TLam{}, t, l, c}}, self, path, hot)) - case Lex.TAll{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TAll{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TAll{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TAll{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TAll{}, t, l, c}}, self, path, hot)) - case Lex.TAmp{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TAmp{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TAmp{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TAmp{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TAmp{}, t, l, c}}, self, path, hot)) - case Lex.TOpen{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TOpen{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TOpen{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TOpen{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TOpen{}, t, l, c}}, self, path, hot)) - case Lex.TClose{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TClose{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TClose{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TClose{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TClose{}, t, l, c}}, self, path, hot)) - case Lex.TComma{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TComma{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TComma{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TComma{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TComma{}, t, l, c}}, self, path, hot)) - case Tree.Group{open, +kids, close}: - match open: - case Lex.Tok{ok, +o, ol, oc}: - +call = String.eq(o, "(") - +own = UnitRule.own_of(call, UnitRule.callee(prev), kids, path, hot) - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), own, UnitRule.walk(rest, None{}, self, path, - hot), - Laws.unit.count(kids, None{}, self, hot), - Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(Laws.unit.name(prev), Calls.args(kids)))), - 1n, 0n), - Laws.unit.count(rest, None{}, self, hot), Laws.unit_walk_counts(kids, None{}, self, path, hot), - Equal.trans(Nat, List.length(&2, F.Finding, own), - Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(UnitRule.callee(prev), Calls.args(kids)))), - 1n, 0n), - Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(Laws.unit.name(prev), Calls.args(kids)))), - 1n, 0n), - unit.own(call, UnitRule.callee(prev), kids, path, hot), - Equal.cong(String, Nat, x => Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(x, - Calls.args(kids)))), 1n, - 0n), UnitRule.callee(prev), Laws.unit.name(prev), unit.callee(prev))), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SDef{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SType{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SLaw{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SImport{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SCase{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, False{}), UnitRule.walk(body, None{}, self, path, - Bool.and(hot, Calls.calls(body, self))), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, False{}), - Laws.unit.count(body, None{}, self, Bool.and(hot, Calls.calls(body, self))), Laws.unit.count(rest, - None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, False{}), Laws.unit_walk_counts(body, None{}, self, - path, Bool.and(hot, Calls.calls(body, self))), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SFor{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SLet{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.STerm{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.NNil{}: - Laws.unit_walk_counts(rest, prev, self, path, hot) - case Tree.NCons{x, y}: - Laws.unit_walk_counts(rest, prev, self, path, hot) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# one def's findings: its walk when it calls itself and is not exempt -law unit.stop: - for b: Bool - for +body: Tree.Node - for +name: String - for +path: String - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], - _u => UnitRule.walk(body, None{}, name, path, True{}))) - == Bool.pick(Nat, b, Laws.unit.count(body, None{}, name, True{}), 0n) : Nat} - -def unit.stop(b, body, name, path): - match b: - case True{}: - Laws.unit_walk_counts(body, None{}, name, path, True{}) - case False{}: - {==} - -# unit's def walk: the findings gathered so far, then each def's -law unit.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, UnitRule.check.go(ds, path, acc)) == M1.pick.sum(acc, Laws.unit.defs(ds, path)) : Nat} - -def unit.go(ds, path, acc): - match ds: - case Nil{}: - M1.pick.rev(acc, Nil{}) - case Con{d, +rest}: - match d: - case Calls.Def{+name, +sig, +body}: - +b = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], _u => UnitRule.walk(body, None{}, name, path, True{})) - +cb = Bool.pick(Nat, b, Laws.unit.count(body, None{}, name, True{}), 0n) - Equal.trans(Nat, List.length(&2, F.Finding, UnitRule.check.go(rest, path, x <> acc)), - M1.pick.sum(acc, Nat.add(List.length(&2, F.Finding, x), Laws.unit.defs(rest, path))), - M1.pick.sum(acc, Nat.add(cb, Laws.unit.defs(rest, path))), - unit.go(rest, path, x <> acc), - Equal.cong(Nat, Nat, n => M1.pick.sum(acc, Nat.add(n, Laws.unit.defs(rest, path))), - List.length(&2, F.Finding, x), cb, unit.stop(b, body, name, path))) - -def Laws.unit_counts(path, _text, _toks, tree, _bound, _items): - unit.go(Calls.defs(tree), path, Nil{}) diff --git a/proofs/UnsafeReportsReachableForeignDefs.bend b/proofs/UnsafeReportsReachableForeignDefs.bend deleted file mode 100644 index 41948e5..0000000 --- a/proofs/UnsafeReportsReachableForeignDefs.bend +++ /dev/null @@ -1,121 +0,0 @@ -# proofs of laws/UnsafeReportsReachableForeignDefs.bend -import Base -import ../src/finding.bend as F -import ../src/rules/laws/unsafe.bend as Unsafe -import ../src/rules/imports.bend as Imports -import ../src/rules/digest.bend as Digest -import ../laws/UnsafeReportsReachableForeignDefs.bend as Laws -import ../laws/defs/Unsafe.bend as D1 -import ./lemmas/ImportClosure.bend as M1 -import ./lemmas/CarriedSearch.bend as CS - -# unsafe (laws/UnsafeReportsReachableForeignDefs.bend) - -# the rule's search of the law files' closures is unsafe.origin -law unsafe.reacher: - for ls: List<&2, Digest.Digest> - for +all: List<&2, Digest.Digest> - for +pp: String - {Unsafe.reacher(Unsafe.reaches(ls, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)), pp) == Laws.unsafe.origin(ls, all, pp) : Maybe<&2, String>} - -def unsafe.reacher(ls, all, pp): - match ls: - case Nil{}: - {==} - case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: - match is_law_file: - case True{}: - %unsafe.reacher(rest, all, pp) : - {Unsafe.reacher(Unsafe.Reach{path, D1.unsafe.closure(all, norm)} - <> Unsafe.reaches(rest, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)), pp) - == Bool.pick(Maybe<&2, String>, Imports.has(D1.unsafe.closure(all, norm), pp), Some{path}, _) - : Maybe<&2, String>} - CS.cs.reacher(path, D1.unsafe.closure(all, norm), Unsafe.reaches(rest, Digest.edges(all), - Imports.paths(Digest.edges(all)), List.length(&2, Digest.Digest, all)), pp) - case False{}: - unsafe.reacher(rest, all, pp) - -# the rule's report is unsafe.each -law unsafe.report: - for us: List<&2, Digest.Unsafe> - for +origin: String - for +via: String - for +path: String - {Unsafe.report(us, origin, via, path) == Laws.unsafe.each(us, origin, via, path) : List<&2, F.Finding>} - -def unsafe.report(us, origin, via, path): - match us: - case Nil{}: - {==} - case Con{Digest.Unsafe{+n, +l, +c, fo}, rest}: - match fo: - case True{}: - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - t => F.Finding{path, l, c, 3, "unsafe", Unsafe.say(True{}, n, origin, via)} <> t, - Unsafe.report(rest, origin, via, path), Laws.unsafe.each(rest, origin, via, path), - unsafe.report(rest, origin, via, path)) - case False{}: - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - t => F.Finding{path, l, c, 7, "unsafe", Unsafe.say(False{}, n, origin, via)} <> t, - Unsafe.report(rest, origin, via, path), Laws.unsafe.each(rest, origin, via, path), - unsafe.report(rest, origin, via, path)) - -# the rule's found is unsafe.at -law unsafe.found: - for mm: Maybe<&2, String> - for us: List<&2, Digest.Unsafe> - for +path: String - for +norm: String - for +all: List<&2, Digest.Digest> - {Unsafe.found(mm, us, path, norm, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)) == Laws.unsafe.at(mm, us, path, norm, all) : List<&2, F.Finding>} - -def unsafe.found(mm, us, path, norm, all): - match mm: - case None{}: - {==} - case Some{+origin}: - match us: - case Nil{}: - {==} - case Con{u, rest}: - unsafe.report(u <> rest, origin, Laws.unsafe.via(all, Imports.norm(origin), norm), path) - -# the rule's walk over fs, against the law files of all, as its check runs it -def unsafe.run(fs: List<&2, Digest.Digest>, +all: List<&2, Digest.Digest>) -> List<&2, F.Finding>: - Unsafe.check.go(fs, Unsafe.reaches(all, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)), Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)) - -# the rule's walk over the files is unsafe.files -law unsafe.go: - for fs: List<&2, Digest.Digest> - for +all: List<&2, Digest.Digest> - {unsafe.run(fs, all) == Laws.unsafe.files(fs, all) : List<&2, F.Finding>} - -def unsafe.go(fs, all): - match fs: - case Nil{}: - {==} - case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, +unsafes, laws}, rest}: - %unsafe.go(rest, all) : - {unsafe.run(Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} - <> rest, all) - == List.append(&2, F.Finding, Laws.unsafe.at(Laws.unsafe.origin(all, all, norm), unsafes, path, norm, all), _) - : List<&2, F.Finding>} - %unsafe.found(Laws.unsafe.origin(all, all, norm), unsafes, path, norm, all) : - {unsafe.run(Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} - <> rest, all) - == List.append(&2, F.Finding, _, unsafe.run(rest, all)) : List<&2, F.Finding>} - %unsafe.reacher(all, all, norm) : - {unsafe.run(Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} - <> rest, all) - == List.append(&2, F.Finding, Unsafe.found(_, unsafes, path, norm, Digest.edges(all), - Imports.paths(Digest.edges(all)), List.length(&2, Digest.Digest, all)), unsafe.run(rest, all)) - : List<&2, F.Finding>} - {==} - -def Laws.unsafe_reports(ds): - unsafe.go(ds, ds) diff --git a/proofs/lemmas/CarriedSearch.bend b/proofs/lemmas/CarriedSearch.bend index 9609105..894a9d0 100644 --- a/proofs/lemmas/CarriedSearch.bend +++ b/proofs/lemmas/CarriedSearch.bend @@ -6,7 +6,6 @@ import ../../src/syntax/lex.bend as Lex import ../../src/syntax/tree.bend as Tree import ../../src/config.bend as Config import ../../src/rules/digest.bend as Digest -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../src/rules/imports.bend as Imports import ../../src/rules/calls.bend as Calls import ../../src/syntax/bind.bend as Bind @@ -102,51 +101,6 @@ law cs.alias_path: def cs.alias_path(n, p, as, name): cs.alias_path.pick(String.eq(n, name), as, name, p) -# a hit carried into reacher's walk is its answer -law cs.reacher.some: - for rs: List<&2, Unsafe.Reach> - for +pp: String - for +x: String - {Unsafe.reacher.go(rs, pp, Some{x}) == Some{x} : Maybe<&2, String>} - -def cs.reacher.some(rs, _pp, _x): - match rs: - case Nil{}: - {==} - case Con{h, r}: - match h: - case Unsafe.Reach{o, ps}: - {==} - -# reacher's walk past a test: the pick it stands for -law cs.reacher.pick: - for b: Bool - for +rs: List<&2, Unsafe.Reach> - for +pp: String - for +o: String - {Unsafe.reacher.go(rs, pp, Bool.pick(Maybe<&2, String>, b, Some{o}, None{})) - == Bool.pick(Maybe<&2, String>, b, Some{o}, Unsafe.reacher.go(rs, pp, None{})) : Maybe<&2, String>} - -def cs.reacher.pick(b, rs, pp, o): - match b: - case True{}: - cs.reacher.some(rs, pp, o) - case False{}: - {==} - -# reacher on a law file's reach: the file when its closure has the path, else -# the rest's -law cs.reacher: - for +o: String - for +ps: List<&2, String> - for +rs: List<&2, Unsafe.Reach> - for +pp: String - {Unsafe.reacher(Unsafe.Reach{o, ps} <> rs, pp) - == Bool.pick(Maybe<&2, String>, Imports.has(ps, pp), Some{o}, Unsafe.reacher(rs, pp)) : Maybe<&2, String>} - -def cs.reacher(o, ps, rs, pp): - cs.reacher.pick(Imports.has(ps, pp), rs, pp, o) - # a hit carried into find's walk is its answer law cs.find.some: for env: List<&2, Bind.Bind> diff --git a/proofs/lemmas/ImportClosure.bend b/proofs/lemmas/ImportClosure.bend index cebf85d..961b498 100644 --- a/proofs/lemmas/ImportClosure.bend +++ b/proofs/lemmas/ImportClosure.bend @@ -1,10 +1,9 @@ # the fast closure walk, lookups and early stops equal the spec walk import Base import ../../src/lazy/lazy.bend as Lazy -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../src/rules/imports.bend as Imports import ../../src/rules/digest.bend as Digest -import ../../laws/UnsafeReportsReachableForeignDefs.bend as L1 +import ../../laws/ImportReachIsClosed.bend as L1 import ../../laws/defs/Unsafe.bend as D1 import ./RoleProofPicked.bend as M1 import ./UnsafeStringCompare.bend as M2 @@ -394,7 +393,7 @@ def unsafe.deps_imports(ds, pp): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, +deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, +deps, laws}, +rest}: %Equal.sym(List<&2, String>, Imports.deps.spec(Digest.edges(rest), pp), D1.unsafe.imports(rest, pp), unsafe.deps_imports(rest, pp)) : {Bool.pick(List<&2, String>, String.eq(norm, pp), deps, _) @@ -411,7 +410,7 @@ def unsafe.read_paths(ds, pp): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: %Equal.sym(Bool, Imports.has(norm <> Imports.paths(Digest.edges(rest)), pp), Bool.or(String.eq(norm, pp), Imports.has(Imports.paths(Digest.edges(rest)), pp)), unsafe.has_con(norm, Imports.paths(Digest.edges(rest)), pp)) : @@ -430,7 +429,7 @@ def unsafe.len_paths(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: Equal.cong(Nat, Nat, n => 1n+n, List.length(&2, String, Imports.paths(Digest.edges(rest))), List.length(&2, Digest.Digest, rest), unsafe.len_paths(rest)) @@ -1689,7 +1688,7 @@ def unsafe.unread_imports(ds, pp, h): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: unsafe.unread_imports.at(deps, D1.unsafe.imports(rest, pp), D1.unsafe.read(rest, pp), String.eq(norm, pp), h, z => unsafe.unread_imports(rest, pp, z)) diff --git a/proofs/lemmas/LawFileExemptions.bend b/proofs/lemmas/LawFileExemptions.bend index 1f66a38..c84044e 100644 --- a/proofs/lemmas/LawFileExemptions.bend +++ b/proofs/lemmas/LawFileExemptions.bend @@ -14,13 +14,10 @@ import ../../src/rules/correctness/pick.bend as Pick import ../../src/rules/pedantic/tail.bend as Tail import ../../src/rules/suspicious/concat.bend as Concat import ../../src/rules/suspicious/eager.bend as Eager -import ../../src/rules/suspicious/rewalk.bend as Rewalk -import ../../src/rules/suspicious/strict.bend as Strict -import ../../src/rules/suspicious/hoist.bend as Hoist -import ../../src/rules/suspicious/index.bend as Index -import ../../src/rules/suspicious/ring.bend as Ring -import ../../src/rules/suspicious/table.bend as Table -import ../../src/rules/suspicious/unit.bend as UnitRule +import ../../src/rules/pedantic/rewalk.bend as Rewalk +import ../../src/rules/pedantic/strict.bend as Strict +import ../../src/rules/pedantic/index.bend as Index +import ../../src/rules/table.bend as Table import ../../src/rules/suspicious/thunk.bend as Thunk import ../../laws/ExemptPathsYieldNoFindings.bend as L1 import ../../laws/RoleLawFileNamed.bend as L2 @@ -261,30 +258,6 @@ def ex.strict(ds, path, n, e): def L1.strict_exempt(path, _text, _toks, tree, _bound, _items, e): ex.strict(Calls.defs(tree), path, 0n, e) -# hoist's def walk gathers nothing in a law file -law ex.hoist: - for ds: List<&2, Calls.Def> - for +wide: List<&2, String> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {Hoist.check.go(ds, wide, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.hoist(ds, wide, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => Hoist.walk(body, name, wide, Hoist.carried(sig, body, name), path, True{}), n, - v => Hoist.check.go(rest, wide, path, v), ex.hoist(rest, wide, path, 1n+n, e)) - -def L1.hoist_exempt(path, _text, _toks, tree, _bound, _items, e): - +ds = Calls.defs(tree) - ex.hoist(ds, Hoist.wide_defs(ds, Nil{}), path, 0n, e) - # index's def walk gathers nothing in a law file law ex.index: for ds: List<&2, Calls.Def> @@ -309,74 +282,6 @@ def L1.index_exempt(path, _text, _toks, tree, _bound, _items, e): +ds = Calls.defs(tree) ex.index(ds, Table.modules(ds, Nil{}), path, 0n, e) -# ring's def walk gathers nothing in a law file -law ex.ring: - for ds: List<&2, Calls.Def> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {Ring.check.go(ds, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.ring(ds, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => Ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body)), n, - v => Ring.check.go(rest, path, v), ex.ring(rest, path, 1n+n, e)) - -def L1.ring_exempt(path, _text, _toks, tree, _bound, _items, e): - ex.ring(Calls.defs(tree), path, 0n, e) - -# table's def walk gathers nothing in a law file -law ex.table: - for ds: List<&2, Calls.Def> - for +mods: List<&2, String> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {Table.check.go(ds, mods, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.table(ds, mods, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => Table.walk(body, path, Table.scope(body, mods)), n, - v => Table.check.go(rest, mods, path, v), ex.table(rest, mods, path, 1n+n, e)) - -def L1.table_exempt(path, _text, _toks, tree, _bound, _items, e): - +ds = Calls.defs(tree) - ex.table(ds, Table.modules(ds, Nil{}), path, 0n, e) - -# unit's def walk gathers nothing in a law file -law ex.unit: - for ds: List<&2, Calls.Def> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {UnitRule.check.go(ds, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.unit(ds, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => UnitRule.walk(body, None{}, name, path, True{}), n, - v => UnitRule.check.go(rest, path, v), ex.unit(rest, path, 1n+n, e)) - -def L1.unit_exempt(path, _text, _toks, tree, _bound, _items, e): - ex.unit(Calls.defs(tree), path, 0n, e) - # thunk's def walk gathers nothing in a law file law ex.thunk: for ds: List<&2, Calls.Def> diff --git a/proofs/lemmas/PackageRootLookup.bend b/proofs/lemmas/PackageRootLookup.bend index 253b836..3886091 100644 --- a/proofs/lemmas/PackageRootLookup.bend +++ b/proofs/lemmas/PackageRootLookup.bend @@ -6,7 +6,6 @@ import ../../src/rules.bend as Rules import ../../src/role.bend as Role import ../../src/rules/digest.bend as Digest import ../../src/rules/laws/law.bend as Law -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../laws/PackagesScoped.bend as L1 import ../../laws/ScopePackageRootsFound.bend as L2 import ../../laws/defs/ScopeWalkFiles.bend as D1 @@ -89,22 +88,22 @@ def pk.members(ds, rs, pkg): match ds: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +unsafes, +laws}, + case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +laws}, +tl}: a = L2.package_nearest(rs, dir) %a : {Bool.pick(List<&2, Digest.Digest>, String.eq(Rules.owner(rs, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> Rules.members(tl, rs, pkg), Rules.members(tl, rs, pkg)) == Bool.pick(List<&2, Digest.Digest>, String.eq(_, pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> D1.part(tl, rs, pkg), D1.part(tl, rs, pkg)) : List<&2, Digest.Digest>} e = pk.members(tl, rs, pkg) %e : {Bool.pick(List<&2, Digest.Digest>, String.eq(Rules.owner(rs, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> Rules.members(tl, rs, pkg), Rules.members(tl, rs, pkg)) == Bool.pick(List<&2, Digest.Digest>, String.eq(Rules.owner(rs, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> _, _) + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> _, _) : List<&2, Digest.Digest>} {==} @@ -130,11 +129,11 @@ def pk.covers(ps, ds, rs): def L1.packages_scoped(srcs, rs): +ds = Digest.all.role(Role.marks(srcs), srcs) a = Equal.cong(List<&2, Digest.Digest>, List<&2, F.Finding>, - mm => List.concat(&2, F.Finding, [Law.check(mm), Rules.covers(rs, ds, rs), Unsafe.check(ds)]), + mm => List.concat(&2, F.Finding, [Law.check(mm), Rules.covers(rs, ds, rs)]), Rules.members(ds, rs, ""), D1.part(ds, rs, ""), pk.members(ds, rs, "")) b = Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - cc => List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), cc, Unsafe.check(ds)]), + cc => List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), cc]), Rules.covers(rs, ds, rs), D1.covered(rs, ds, rs), pk.covers(rs, ds, rs)) Equal.trans(List<&2, F.Finding>, Rules.project(srcs, rs), - List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), Rules.covers(rs, ds, rs), Unsafe.check(ds)]), + List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), Rules.covers(rs, ds, rs)]), D1.packed(srcs, rs), a, b) diff --git a/proofs/lemmas/PlannerPerFileFindings.bend b/proofs/lemmas/PlannerPerFileFindings.bend index bbe9592..be6290f 100644 --- a/proofs/lemmas/PlannerPerFileFindings.bend +++ b/proofs/lemmas/PlannerPerFileFindings.bend @@ -123,33 +123,43 @@ def L3.scope_findings(world): M3.lint.app_nil(F.Finding, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world))))) %n : {List.append(&2, F.Finding, Plan.unread(Plan.gots(Plan.inputs(world), world)), - List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} a = Equal.sym(List<&2, F.Finding>, Plan.unread(Plan.gots(Plan.inputs(world), world)), D1.unread_all(world, Plan.inputs(world)), lint.unread(Plan.inputs(world), world)) %a : {List.append(&2, F.Finding, _, - List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} - b = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), - D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), - lint.on(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), Plan.inputs(world), world)) + b = Equal.sym(List<&2, F.Finding>, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + D1.on_all(Plan.marks.run(W.paths.of(world), world, Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + world, Plan.inputs(world)), + lint.on(Plan.marks.run(W.paths.of(world), world, Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.inputs(world), world)) %b : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), List.append(&2, F.Finding, _, List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} t = Equal.sym(List<&2, F.Finding>, Plan.settings(world), D1.settings_found(world), L4.setting_reports(world)) %t : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, _, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} c = Equal.sym(List<&2, Src.Src>, Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), D1.parsed_all(world, Plan.inputs(world)), lint.parsed(Plan.inputs(world), world)) %c : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(_, Plan.packages(Plan.roots(world)))))) @@ -157,7 +167,8 @@ def L3.scope_findings(world): k = Equal.sym(List<&2, String>, Plan.packages(Plan.roots(world)), D1.pkgs(Plan.roots(world)), L1.package_dirs(Plan.roots(world))) %k : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(D1.parsed_all(world, Plan.inputs(world)), _)))) @@ -167,13 +178,15 @@ def L3.scope_findings(world): D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world))), L2.packages_scoped(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %q : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} z = M3.lint.app_nil(F.Finding, D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %z : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} diff --git a/src/README.md b/src/README.md index e4b3d0b..c3654dd 100644 --- a/src/README.md +++ b/src/README.md @@ -48,13 +48,14 @@ def space() -> String: A group sets every rule in it; a rule set by name wins over its group; an unset group has its default. The groups: -| group | rules | default | -|---------------|----------------------------------------------------------------------------------|---------| -| `correctness` | `hole` `pick` `put` `arms` `escape` `twice` `strings` `chars` `foreign` `setting` | error | -| `suspicious` | `unused` `strict` `eager` `concat` `fuel` `index` `table` `hoist` `ring` `rewalk` `unit` `fromrev` `prefix` (`scan`, `thunk`: opt-in) | warn | -| `style` | `doc` `space` `wrap` `param` `noqa` (`transcong`: opt-in) | warn | -| `laws` | `coverage` `closed` `unsafe` | warn | -| `pedantic` | `tail` | off | +| group | rules | default | +|---------------|------------------------------------------------------------------------|---------| +| `correctness` | `hole` `pick` `put` `arms` `escape` `twice` `setting` | error | +| `suspicious` | `unused` `eager` `concat` `fromrev` `prefix` (`thunk`: opt-in) | warn | +| `perf` | `strings` `chars` | warn | +| `style` | `doc` `space` `wrap` `param` `noqa` (`transcong`: opt-in) | warn | +| `laws` | (`coverage`, `closed`: opt-in) | warn | +| `pedantic` | `foreign` `strict` `fuel` `index` `rewalk` `tail` | off | The stable codes, assigned once (do not renumber): @@ -65,20 +66,26 @@ The stable codes, assigned once (do not renumber): | C003 | `pick` | U003 | `eager` | S003 | `wrap` | | C004 | `put` | U004 | `concat` | S004 | `param` | | C005 | `arms` | U005 | retired | S005 | `noqa` | -| C006 | `escape` | U006 | `fuel` | L001 | `coverage` | -| C007 | `twice` | U007 | `index` | L002 | `closed` | -| C008 | `strings` | U008 | `table` | L003 | `unsafe` | -| C009 | `chars` | U009 | `hoist` | L004 | retired | -| C010 | `foreign` | U010 | `ring` | L005 | retired | -| C011 | `setting` | U011 | `rewalk` | P001 | `tail` | -| | | U012 | `unit` | S006 | `transcong` | -| | | U013 | `scan` | | | +| C006 | `escape` | U006 | `fuel` | S006 | `transcong` | +| C007 | `twice` | U007 | `index` | L001 | `coverage` | +| C008 | `strings` | U008 | retired | L002 | `closed` | +| C009 | `chars` | U009 | retired | L003 | retired | +| C010 | `foreign` | U010 | retired | L004 | retired | +| C011 | `setting` | U011 | `rewalk` | L005 | retired | +| | | U012 | retired | P001 | `tail` | +| | | U013 | retired | | | | | | U014 | retired | | | | | | U015 | `thunk` | | | | | | U016 | `fromrev` | | | | | | U017 | `prefix` | | | -Letters: `C` correctness, `U` suspicious, `S` style, `L` laws, `P` pedantic. +Letters: `C` correctness, `U` suspicious, `S` style, `L` laws, `P` pedantic, +for the group a rule had when its code was assigned. A code names the rule, +not its group, so a rule that moves keeps it: `strings` and `chars` (C008, +C009) are `perf`, what is slow but never wrong; `foreign` (C010), `strict` +(U002), `fuel` (U006), `index` (U007) and `rewalk` (U011) are `pedantic`, +where too few of their findings were defects, or their fix was slower, to +report by default. `pedantic` is advice that is noisy on idiomatic code: off until a project asks for it. L004 was `quantify`, the opt-in strict mode of `closed`; @@ -87,14 +94,24 @@ a check on `IO.args()` readers, retired before its first release: every reader already drops the program, and it could not tell one that does from one that does not. U014 is never reused either. L005 was `trace`, which held a SPEC.md table (now docs/SPEC.md) and the laws' ID tags to each other; a guarantee is -named by its law file now, and L005 is never reused. `scan` and `thunk` are -opt-in: they are in `suspicious`, but no group setting reaches them; only -`def scan()` or `def thunk()` turns each on, and `transcong`, in `style`, only by -`def transcong()`. An unknown level word grades as an error, so a typo shows. A `bolt.bend` is +named by its law file now, and L005 is never reused. U008 `table`, U009 +`hoist`, U010 `ring`, U012 `unit` and U013 `scan` were retired when an audit +of public Bend found none or few of their findings real (`index` still +leaves a get on a fixed table alone, src/rules/table.bend). L003 `unsafe` +was retired since bend 2.0.32 itself fails a proof that reaches an +`@unsafe` or foreign def. None of these codes is reused. A bolt.bend that +still sets one of them gets a `setting` finding (C011); delete the def. + +`thunk` and `transcong` are opt-in: they are in their groups, but no group +setting reaches them; only `def thunk()` or `def transcong()` turns each on. +So are `coverage` and `closed`: what every def must be named by a law, and +whether a law with no binder is allowed, are a project's policy, not a +defect, so a project that keeps laws turns them on by name (`def +coverage() -> String: "warn"`). An unknown level word grades as an error, so a typo shows. A `bolt.bend` is read, never linted. A setting whose name is no rule's slug and no group sets nothing, so `setting` (C011) reports it. Without one, the defaults apply. This repo's -[bolt.bend](../bolt.bend) sets every group to error: the gate must see -`clean`. +[bolt.bend](../bolt.bend) sets every group to error and turns the opt-in +laws rules on: the gate must see `clean`. ## Suppressing a finding @@ -110,7 +127,7 @@ commas (spaces around them allowed), then any text. Codes are the stable ids above, case-sensitive, and a comment may name several (`# noqa: U002, U003`). Only a real comment counts, as the lexer reads it: `noqa` inside a string literal is text. The filter runs once every rule has -run, the project rules (`coverage`, `unsafe`) included, and a +run, the project rule (`coverage`) included, and a silenced finding is neither printed nor counted, so it changes neither the summary nor the exit code. No rule reads the comments: what each rule finds is the same with or without them. The editor applies the same filter. @@ -136,8 +153,7 @@ digests (`rules.bend`'s `project`). The project rules run per package (laws/PackagesScoped.bend). Over the whole tree, a package root is `.` and every other directory the walk reads that holds a `main.bend`, and a file belongs to the nearest root at or above -it. `coverage` runs on one package's files at a time; `unsafe` runs on every -file. Over files named on the line, `.` is the only package. So a repo with +it. `coverage` runs on one package's files at a time. Over files named on the line, `.` is the only package. So a repo with nested packages, each with its own laws, lints in one run from the top, and a law covers only defs of its own package. @@ -149,6 +165,19 @@ reaches, is code, even when a law imports it to state laws about it. So a `LAWS.bend` of nothing but `import ./laws/.bend` lines makes those files law files, and a proof may state lemma laws of its own. +The walk is over the whole tree under the working directory, even over files +named on the line: bolt reads every `.bend` file there for its imports +(lexed, never parsed), and each named file the walk does not find (one +outside the working directory), each file once, and lints only the files +named. A path is matched to its file however it is spelled (`a.bend`, +`./a.bend`, or absolute under the working directory). So a file named beside +a `LAWS.bend` that imports it is still code when a `main.bend` the run does +not name imports it, and a file under `laws/` named without its `LAWS.bend` +is still a law file. The editor reads the same graph as `bolt ` when a +document is opened or saved, from the directory it runs in, and keeps those +roles for the edits in between (laws/LspKeepsRoleMarks.bend), so a change +that adds or drops an import changes a file's role at the next save. + A rule is handed a [`Src`](src.bend), not the text: the path, the text, and the file's tokens, tree, binder and outline, each read once for the whole set. A tree parse of a 156 KB file costs 1.5 s here, so twenty rules that @@ -158,12 +187,11 @@ its phases use the whole list several times, and a `+` reuse copies what it is given, so handing them the parsed `Src` duplicated every tree (100 files cost 362 s, growing faster than the count; 28 s now). The digest carries only what those rules ask: the paths, the top-level defs and types, what the -laws name, the imports and the `@unsafe` defs. +laws name, the imports and the laws. What the rules share, `rules/calls.bend` (the recursion rules), `rules/tokens.bend`, `rules/imports.bend` and `rules/digest.bend`, sits beside the groups. The cost rules built on `rules/calls.bend` (`pick`, -`strict`, `eager`, `tail`, `concat`, `index`, `table`, `hoist`, `ring`, -`rewalk`, `unit`, `scan`, `thunk`) skip what never runs: a law file, a proof file, and a def +`strict`, `eager`, `tail`, `concat`, `index`, `rewalk`, `thunk`) skip what never runs: a law file, a proof file, and a def that is a proof wherever it is, one that returns a proof (`-> {a == b : T}`) or one written with no type at all (`def f(x, y):`, no `:` among its parameters and no `->`), which is how Bend fills the law named `f`. @@ -245,7 +273,7 @@ parameters and no `->`), which is how Bend fills the law named `f`. thunk (`Lazy.stop(T, hit, x, _u => go(rest))`) stops early but leaves the loop on every step, and measured slower. Law files, proof files and defs that are proofs (they return a proof, or have no type) are exempt. -- `strict` — a self-call inside `Bool.and`/`Bool.or`, or either side of +- `strict` (pedantic) — a self-call inside `Bool.and`/`Bool.or`, or either side of `&&`/`||`, matched by exactly those texts (a qualified `Base.Bool.or` is not seen). They are functions too: both sides always run, so there is no short-circuit (a game's overlap test went 31 -> 55 fps once the call moved @@ -285,61 +313,20 @@ parameters and no `->`), which is how Bend fills the law named `f`. alone (`path ++ "/" ++ name` builds a value used elsewhere, not an accumulator). Prepend each chunk and reverse once at the end (ez's slurp, native: 14.75 s -> 0.36 s at 16 MB). -- `index` — `List.get`/`String.get` at a computed index inside a def that +- `index` (pedantic) — `List.get`/`String.get` at a computed index inside a def that calls itself: the list is walked again each step. Walk the cells instead (one sort phase went 39 s -> 0.9 s). A get anywhere in the def is reported, one in a base arm that runs once included. A literal index of - any size is exempt. A `List.get` on a fixed table is `table`'s; a - `String.get` always stays here. A get that is the def's own self-call (the + any size is exempt, and so is a `List.get` on a fixed table (a literal, a + sized array, or `List.replicate` / `Array.new` / `List.range` with a + constant count: src/rules/table.bend); a `String.get` always stays here. A get that is the def's own self-call (the step of a def named `List.get` or `String.get`, as Base's are) is exempt. -- `table` — `List.get` or `List.set` at a computed index inside a def that - calls itself, when the list is a fixed table (a literal, a sized array, or - `List.replicate` / `Array.new` / `List.range` with a constant count), - inline, as a table def of the file, or held by the let of that name in - scope. A let reaches the statements after it, not a sibling case arm, and a - later let of the name to anything else ends it. Keep it in an `Array`. A - literal index, a growing list, and a data-dependent length stay with - `index` or stay quiet, so one call is one finding. -- `hoist` — a list or array of more than eight constants, or a call that - builds one from inputs that do not change, sits inside a def that calls - itself and is then indexed. The build runs again on every step. Build it - once, outside the recursion. A call builds one when it is a Base - constructor (`List.replicate`, `Array.new`, ...) or a def of the file whose - body is itself a fixed table of more than eight cells; a def whose body is - not in the file does not count. Eight cells or fewer, and a build whose - arguments depend on the step, are left alone. -- `ring` — a self-call replaces a binder with a drop of a constant count and - an append (`List.drop` / `List.tail` / `String.drop` / `String.tail`, then - `List.append` / `String.append` / `++`), passed back in that binder's own - parameter position. Each step copies the window. Keep it in an `Array` and - advance an index. A list the def matches as input (any scrutinee of a - match), a window dropped into another parameter's slot, and a one-shot trim - are left alone. -- `rewalk` — one straight piece of a def calls the same walk twice on the +- `rewalk` (pedantic) — one straight piece of a def calls the same walk twice on the same argument, and one result is used only for a single value (one index, one field, or a let read only that way) while the other result is kept whole. Take the value from that other result. A let of a name the argument uses between the two calls makes them different walks, and so does a different case arm. -- `unit` — a multiply or divide by the literal `1`, `1n` or `1.0` on a - step that recurses, either as `*` / `/` or as `Nat.mul` / `U32.mul` / - `F32.mul` (and `.div`, only when the divisor is one). Drop the operation. - Any other factor or divisor is left alone, and so is a base case: a case - arm that does not call the def. Only a `case` arm can be a base case, so a - `Bool.pick` branch beside a self-call is still the step, and so is a lambda - body inside it. One finding per operation: `Nat.mul(1n, 1n)` is one. -- `scan` (opt-in) — a def that calls itself and, on the same step, hands a - parameter it carries unchanged (every self-call passes it back as the same - lone name in its own position) to a walk: a Base list search - (`List.contains`, `List.find`, `List.filter`, `List.length`, `List.any`, - `List.all`, `List.foldl`, `List.foldr`), or a def of the file that walks - that argument (the one it shrinks, unless a `Nat` or a `String`, or one it - passes to a walk). Every step walks the list again, O(|A| * |B|). Index it - once outside the loop, or merge two sorted walks. A literal table or an - expression in that argument, a lambda's body, a case arm that does not - recurse, a Base walk that rebuilds the list (`List.map`, `List.append`), - and a def of another module are left alone. One finding per call, on its - callee. - `thunk` (opt-in) — a lambda whose body is exactly a self-call and whose parameter the call does not read, passed as the one lambda of a call: a `Unit -> T` thunk such as `Lazy.or_else(hit, _u => go(rest, k))`. The @@ -403,12 +390,12 @@ parameters and no `->`), which is how Bend fills the law named `f`. `Map.put` (`def Map.put`, Base's own source) is exempt. - `escape` — `\0` then a digit in a literal (`"\033"`). Bend has no octal escape: that is NUL followed by the digits. Write `\u{1B}`. -- `strings` — a `match` over string-literal arms totalling more than 64 +- `strings` (perf) — a `match` over string-literal arms totalling more than 64 characters: compile time and memory blow up with the characters (45 chars cost 0.8 s and 0.35 GB here, 480 chars 12 s and 4.8 GB). Map the string to a sum type once. Only the first match column is read, and a literal with no closing quote is not counted. -- `chars` — a `match` with more than eight character-literal arms +- `chars` (perf) — a `match` with more than eight character-literal arms (`case '.':`). `Char` is `Chr{code: U32}`, so each arm is a U32 literal inside a constructor pattern, and the C backend pays about 90 MB for one (eighteen arms cost 1.57 GB and 8.3 s here, 0.10 GB and 0.7 s once @@ -428,7 +415,7 @@ parameters and no `->`), which is how Bend fills the law named `f`. - `arms` — a later `Nat` arm that an earlier `kn+p` already matches, so it is unreachable. `Succ{p}` and `Succ{_}` count as `1n+p`. The checker takes it silently and the answer is wrong: put the narrow arms first. -- `foreign` — a foreign def with a `.c` body and no `.js` body, or the +- `foreign` (pedantic) — a foreign def with a `.c` body and no `.js` body, or the reverse: the missing lane cannot run it. A file headed `# lanes: native` needs no `.js`: that exact line must be one of the comment lines before the file's first non-comment line. @@ -437,11 +424,11 @@ parameters and no `->`), which is how Bend fills the law named `f`. or `shadow`): grading only looks names up, so it sets nothing, and the typo fails open. It is reported once for each bolt.bend that grades a file of the run, at that bolt.bend's path and the setting's `def` line, - after the per-file findings and before `coverage` and `unsafe`. + after the per-file findings and before `coverage`'s. It is graded by that bolt.bend like any rule (`def setting() -> String: "off"` there turns it off). A bolt.bend is read, not linted, so a noqa comment in one silences nothing. bolt runs it; the editor does not. -- `fuel` — a `Nat` literal (digits, then `n`), or `U32.to_nat` of a U32 +- `fuel` (pedantic) — a `Nat` literal (digits, then `n`), or `U32.to_nat` of a U32 literal (`U32.to_nat(100000)`), passed in a call, `name(..)`, to a fuel parameter of a def of the same file. A fuel parameter is known by its name alone, the one before its colon: `fuel`, `gas`, `steps` or `budget`, or @@ -462,21 +449,11 @@ parameters and no `->`), which is how Bend fills the law named `f`. ~64K elements elsewhere). Carry an accumulator. The test is on that parameter's type alone, never on whether the self-call shrinks it. Everything inside a `Bool.pick(..)` is skipped. -- `closed` — a law in a law file with no `for`/`exs` binder. A closed law, +- `closed` (opt-in) — a law in a law file with no `for`/`exs` binder. A closed law, an equality (`{lhs == rhs : T}`, including `IO(T)`) or not, holds for the one input it names: a unit test the checker runs, not a guarantee. Nothing exempts one: quantify it, or delete it. -- `unsafe` (project) — an `@unsafe def` or a foreign def (a body of only - `import "./x.c"` / `import "./x.js"` lines) that a LAWS.bend or PROOF.bend - reaches through its relative imports, over the files bolt read. Since bend - 2.0.32 such a proof fails: bend prints `SOME PROOFS FAIL`, then `Error: N - defs rely on unsafe or foreign code:` and a `- name` list, and exits 1 - (2.0.34), every def of every imported module counted. That list names the - defs, not the import that brought the effect in; the finding names the - import path from the law file (`LAWS.bend -> mid.bend -> eff/io.bend`). Move - the effect into a sibling module no law file imports and pass it in as a - service. Hash imports (`import 0x.../path`) are not followed. -- `coverage` (project) — in a package that states laws (a LAWS.bend among its +- `coverage` (project, opt-in) — in a package that states laws (a LAWS.bend among its files), a def or a type that no law of the package reaches. It is `coverage`, not `law`, because `law` is a Bend keyword: `def law()` is no def, so a bolt.bend could never set it by name. IO is no exemption: a def that @@ -532,6 +509,11 @@ case, but its absence is not held against a rule: most Bend code is written with agents, and a shape nobody's agent flags stays unfixed whether or not it is a defect. +A guard is the one exception to **Seen**: a rule whose shape is rare but +whose miss is a silently wrong result that `bend` never reports (`arms`, +`escape`, `setting`) may ship on fewer sites, as long as it is **Precise**, +its fix is legal, and it is **New**. + A rule is on by default only at near-zero false positives; anything less certain starts opt-in or in `pedantic`. A shipped rule that projects often `# noqa` is demoted or reworked. diff --git a/src/codes.bend b/src/codes.bend index a6fbd84..4892858 100644 --- a/src/codes.bend +++ b/src/codes.bend @@ -4,7 +4,13 @@ # of src/README.md: do not renumber a rule, and do not reuse a retired code # (L004 was `quantify`, now `closed` itself; C001 was `shadow` and U005 was # `nat`, whose failures bend 2.0.25 no longer has; U014 was `argv`, retired -# before its first release, since every reader already drops the program). +# before its first release, since every reader already drops the program; +# U008 `table`, U009 `hoist`, U010 `ring`, U012 `unit` and U013 `scan` were +# retired for want of real hits, and L003 `unsafe` since bend 2.0.32 reports +# an unsafe def a proof reaches itself). A code names the rule, not its +# group: a rule that moves keeps its code (`strings` and `chars` are `perf`, +# `foreign` and the cost rules `fuel`, `index`, `strict` and `rewalk` are +# `pedantic`). # # No law can say this, since it is about versions: a released code is never # renumbered or reused, which review of this table keeps. @@ -24,25 +30,15 @@ def table() -> List<&2, Entry>: Entry{"arms", "C005", "correctness"}, Entry{"escape", "C006", "correctness"}, Entry{"twice", "C007", "correctness"}, - Entry{"strings", "C008", "correctness"}, - Entry{"chars", "C009", "correctness"}, - Entry{"foreign", "C010", "correctness"}, Entry{"setting", "C011", "correctness"}, Entry{"unused", "U001", "suspicious"}, - Entry{"strict", "U002", "suspicious"}, Entry{"eager", "U003", "suspicious"}, Entry{"concat", "U004", "suspicious"}, - Entry{"fuel", "U006", "suspicious"}, - Entry{"index", "U007", "suspicious"}, - Entry{"table", "U008", "suspicious"}, - Entry{"hoist", "U009", "suspicious"}, - Entry{"ring", "U010", "suspicious"}, - Entry{"rewalk", "U011", "suspicious"}, - Entry{"unit", "U012", "suspicious"}, - Entry{"scan", "U013", "suspicious"}, Entry{"thunk", "U015", "suspicious"}, Entry{"fromrev", "U016", "suspicious"}, Entry{"prefix", "U017", "suspicious"}, + Entry{"strings", "C008", "perf"}, + Entry{"chars", "C009", "perf"}, Entry{"doc", "S001", "style"}, Entry{"space", "S002", "style"}, Entry{"wrap", "S003", "style"}, @@ -51,7 +47,11 @@ def table() -> List<&2, Entry>: Entry{"transcong", "S006", "style"}, Entry{"coverage", "L001", "laws"}, Entry{"closed", "L002", "laws"}, - Entry{"unsafe", "L003", "laws"}, + Entry{"foreign", "C010", "pedantic"}, + Entry{"strict", "U002", "pedantic"}, + Entry{"fuel", "U006", "pedantic"}, + Entry{"index", "U007", "pedantic"}, + Entry{"rewalk", "U011", "pedantic"}, Entry{"tail", "P001", "pedantic"}] # this row is the slug diff --git a/src/config.bend b/src/config.bend index c80cffc..281b123 100644 --- a/src/config.bend +++ b/src/config.bend @@ -283,9 +283,10 @@ def default(+group: String) -> Level: # a rule only its own setting turns on: a group setting, or the group's # default, would put new findings on projects that never asked for it -# (`thunk`, `scan` and `transcong`) +# (`thunk`, `transcong`, and the laws' `coverage` and `closed`, which are a +# project's policy rather than defects) def opt_in(+rule: String) -> Bool: - List.contains(~String, ~String.eq, ["thunk", "scan", "transcong"], rule) + List.contains(~String, ~String.eq, ["thunk", "transcong", "coverage", "closed"], rule) # the rule's own setting, else its group's, else the group's default; an # opt-in rule's own setting, else off diff --git a/src/lint/plan.bend b/src/lint/plan.bend index f31b9dc..2290abf 100644 --- a/src/lint/plan.bend +++ b/src/lint/plan.bend @@ -166,6 +166,40 @@ def sources(ps: List<&2, String>) -> List<&2, String>: def inputs(+world: W.World) -> List<&2, String>: sources(files(world)) +# a path as the one file it names wherever it is spelled from: resolved +# against the working directory when relative, then normalized +# (src/rules/imports.bend), so `a.bend`, `./a.bend` and `/w/a.bend` run +# from `/w` are one +def abs(+cwd: String, +path: String) -> String: + Imports.norm(Bool.pick(String, String.starts_with(path, "/"), path, cwd ++ "/" ++ path)) + +# each path resolved (abs) +def norms(ps: List<&2, String>, +cwd: String) -> List<&2, String>: + match ps: + case Nil{}: + Nil{} + case Con{p, rest}: + abs(cwd, p) <> norms(rest, cwd) + +# the paths that resolve to none of the paths seen +def unseen(ps: List<&2, String>, +cwd: String, +seen: List<&2, String>) -> List<&2, String>: + match ps: + case Nil{}: + Nil{} + case Con{+p, rest}: + +more = unseen(rest, cwd, seen) + Bool.pick(List<&2, String>, Imports.has(seen, abs(cwd, p)), more, p <> more) + +# the files a run reads its roles from: every source the walk of the tree +# finds, then each source of the run the walk does not find (one named +# outside the working directory), so each file is read once. A file's role +# follows the imports, and the file that makes a named file code, a +# package's main.bend that imports it, may be named or not +def graph(+world: W.World) -> List<&2, String>: + +found = sources(walk.found(tree(world))) + +cwd = W.cwd.of(world) + List.append(&2, String, found, unseen(inputs(world), cwd, norms(found, cwd))) + # --------------------------------------------------------------------------- # what the plan reads @@ -266,24 +300,17 @@ def asks.texts(ps: List<&2, String>) -> List<&2, W.Ask>: case Con{p, rest}: W.Contents{p} <> asks.texts(rest) -# the directories the walk lists: every one it reads, none when files were -# named -def lists.named(paths: List<&2, String>, +world: W.World) -> List<&2, W.Ask>: - match paths: - case Nil{}: - asks.entries(walk.read(tree(world))) - case Con{_p, _rest}: - Nil{} - -# the listings the plan reads +# the listings the plan reads: every directory the walk of the tree reads, +# whether files were named or not, since a named file's role is read from the +# imports of the whole tree (graph) def lists(+world: W.World) -> List<&2, W.Ask>: - lists.named(W.paths.of(world), world) + asks.entries(walk.read(tree(world))) -# the texts the plan reads: each source and every bolt.bend candidate of their -# directories, each once +# the texts the plan reads: each source, every file of the graph, and every +# bolt.bend candidate of the sources' directories, each once def texts(+world: W.World) -> List<&2, W.Ask>: +ps = inputs(world) - asks.texts(uniq(List.concat(&2, String, [ps, conf.cands(conf.dirs(ps), world)]), + asks.texts(uniq(List.concat(&2, String, [ps, graph(world), conf.cands(conf.dirs(ps), world)]), [])) # every question the plan reads the World through: the listings, then the @@ -359,10 +386,43 @@ def per_file.role(+ms: List<&2, Role.Mark>, ss: List<&2, Src.Src>) -> List<&2, F List.concat(&2, F.Finding, [Rules.on.role(Role.of(ms, path), Src.Src{path, text, toks, tree, bound, items}), per_file.role(ms, rest)]) -# every source's findings from the per-file rules: a file a LAWS.bend or a -# PROOF.bend imports is a law or proof file to them as those are -def per_file(+ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - per_file.role(Role.marks(ss), ss) +# the facts (src/role.bend) of each file the World read, from its text, each +# at its path resolved against the working directory (abs) +def facts.of(gs: List<&2, Got>, +cwd: String) -> List<&2, Role.Fact>: + match gs: + case Nil{}: + Nil{} + case Con{Got{_path, None{}}, rest}: + facts.of(rest, cwd) + case Con{Got{path, Some{text}}, rest}: + Role.fact.text(abs(cwd, path), text) <> facts.of(rest, cwd) + +# each path's mark as the path is spelled: its role among marks taken over +# resolved paths (abs), neither when it has none there +def marks.key(ps: List<&2, String>, +cwd: String, +ms: List<&2, Role.Mark>) -> List<&2, Role.Mark>: + match ps: + case Nil{}: + Nil{} + case Con{+p, rest}: + Role.Mark{Imports.norm(p), Role.of.norm(ms, abs(cwd, p))} <> marks.key(rest, cwd, ms) + +# the marks of a run over files named on the line: every file of the graph +# read for its imports, the walk taken over their resolved paths, and each +# named file's role looked up there as it is named +def marks.named(+world: W.World) -> List<&2, Role.Mark>: + +cwd = W.cwd.of(world) + marks.key(inputs(world), cwd, Role.marks.facts(facts.of(gots(graph(world), world), cwd))) + +# the marks a run's roles are read from: over the whole tree, the sources' +# own; over files named on the line, the graph's, every file of the tree +# read for its imports, so the main.bend that imports a named file makes it +# code even when it is not named +def marks.run(paths: List<&2, String>, +world: W.World, +ss: List<&2, Src.Src>) -> List<&2, Role.Mark>: + match paths: + case Nil{}: + Role.marks(ss) + case Con{_p, _rest}: + marks.named(world) # the path of the first candidate the World holds a text for, as a list of # one; none when it holds none @@ -409,7 +469,7 @@ def settings(+world: W.World) -> List<&2, F.Finding>: # then `setting`'s over each bolt.bend grading reads, then the project rules' # over every source and the other packages def found.at(+world: W.World, +gs: List<&2, Got>, +ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - List.concat(&2, F.Finding, [unread(gs), per_file(ss), settings(world), + List.concat(&2, F.Finding, [unread(gs), per_file.role(marks.run(W.paths.of(world), world, ss), ss), settings(world), Rules.project(ss, packages(roots(world)))]) # every finding of every rule, ungraded diff --git a/src/lsp/docs.bend b/src/lsp/docs.bend index 06d22e2..4c14269 100644 --- a/src/lsp/docs.bend +++ b/src/lsp/docs.bend @@ -4,41 +4,56 @@ import Base import ../lazy/lazy.bend as Lazy import ./report.bend as Rep +import ../role.bend as Role # an open document: its URI, the text the editor shows, the checker's errors -# from its last run +# from its last run, and the role marks read with them type Doc is Data: - Doc{uri: String, text: String, errors: List<&2, Rep.Diag>} + Doc{uri: String, text: String, errors: List<&2, Rep.Diag>, marks: List<&2, Role.Mark>} # the documents without the one at a URI def del(docs: List<&2, Doc>, +uri: String) -> List<&2, Doc>: match docs: case Nil{}: Nil{} - case Con{Doc{+u, t, e}, r}: + case Con{Doc{+u, t, e, m}, r}: +rest = del(r, uri) - Bool.pick(List<&2, Doc>, String.eq(u, uri), rest, Doc{u, t, e} <> rest) + Bool.pick(List<&2, Doc>, String.eq(u, uri), rest, Doc{u, t, e, m} <> rest) # the checker's errors for a document, none when it is not open def errors(docs: List<&2, Doc>, +uri: String) -> List<&2, Rep.Diag>: match docs: case Nil{}: Nil{} - case Con{Doc{u, t, e}, r}: + case Con{Doc{u, t, e, m}, r}: Lazy.stop(List<&2, Rep.Diag>, String.eq(u, uri), e, _u => errors(r, uri)) -# a document's text set (opened, or changed); its errors stay +# the role marks kept for a document, none when it is not open +def marks(docs: List<&2, Doc>, +uri: String) -> List<&2, Role.Mark>: + match docs: + case Nil{}: + Nil{} + case Con{Doc{u, t, e, m}, r}: + Lazy.stop(List<&2, Role.Mark>, String.eq(u, uri), m, _u => marks(r, uri)) + +# a document's text set (opened, or changed); its errors and marks stay def set(+docs: List<&2, Doc>, +uri: String, text: String) -> List<&2, Doc>: - Doc{uri, text, errors(docs, uri)} <> del(docs, uri) + Doc{uri, text, errors(docs, uri), marks(docs, uri)} <> del(docs, uri) # the text of an open document, when it is open def get(docs: List<&2, Doc>, +uri: String) -> Maybe<&2, String>: match docs: case Nil{}: None{} - case Con{Doc{u, t, e}, r}: + case Con{Doc{u, t, e, m}, r}: Lazy.stop(Maybe<&2, String>, String.eq(u, uri), Some{t}, _u => get(r, uri)) -# a document's errors set, from a run of the checker -def set_errors(+docs: List<&2, Doc>, +uri: String, errors: List<&2, Rep.Diag>) -> List<&2, Doc>: - Doc{uri, Maybe.default(&2, String, get(docs, uri), ""), errors} <> del(docs, uri) +# a document's errors set, from a run of the checker, with the marks read +# beside it +def set_checked( + +docs: List<&2, Doc>, + +uri: String, + errors: List<&2, Rep.Diag>, + ms: List<&2, Role.Mark> +) -> List<&2, Doc>: + Doc{uri, Maybe.default(&2, String, get(docs, uri), ""), errors, ms} <> del(docs, uri) diff --git a/src/lsp/files/disk.bend b/src/lsp/files/disk.bend index 3126cfc..4e07986 100644 --- a/src/lsp/files/disk.bend +++ b/src/lsp/files/disk.bend @@ -85,6 +85,10 @@ def base_path(_u: Unit) -> IO(String): # noqa: L001 disk effect def cwd(_u: Unit) -> IO(String): # noqa: L001 disk effect WalkDisk.cwd() +# a directory's entries, as the lint's walk lists them +def entries(dir: String) -> IO(List<&2, String>): # noqa: L001 disk effect + WalkDisk.entries(dir) + # the real file system def new() -> S.Files: # noqa: L001 effect record - S.Files{read, base_path, cwd} + S.Files{read, base_path, cwd, entries} diff --git a/src/lsp/files/service.bend b/src/lsp/files/service.bend index 2467191..e314a44 100644 --- a/src/lsp/files/service.bend +++ b/src/lsp/files/service.bend @@ -1,26 +1,34 @@ -# files: an effect service: a file's text by path, where Base lives, and -# the working directory a relative path is read against. +# files: an effect service: a file's text by path, where Base lives, the +# working directory a relative path is read against, and a directory's +# entries (a sub-directory's name ending in "/", as src/walk/ lists them). import Base -# effect service: a file's text, where Base is, and the working directory +# effect service: a file's text, where Base is, the working directory, and a +# directory's entries type Files is Type: - Files{read: String -> IO(Maybe<&2, String>), base_path: Unit -> IO(String), cwd: Unit -> IO(String)} + Files{read: String -> IO(Maybe<&2, String>), base_path: Unit -> IO(String), cwd: Unit -> IO(String), + entries: String -> IO(List<&2, String>)} # None when the file cannot be read def read(ff: Files, path: String) -> IO(Maybe<&2, String>): - Files{r, _b, _c} = ff + Files{r, _b, _c, _e} = ff r(path) # the path of Base, from a files service def base_path(ff: Files) -> IO(String): - Files{_r, b, _c} = ff + Files{_r, b, _c, _e} = ff b(Unit{}) # the working directory, from the root; "" when it cannot be named def cwd(ff: Files) -> IO(String): # noqa: L001 effect accessor - Files{_r, _b, c} = ff + Files{_r, _b, c, _e} = ff c(Unit{}) +# a directory's entries, none when it cannot be opened +def entries(ff: Files, dir: String) -> IO(List<&2, String>): # noqa: L001 effect accessor + Files{_r, _b, _c, e} = ff + e(dir) + # every path read, in order (a missing file is a None) def read_all(~ff: Files, paths: List<&2, String>) -> IO(List<&2, Maybe<&2, String>>): # noqa: L001 effect accessor match paths: diff --git a/src/lsp/server.bend b/src/lsp/server.bend index 053ac8c..abbf735 100644 --- a/src/lsp/server.bend +++ b/src/lsp/server.bend @@ -3,7 +3,10 @@ # in (transport/, files/, checker/answer.bend's Checker; checker/bend.bend's # service is the real checker). The open documents are the # loop's state. The checker runs on open and save, against the file on disk; -# the linter on every open, change and save, on the text the editor shows. +# the linter on every open, change and save, on the text the editor shows, +# each file told the role the imports of the tree give it, read as `bolt +# ` reads them (src/lint/plan.bend's graph) on open and save and kept +# for the changes between. import Base import 0xa81e161008ecc2f297c0a3065cb9b188/main.bend as Ezjson import 0xa81e161008ecc2f297c0a3065cb9b188/src/value.bend as J @@ -15,6 +18,9 @@ import ../rules.bend as Lint import ../finding.bend as Finding import ../config.bend as Config import ../lint/plan.bend as Plan +import ../lint/world.bend as W +import ../role.bend as Role +import ../src.bend as Src import ../noqa.bend as Noqa import ./transport/service.bend as T import ./checker/answer.bend as C @@ -223,16 +229,27 @@ def pub.note(cc: Enc.Cols, uri: String, ds: List<&2, Rep.Diag>, gs: List<&2, Fin def pub.cands(cwd: String, path: String) -> List<&2, String>: Config.chain(Config.home(cwd, Config.dir_of(path))) -# the linter's findings on the text the editor shows, at the levels the -# nearest bolt.bend read gives them, less what the text's noqa comments +# the linter's findings on the text the editor shows, each rule told the +# file's role among the marks, at the levels the nearest bolt.bend read gives +# them, less what the text's noqa comments # silence, then the `noqa` rule's, which judges no project rule's code (the # editor runs none); none for a bolt.bend, which is bolt's configuration, # not source -def pub.lint(+path: String, +text: String, rs: List<&2, Maybe<&2, String>>) -> List<&2, Finding.Graded>: +def pub.lint( + +path: String, + +text: String, + rs: List<&2, Maybe<&2, String>>, + ms: List<&2, Role.Mark> +) -> List<&2, Finding.Graded>: +cfg = Config.nearest(rs) Bool.pick(List<&2, Finding.Graded>, Plan.is_config(path), [], - Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Lint.graded(cfg, Lint.all(path, text)), - fs => Lint.graded(cfg, fs))) + Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, + Lint.graded(cfg, Lint.on.role(Role.of(ms, path), Src.of(path, text))), fs => Lint.graded(cfg, fs))) + +# the role marks `bolt ` reads from a World: the imports of every file +# of the tree (Plan.graph), each file read for its imports alone +def pub.marks(+world: W.World) -> List<&2, Role.Mark>: + Plan.marks.named(world) # open or save: the checker's errors with the lint of the text, the errors # kept for the changes after @@ -242,9 +259,11 @@ def pub.checked( +text: String, +ds: List<&2, Rep.Diag>, rs: List<&2, Maybe<&2, String>>, + +ms: List<&2, Role.Mark>, +docs: List<&2, Docs.Doc> ) -> Work: - noted([pub.note(Enc.cols(ee, text), uri, ds, pub.lint(Proto.path(uri), text, rs))], Docs.set_errors(docs, uri, ds)) + noted([pub.note(Enc.cols(ee, text), uri, ds, pub.lint(Proto.path(uri), text, rs, ms))], + Docs.set_checked(docs, uri, ds, ms)) # open: the document's text kept, then published with the checker's errors def pub.open( @@ -253,9 +272,10 @@ def pub.open( +text: String, ds: List<&2, Rep.Diag>, rs: List<&2, Maybe<&2, String>>, + +ms: List<&2, Role.Mark>, +docs: List<&2, Docs.Doc> ) -> Work: - pub.checked(ee, uri, text, ds, rs, Docs.set(docs, uri, text)) + pub.checked(ee, uri, text, ds, rs, ms, Docs.set(docs, uri, text)) # save: the text the editor shows published with the checker's new errors def pub.save( @@ -263,12 +283,13 @@ def pub.save( +uri: String, ds: List<&2, Rep.Diag>, rs: List<&2, Maybe<&2, String>>, + +ms: List<&2, Role.Mark>, +docs: List<&2, Docs.Doc> ) -> Work: - pub.checked(ee, uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, rs, docs) + pub.checked(ee, uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, rs, ms, docs) # change: the new text kept, and its lint published with the checker's last -# errors +# errors and the marks read with them def pub.change( ee: Enc.Enc, +uri: String, @@ -276,8 +297,8 @@ def pub.change( rs: List<&2, Maybe<&2, String>>, +docs: List<&2, Docs.Doc> ) -> Work: - noted([pub.note(Enc.cols(ee, text), uri, Docs.errors(docs, uri), pub.lint(Proto.path(uri), text, rs))], - Docs.set(docs, uri, text)) + noted([pub.note(Enc.cols(ee, text), uri, Docs.errors(docs, uri), + pub.lint(Proto.path(uri), text, rs, Docs.marks(docs, uri)))], Docs.set(docs, uri, text)) # close: the document forgotten, and an empty list published def pub.close(ee: Enc.Enc, +uri: String, docs: List<&2, Docs.Doc>) -> Work: @@ -289,6 +310,76 @@ def pub.configs(~fs: F.Files, +uri: String) -> IO(List<&2, Maybe<&2, String>>): cwd : String <- F.cwd(fs) F.read_all(~fs, pub.cands(cwd, Proto.path(uri))) +# a file's text as an answer +def pub.read.answer(mm: Maybe<&2, String>) -> W.Answer: + match mm: + case None{}: + W.Unread{} + case Some{text}: + W.Read{text} + +# one question of the planner's, answered from the files: a directory's +# entries, or a file's text (a directory reads as no text) +def pub.answer(~fs: F.Files, ask: W.Ask) -> IO(W.Answer): + match ask: + case W.Entries{dir}: + do IO: + es : List<&2, String> <- F.entries(fs, dir) + return W.Listed{es} + case W.Contents{path}: + do IO: + mm : Maybe<&2, String> <- F.read(fs, path) + return pub.read.answer(mm) + +# every question answered, in order +def pub.answers(~fs: F.Files, asks: List<&2, W.Ask>) -> IO(List<&2, W.Reply>): + match asks: + case Nil{}: + IO.pure(List<&2, W.Reply>, []) + case Con{+a, rest}: + do IO>: + r : W.Answer <- pub.answer(~fs, a) + more : List<&2, W.Reply> <- pub.answers(~fs, rest) + return W.Reply{a, r} <> more + +# the World with more answers in it +def pub.world.more(world: W.World, rs: List<&2, W.Reply>) -> W.World: + W.World{paths, cwd, replies} = world + W.World{paths, cwd, List.append(&2, W.Reply, replies, rs)} + +# the planner's questions answered and the World gone round again, or the +# World as it is once the planner asks none +def pub.world.step( + ~fs: F.Files, + asks: List<&2, W.Ask>, + +world: W.World, + go: W.World -> IO(W.World) +) -> IO(W.World): + match asks: + case Nil{}: + IO.pure(W.World, world) + case Con{a, rest}: + do IO: + rs : List<&2, W.Reply> <- pub.answers(~fs, a <> rest) + go(pub.world.more(world, rs)) + +# the World `bolt ` reads, gathered as src/lint.bend gathers it: the +# planner's questions answered until it asks none, under the same fuel +def pub.world.go(~fs: F.Files, fuel: Nat, +world: W.World) -> IO(W.World): + match fuel: + case 0n: + IO.pure(W.World, world) + case 1n+f: + pub.world.step(~fs, Plan.wants(world), world, ww => pub.world.go(~fs, f, ww)) + +# the role marks the tree's imports give a document, as `bolt ` reads +# them +def pub.seen(~fs: F.Files, +uri: String) -> IO(List<&2, Role.Mark>): + do IO>: + cwd : String <- F.cwd(fs) + ww : W.World <- pub.world.go(~fs, Nat.add(Plan.limit(), 2n), W.World{[Proto.path(uri)], cwd, []}) + return pub.marks(ww) + # open, on the checker's run on the file on disk def pub.opened( ~fs: F.Files, @@ -301,14 +392,16 @@ def pub.opened( do IO: aa : C.Answer <- C.check(ck, Proto.path(uri)) rs : List<&2, Maybe<&2, String>> <- pub.configs(~fs, uri) - return pub.open(ee, uri, text, C.errors(aa), rs, docs) + ms : List<&2, Role.Mark> <- pub.seen(~fs, uri) + return pub.open(ee, uri, text, C.errors(aa), rs, ms, docs) # save, on the checker's run on the file on disk def pub.saved(~fs: F.Files, ~ck: C.Checker, ee: Enc.Enc, +uri: String, docs: List<&2, Docs.Doc>) -> IO(Work): do IO: aa : C.Answer <- C.check(ck, Proto.path(uri)) rs : List<&2, Maybe<&2, String>> <- pub.configs(~fs, uri) - return pub.save(ee, uri, C.errors(aa), rs, docs) + ms : List<&2, Role.Mark> <- pub.seen(~fs, uri) + return pub.save(ee, uri, C.errors(aa), rs, ms, docs) # change: the linter only def pub.changed(~fs: F.Files, ee: Enc.Enc, +uri: String, text: String, docs: List<&2, Docs.Doc>) -> IO(Work): diff --git a/src/lsp/tests/checker.bend b/src/lsp/tests/checker.bend index 0c3f900..097408e 100644 --- a/src/lsp/tests/checker.bend +++ b/src/lsp/tests/checker.bend @@ -5,6 +5,10 @@ # check clean, and `unproven/` has none at all, which is the same verdict. # A fixture holding an *incomplete* PROOF.bend cannot live in this tree: the # gate checks every PROOF.bend it finds, and that one is meant to fail. +# Each open reads every file of the tree for its imports (the roles `bolt +# ` gives it), and this tree holds proofs past the JS lane's stack, so +# the test runs native only: +# lanes: native import Base import ../server.bend as Server import ../transport/script.bend as Script diff --git a/src/role.bend b/src/role.bend index 549f61d..21cc447 100644 --- a/src/role.bend +++ b/src/role.bend @@ -14,6 +14,8 @@ import ./paths.bend as Paths import ./src.bend as Src import ./lazy/lazy.bend as Lazy import ./rules/imports.bend as Imports +import ./syntax/lex.bend as Lex +import ./syntax/outline.bend as Outline import ./finding.bend as F # what a file is to the laws @@ -74,6 +76,13 @@ def fact(ss: Src.Src) -> Fact: +nn = Imports.norm(path) Fact{nn, Imports.targets(items, nn), of_path(path), String.eq(Paths.base(path), "main.bend")} +# a file's fact read from its text alone: lexed and outlined for its imports, +# never parsed, so a file read only for its place in the graph costs little +def fact.text(+path: String, +text: String) -> Fact: + +nn = Imports.norm(path) + Fact{nn, Imports.targets(Outline.of_tokens(text, Lex.tokens(text)), nn), of_path(path), + String.eq(Paths.base(path), "main.bend")} + # every file's fact, in order def facts(ss: List<&2, Src.Src>) -> List<&2, Fact>: match ss: @@ -157,13 +166,17 @@ def marks.of( Bool.pick(List<&2, Mark>, Bool.and(Imports.has(laws, norm), Bool.not(Imports.has(code, norm))), Mark{norm, role.of(named, reached(Imports.has(lawful, norm)))} <> more, more) +# the role of every file on the laws' side of a graph given as facts +def marks.facts(+fs: List<&2, Fact>) -> List<&2, Mark>: + +ls = reach(fs, roots(fs)) + marks.of(fs, ls, reach(fs, lawful(fs)), reach(fs, code(fs, ls))) + # the role of every file on the laws' side. Both walks are the one every # project rule takes over imports (src/rules/imports.bend), each from all its # roots at once def marks(ss: List<&2, Src.Src>) -> List<&2, Mark>: - +fs = facts(ss) - +ls = reach(fs, roots(fs)) - marks.of(fs, ls, reach(fs, lawful(fs)), reach(fs, code(fs, ls))) + marks.facts(facts(ss)) + # a normalized path's role among the marks: neither when it is not marked def of.norm(ms: List<&2, Mark>, +nn: String) -> Role: diff --git a/src/rules.bend b/src/rules.bend index 09a0343..7959b65 100644 --- a/src/rules.bend +++ b/src/rules.bend @@ -2,7 +2,7 @@ # rules// with `check(src) -> List`, run over each file read # once (src/src.bend); a project rule has `check(ds) -> List` and # sees many files at once, as the digests of rules/digest.bend: `coverage` -# one package's files, `unsafe` every file (`project` below). The directory +# one package's files at a time (`project` below). The directory # names the group, and codes.bend says it again for the config. # Adding one is adding it here and a row in codes.bend. One rule is not # listed here, `noqa` (rules/style/noqa.bend): it reads the other rules' @@ -11,9 +11,9 @@ # grades the run, not a source, so the planner runs it between the per-file # and the project rules. # The groups are what a bolt.bend switches at once: correctness (what will -# fail or blow up), suspicious (what is probably a slip), style, laws, and -# pedantic (advice that is noisy on idiomatic code: off unless a project -# asks for it). +# fail or blow up), suspicious (what is probably a slip), perf (what is +# slow but right), style, laws, and pedantic (advice that is noisy on +# idiomatic code: off unless a project asks for it). import Base import ./finding.bend as F import ./codes.bend as Codes @@ -32,30 +32,24 @@ import ./rules/style/param.bend as Param import ./rules/style/transcong.bend as Transcong import ./rules/correctness/pick.bend as Pick import ./rules/laws/law.bend as Law -import ./rules/suspicious/strict.bend as Strict +import ./rules/pedantic/strict.bend as Strict import ./rules/suspicious/eager.bend as Eager import ./rules/pedantic/tail.bend as Tail import ./rules/suspicious/concat.bend as Concat -import ./rules/suspicious/index.bend as Index -import ./rules/suspicious/table.bend as Table -import ./rules/suspicious/hoist.bend as Hoist -import ./rules/suspicious/ring.bend as Ring -import ./rules/suspicious/rewalk.bend as Rewalk -import ./rules/suspicious/unit.bend as Unit -import ./rules/suspicious/scan.bend as Scan +import ./rules/pedantic/index.bend as Index +import ./rules/pedantic/rewalk.bend as Rewalk import ./rules/suspicious/thunk.bend as Thunk import ./rules/suspicious/fromrev.bend as Fromrev import ./rules/suspicious/prefix.bend as Prefix import ./rules/correctness/put.bend as Put import ./rules/correctness/escape.bend as Escape -import ./rules/correctness/strings.bend as Strings -import ./rules/correctness/chars.bend as Chars +import ./rules/perf/strings.bend as Strings +import ./rules/perf/chars.bend as Chars import ./rules/correctness/twice.bend as Twice import ./rules/correctness/arms.bend as Arms -import ./rules/correctness/foreign.bend as Foreign -import ./rules/suspicious/fuel.bend as Fuel +import ./rules/pedantic/foreign.bend as Foreign +import ./rules/pedantic/fuel.bend as Fuel import ./rules/laws/closed.bend as Closed -import ./rules/laws/unsafe.bend as Unsafe # the file a Src was read from def on.path(ss: Src.Src) -> String: @@ -72,9 +66,9 @@ def on.at(+path: String, fs: List<&2, F.Finding>) -> List<&2, F.Finding>: F.Finding{path, line, col, len, rule, msg} <> on.at(path, rest) # every per-file rule on a file, told its role (src/role.bend): the rules a -# law or proof file changes (doc, hole, param, scan, fromrev, prefix, -# closed) read the role, never the path, and the cost rules check nothing -# in a law or proof file +# law or proof file changes (doc, hole, param, fromrev, prefix, closed) read +# the role, never the path, and the cost rules check nothing in a law or +# proof file def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: List.concat(&2, F.Finding, [ Doc.check.role(rr, ss), @@ -91,12 +85,7 @@ def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: Role.skip(rr, _u => Concat.check(ss)), Role.skip(rr, _u => Concat.cont(ss)), Role.skip(rr, _u => Index.check(ss)), - Role.skip(rr, _u => Table.check(ss)), - Role.skip(rr, _u => Hoist.check(ss)), - Role.skip(rr, _u => Ring.check(ss)), Role.skip(rr, _u => Rewalk.check(ss)), - Role.skip(rr, _u => Unit.check(ss)), - Scan.check.role(rr, ss), Role.skip(rr, _u => Thunk.check(ss)), Fromrev.check.role(rr, ss), Prefix.check.role(rr, ss), @@ -111,23 +100,10 @@ def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: Closed.check.role(rr, ss), Transcong.check(ss)]) -# every rule's findings on a file read once, as each rule gives them, its -# role read from its name -def on.rules(+ss: Src.Src) -> List<&2, F.Finding>: - on.rules.role(Role.of_path(on.path(ss)), ss) - -# every rule's findings on a file read once, each at that file -def on(+ss: Src.Src) -> List<&2, F.Finding>: - on.at(on.path(ss), on.rules(ss)) - # every rule's findings on a file read once, told its role, each at that file def on.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: on.at(on.path(ss), on.rules.role(rr, ss)) -# every rule's findings on a file -def all(+path: String, +text: String) -> List<&2, F.Finding>: - on(Src.of(path, text)) - # Packages (laws/ScopePackageRootsFound.bend). A package root other than `.` is a directory # holding a main.bend; a file belongs to the nearest root at or above it, and # `coverage` runs on each package's files alone. A directory is @@ -171,10 +147,10 @@ def members(ds: List<&2, Digest.Digest>, +roots: List<&2, String>, +pkg: String) match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = members(rest, roots, pkg) Bool.pick(List<&2, Digest.Digest>, String.eq(owner(roots, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> more, more) + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> more, more) # `coverage` on each package but `.` def covers(rs: List<&2, String>, +ds: List<&2, Digest.Digest>, +roots: List<&2, String>) -> List<&2, F.Finding>: @@ -187,10 +163,10 @@ def covers(rs: List<&2, String>, +ds: List<&2, Digest.Digest>, +roots: List<&2, # every project rule's findings on the files and the package roots but `.` # (each a directory as Digest.dir names one): each file is read down to its # digest once (rules/digest.bend); `coverage` runs on each package's files, -# `.` first, and `unsafe` on every file +# `.` first def project(+srcs: List<&2, Src.Src>, +rs: List<&2, String>) -> List<&2, F.Finding>: +ds = Digest.all.role(Role.marks(srcs), srcs) - List.concat(&2, F.Finding, [Law.check(members(ds, rs, "")), covers(rs, ds, rs), Unsafe.check(ds)]) + List.concat(&2, F.Finding, [Law.check(members(ds, rs, "")), covers(rs, ds, rs)]) # the group a rule is in; an unknown slug grades with correctness def group(+rule: String) -> String: diff --git a/src/rules/digest.bend b/src/rules/digest.bend index 6ad13a0..9976f2c 100644 --- a/src/rules/digest.bend +++ b/src/rules/digest.bend @@ -4,8 +4,8 @@ # file's text, tokens and tree, and the pair of project rules grew faster than # the file count (100 files cost 362 s that way). So each file is read down # once to what those rules ask of it -- its path, its top-level defs and types -# by name and line, what its laws name, what it imports, and its `@unsafe` -# and foreign defs -- and the phases run over these, which are strings and cheap to copy. +# by name and line, what its laws name, what it imports, and its laws -- and +# the phases run over these, which are strings and cheap to copy. import Base import ../src.bend as Src import ../syntax/lex.bend as Lex @@ -17,7 +17,6 @@ import ./imports.bend as Imports import ./laws/closed.bend as Closed import ../paths.bend as Paths import ../role.bend as Role -import ./correctness/foreign.bend as Foreign import ./calls.bend as Calls # what a file holds @@ -34,12 +33,6 @@ type Mention is Data: type Top is Data: Top{kind: Outline.ItemKind, name: String, line: U32, ctors: List<&2, String>, calls: List<&2, Mention>} -# a def a proof may not reach (bend 2.0.32): an `@unsafe def`, where its -# `@` is, or a foreign def (its body only `import "./x.c"` / `import -# "./x.js"` lines), where its header starts; foreign says which -type Unsafe is Data: - Unsafe{name: String, line: U32, col: U32, foreign: Bool} - # a law of a LAWS.bend: its name, the line it starts on, whether it binds # (a `for` or `exs` line), and the lines of its comment block, `# ` dropped type Law is Data: @@ -48,10 +41,10 @@ type Law is Data: # one file as the project rules see it: its path as given and normalized, its # directory, whether it states laws (LAWS.bend) or proves them too # (PROOF.bend), whether the coverage rule lets it off, its top-level items, what -# its own laws name, the files it imports, its `@unsafe` and foreign defs +# its own laws name, the files it imports, and its laws type Digest is Data: Digest{path: String, norm: String, dir: String, is_laws: Bool, is_law_file: Bool, exempt: Bool, - tops: List<&2, Top>, says: List<&2, Mention>, deps: List<&2, String>, unsafes: List<&2, Unsafe>, + tops: List<&2, Top>, says: List<&2, Mention>, deps: List<&2, String>, laws: List<&2, Law>} # what the laws name @@ -260,65 +253,6 @@ def ranged( def is_exempt(+pp: String) -> Bool: Bool.or(Paths.is_law_file(pp), Paths.is_test(pp)) -# the significant tokens: no space, newline or comment -def significant(toks: List<&2, Lex.Tok>) -> List<&2, Lex.Tok>: - match toks: - case Nil{}: - Nil{} - case Con{Lex.Tok{+k, t, l, c}, rest}: - +more = significant(rest) - Bool.pick(List<&2, Lex.Tok>, Lex.significant(k), Lex.Tok{k, t, l, c} <> more, more) - -# the name after `unsafe def`, when the tokens run so -def unsafe_name(toks: List<&2, Lex.Tok>) -> Maybe<&2, String>: - match toks: - case Con{Lex.Tok{k1, +u, l1, c1}, Con{Lex.Tok{k2, +d, l2, c2}, Con{Lex.Tok{k3, n, l3, c3}, rest}}}: - Bool.pick(Maybe<&2, String>, Bool.and(String.eq(u, "unsafe"), String.eq(d, "def")), Some{n}, None{}) - case other: - None{} - -# an `@` that heads an unsafe def, onto the others -def push(mm: Maybe<&2, String>, +ll: U32, +cc: U32, more: List<&2, Unsafe>) -> List<&2, Unsafe>: - match mm: - case None{}: - more - case Some{n}: - Unsafe{n, ll, cc, False{}} <> more - -# every `@ unsafe def name` run of tokens, on one line or two -def unsafe_defs(toks: List<&2, Lex.Tok>) -> List<&2, Unsafe>: - match toks: - case Nil{}: - Nil{} - case Con{Lex.Tok{k, +at, +l, +c}, +rest}: - +more = unsafe_defs(rest) - Bool.pick(List<&2, Unsafe>, String.eq(at, "@"), push(unsafe_name(rest), l, c, more), more) - -# a foreign def, when its body is one (Foreign.lanes), onto the others -def foreign.push( - mm: Maybe<&2, Foreign.Lanes>, - +name: String, - +ll: U32, - +cc: U32, - more: List<&2, Unsafe> -) -> List<&2, Unsafe>: - match mm: - case None{}: - more - case Some{_lanes}: - Unsafe{name, ll, cc, True{}} <> more - -# every top-level foreign def, where its header starts -def foreign_defs(root: Tree.Node) -> List<&2, Unsafe>: - match root: - case Tree.NCons{Tree.Stmt{Tree.SDef{}, +kids, body}, rest}: - foreign.push(Foreign.lanes(body), Foreign.name.of(Bind.declared(kids)), Tree.line(kids), Tree.col(kids), - foreign_defs(rest)) - case Tree.NCons{h, rest}: - foreign_defs(rest) - case other: - Nil{} - # the laws # -------- @@ -395,7 +329,6 @@ def of(s2: Src.Src) -> Digest: Digest{path, p, Imports.dir_of(path), Paths.is_laws(path), Paths.is_law_file(path), is_exempt(p), ranged(items, uses.of(bound), as, p, proofs(Calls.defs(tree))), stated(Paths.is_laws(path), tree, bound, as, p), Imports.targets(items, p), - List.append(&2, Unsafe, unsafe_defs(significant(toks)), foreign_defs(tree)), laws.in(Paths.is_laws(path), tree, items)} # one parsed file read down to its digest, told its role (src/role.bend): a @@ -408,7 +341,6 @@ def of.role(+rr: Role.Role, s2: Src.Src) -> Digest: Digest{path, p, Imports.dir_of(path), laws, Role.is_law_file(rr), Bool.or(Role.is_law_file(rr), Paths.is_test(p)), ranged(items, uses.of(bound), as, p, proofs(Calls.defs(tree))), stated(laws, tree, bound, as, p), Imports.targets(items, p), - List.append(&2, Unsafe, unsafe_defs(significant(toks)), foreign_defs(tree)), laws.in(laws, tree, items)} # one parsed file's digest, told its role among the marks (src/role.bend) @@ -430,5 +362,5 @@ def edges(ds: List<&2, Digest>) -> List<&2, Imports.Edge>: match ds: case Nil{}: Nil{} - case Con{Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: Imports.Edge{norm, deps} <> edges(rest) diff --git a/src/rules/imports.bend b/src/rules/imports.bend index dfee61d..8792c4b 100644 --- a/src/rules/imports.bend +++ b/src/rules/imports.bend @@ -172,43 +172,3 @@ def walk( # the closure of the file at from, a path normalized already, over the graph def closure.go(es: List<&2, Edge>, +read: List<&2, String>, +fuel: Nat, +from: String) -> List<&2, String>: List.reverse(&2, String, walk(fuel, [from], [from], es, read)) - -# a file the trail's walk has queued, and the files it was reached through, -# itself first and the start last -type Step is Data: - Step{at: String, back: List<&2, String>} - -# each file queued, reached through back -def steps(ds: List<&2, String>, +back: List<&2, String>) -> List<&2, Step>: - match ds: - case Nil{}: - Nil{} - case Con{+d, rest}: - Step{d, d <> back} <> steps(rest, back) - -# the walk `walk` takes, breadth first, each queued file carrying how it was -# reached, until it takes the file at to: the files from the start to it, in -# order; none when the walk never takes it -def trail.go( - fuel: Nat, - todo: List<&2, Step>, - +seen: List<&2, String>, - +es: List<&2, Edge>, - +read: List<&2, String>, - +to: String -) -> List<&2, String>: - match fuel todo: - case 0n t: - Nil{} - case 1n+f Nil{}: - Nil{} - case 1n+f Con{Step{+at, +back}, rest}: - +found = fresh.fast(deps.first(es, at), seen, read) - Lazy.stop(List<&2, String>, String.eq(at, to), List.reverse(&2, String, back), - _u => trail.go(f, List.append(&2, Step, rest, steps(found, back)), - List.append(&2, String, List.reverse(&2, String, found), seen), es, read, to)) - -# a shortest chain of relative imports from the file at from to the file at -# to, both normalized: the files in order, from first and to last -def trail(es: List<&2, Edge>, +read: List<&2, String>, +fuel: Nat, +from: String, +to: String) -> List<&2, String>: - trail.go(fuel, [Step{from, [from]}], [from], es, read, to) diff --git a/src/rules/laws/law.bend b/src/rules/laws/law.bend index 3323ba2..b1f81b3 100644 --- a/src/rules/laws/law.bend +++ b/src/rules/laws/law.bend @@ -50,7 +50,7 @@ def named(ds: List<&2, Digest.Digest>) -> List<&2, Digest.Mention>: match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: named.put(is_laws, says, named(rest)) # the graph @@ -95,7 +95,7 @@ def graph.items(ds: List<&2, Digest.Digest>) -> List<&2, Imports.Edge>: match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: nodes(tops, norm, graph.items(rest)) # the graph: the empty key first, leading to what the laws name, then the @@ -119,7 +119,7 @@ def law_dirs(ds: List<&2, Digest.Digest>) -> List<&2, String>: match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = law_dirs(rest) Bool.pick(List<&2, String>, is_laws, dir <> more, more) @@ -146,7 +146,7 @@ def check.go(ds: List<&2, Digest.Digest>, +cl: List<&2, String>) -> List<&2, F.F match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = check.go(rest, cl) Lazy.stop(List<&2, F.Finding>, exempt, more, _u => List.append(&2, F.Finding, gaps(tops, cl, norm, path), more)) diff --git a/src/rules/laws/unsafe.bend b/src/rules/laws/unsafe.bend deleted file mode 100644 index f0c0aa8..0000000 --- a/src/rules/laws/unsafe.bend +++ /dev/null @@ -1,138 +0,0 @@ -# rule unsafe (project-wide): an `@unsafe def` or a foreign def (its body -# only `import "./x.c"` / `import "./x.js"` lines, what rule foreign reads) -# that a LAWS.bend or PROOF.bend reaches by relative imports, over the files -# bolt read. Since bend 2.0.32 a proof passes only when no def it loads, -# imports included, is `@unsafe` or foreign or names one that is: every def -# of every imported module counts, named by a law or not. Otherwise `bend` -# prints `SOME PROOFS FAIL`, then `Error: N defs rely on unsafe or foreign -# code:` and a `- name` list, and exits 1 (2.0.34; before 2.0.32 it printed -# "All terms check, but ..." and exited 0). So the gate is red, and the -# message names the defs that lean on the effect, not the import that -# brought it in: this rule names that path, law file first. `@unsafe` skips -# the termination check (a def that never returns proves anything): recurse -# on a shrinking argument or on fuel, and drop it. A foreign effect is fine -# in the program, never under a law: move it into a sibling module no law -# file imports, and hand the laws' code the effect as a service record -# (AGENTS.md; snap, ezhttp, bolt and ez were restructured that way). A def no -# law file reaches is not this rule's business. Hash imports (`import -# 0x.../path`, resolved through BEND_LIB) are not followed: bolt reads only -# the files of the run. The rule reads each file's digest -# (src/rules/digest.bend), and takes every closure over one import graph. -import Base -import ../../finding.bend as F -import ../digest.bend as Digest -import ../imports.bend as Imports - -# a law file and what it reaches -type Reach is Data: - Reach{origin: String, files: List<&2, String>} - -# every LAWS.bend and PROOF.bend with its closure over the graph -def reaches( - ds: List<&2, Digest.Digest>, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, Reach>: - match ds: - case Nil{}: - Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - +more = reaches(rest, es, read, fuel) - Bool.pick(List<&2, Reach>, is_law_file, Reach{path, Imports.closure.go(es, read, fuel, norm)} <> more, more) - -# the first law file that reaches the path; hit: the one before rs did, so -# the search stops there -def reacher.go(rs: List<&2, Reach>, +pp: String, hit: Maybe<&2, String>) -> Maybe<&2, String>: - match rs: - case Nil{}: - hit - case Con{Reach{origin, ps}, rest}: - match hit: - case Some{x}: - Some{x} - case None{}: - reacher.go(rest, pp, Bool.pick(Maybe<&2, String>, Imports.has(ps, pp), Some{origin}, None{})) - -# the first law file that reaches the path -def reacher(rs: List<&2, Reach>, +pp: String) -> Maybe<&2, String>: - reacher.go(rs, pp, None{}) - -# what a def reached from a law file along the import path via is, and what -# to do about it -def say(foreign: Bool, +name: String, +origin: String, +via: String) -> String: - match foreign: - case True{}: - "foreign def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; move the effect into a sibling module no law file imports, and pass it in as a service." - case False{}: - "@unsafe def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; recurse on a shrinking argument or on fuel and drop @unsafe, or move it into a sibling module no law file imports." - -# each unsafe or foreign def as a finding, at its `@unsafe` or its `def`, -# reached from the law file along the import path via -def report(us: List<&2, Digest.Unsafe>, +origin: String, +via: String, +path: String) -> List<&2, F.Finding>: - match us: - case Nil{}: - Nil{} - case Con{Digest.Unsafe{n, l, c, +fo}, rest}: - F.Finding{path, l, c, Bool.pick(U32, fo, 3, 7), "unsafe", say(fo, n, origin, via)} <> report(rest, origin, via, path) - -# the import path from the file at from to the file at to, both normalized, -# as `a -> b -> c` -def via(es: List<&2, Imports.Edge>, +read: List<&2, String>, +fuel: Nat, +from: String, +to: String) -> String: - String.join(Imports.trail(es, read, fuel, from, to), " -> ") - -# the unsafe and foreign defs of the file at norm, reached from the law file -# at origin: the import path is looked for only when there is one -def found.some( - us: List<&2, Digest.Unsafe>, - +origin: String, - +path: String, - +norm: String, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, F.Finding>: - match us: - case Nil{}: - Nil{} - case Con{u, rest}: - report(u <> rest, origin, via(es, read, fuel, Imports.norm(origin), norm), path) - -# the unsafe and foreign defs of a file, when a law file reaches it -def found( - mm: Maybe<&2, String>, - us: List<&2, Digest.Unsafe>, - +path: String, - +norm: String, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{origin}: - found.some(us, origin, path, norm, es, read, fuel) - -def check.go( - ds: List<&2, Digest.Digest>, - +rs: List<&2, Reach>, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - Nil{} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - List.append(&2, F.Finding, found(reacher(rs, norm), unsafes, path, norm, es, read, fuel), - check.go(rest, rs, es, read, fuel)) - -# the rule, over every file the linter read -def check(+ds: List<&2, Digest.Digest>) -> List<&2, F.Finding>: - +es = Digest.edges(ds) - +read = Imports.paths(es) - +fuel = List.length(&2, Digest.Digest, ds) - check.go(ds, reaches(ds, es, read, fuel), es, read, fuel) diff --git a/src/rules/correctness/foreign.bend b/src/rules/pedantic/foreign.bend similarity index 100% rename from src/rules/correctness/foreign.bend rename to src/rules/pedantic/foreign.bend diff --git a/src/rules/suspicious/fuel.bend b/src/rules/pedantic/fuel.bend similarity index 100% rename from src/rules/suspicious/fuel.bend rename to src/rules/pedantic/fuel.bend diff --git a/src/rules/suspicious/index.bend b/src/rules/pedantic/index.bend similarity index 94% rename from src/rules/suspicious/index.bend rename to src/rules/pedantic/index.bend index 4059826..21b3b49 100644 --- a/src/rules/suspicious/index.bend +++ b/src/rules/pedantic/index.bend @@ -7,8 +7,8 @@ # (`List.get(.., xs, 0n)`, `5000n`) is exempt, as are laws and proofs (a def # with no type at all fills a law: it is a proof). A # `List.get` on a fixed table (a literal, a sized array, a constant -# `List.replicate`) is `table`'s finding instead, so the two rules do not -# both report that call. `String.get` is never `table`'s: it stays here. A +# `List.replicate`: src/rules/table.bend) is left alone; `String.get` always +# stays here. A # get that is the def's own self-call is exempt: the step of a def named # `List.get` or `String.get` itself (Base's), which walks one cell per call. import Base @@ -18,7 +18,7 @@ import ../../syntax/lex.bend as Lex import ../../syntax/tree.bend as Tree import ../calls.bend as Calls import ../../lazy/lazy.bend as Lazy -import ./table.bend as Table +import ../table.bend as Table # is the chain exactly one number? def literal(nn: Tree.Node) -> Bool: @@ -28,7 +28,7 @@ def literal(nn: Tree.Node) -> Bool: case other: False{} -# a fixed table is reported by `table`, not here +# a fixed table (src/rules/table.bend) is left alone def held(+list: Bool, coll: Tree.Node, +fixed: List<&2, String>) -> Bool: match list: case False{}: diff --git a/src/rules/suspicious/rewalk.bend b/src/rules/pedantic/rewalk.bend similarity index 100% rename from src/rules/suspicious/rewalk.bend rename to src/rules/pedantic/rewalk.bend diff --git a/src/rules/suspicious/strict.bend b/src/rules/pedantic/strict.bend similarity index 100% rename from src/rules/suspicious/strict.bend rename to src/rules/pedantic/strict.bend diff --git a/src/rules/correctness/chars.bend b/src/rules/perf/chars.bend similarity index 100% rename from src/rules/correctness/chars.bend rename to src/rules/perf/chars.bend diff --git a/src/rules/correctness/strings.bend b/src/rules/perf/strings.bend similarity index 100% rename from src/rules/correctness/strings.bend rename to src/rules/perf/strings.bend diff --git a/src/rules/style/noqa.bend b/src/rules/style/noqa.bend index 0029b4d..53096cc 100644 --- a/src/rules/style/noqa.bend +++ b/src/rules/style/noqa.bend @@ -7,7 +7,7 @@ # It reads the graded findings of the whole run, so it is no per-file rule: # the planner runs it after every other rule and after the filter, and # what it reports no noqa comment silences. A project rule's code -# (`coverage`, `unsafe`) is judged only when the run is the whole +# (`coverage`) is judged only when the run is the whole # tree (`whole`): over files named, and in the editor, those rules see only # part of the laws, so their silence proves nothing. import Base @@ -18,7 +18,7 @@ import ../../noqa.bend as Noqa # a project rule's code def project(+code: String) -> Bool: - Lazy.or_else(String.eq(code, Codes.code("coverage")), _u => String.eq(code, Codes.code("unsafe"))) + String.eq(code, Codes.code("coverage")) # a code this run can judge: any, over the whole tree; a per-file rule's # (or no rule's) otherwise diff --git a/src/rules/suspicious/hoist.bend b/src/rules/suspicious/hoist.bend deleted file mode 100644 index f119de4..0000000 --- a/src/rules/suspicious/hoist.bend +++ /dev/null @@ -1,653 +0,0 @@ -# rule hoist: a fixed table built inside a def that calls itself, from inputs -# that do not change, and then indexed. The build runs again on every step. -# Build it once, outside the recursion. A build is a list or array literal, -# `List.replicate` / `List.range` / `List.map` / `Array.new` / `Array.map`, or -# a call to a def of this file whose body is itself one fixed table of more -# than eight cells; a def whose body cannot be seen (another module's) is not -# a build. A table of eight cells or fewer is left alone, and so is a build -# whose arguments depend on the step. A name that is not a carried parameter -# (a pattern binder, a value computed on the step) keeps the build; a case arm -# that does not recurse is cold; laws and proofs do not run (a def with no -# type at all fills a law: it is a proof). `List.get` itself stays with -# `index`. -import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy -import ./table.bend as Table - -# where an accessor reads its collection, when it is one -type Slot is Data: - Slot{on: Bool, at: Nat} - -# a `name = expr` whose left side is one plain name -type Got is Data: - Got{nm: String, expr: Tree.Node} - -# which argument a get or set indexes -def slot_pick(+list: Bool, +str: Bool, +arr: Bool) -> Slot: - match list str arr: - case True{} b c: - Slot{True{}, 2n} - case False{} True{} c: - Slot{True{}, 0n} - case False{} False{} True{}: - Slot{True{}, 1n} - case False{} False{} False{}: - Slot{False{}, 0n} - -# List.get / List.set read argument 2, String.get argument 0, Array.get / Array.set argument 1 -def slot_of(+tt: String) -> Slot: - slot_pick( - Bool.or(String.eq(tt, "List.get"), String.eq(tt, "List.set")), - String.eq(tt, "String.get"), - Bool.or(String.eq(tt, "Array.get"), String.eq(tt, "Array.set"))) - -# the self-calls' argument lists -def pushes(nn: Tree.Node, +self: String) -> List<&2, List<&2, Tree.Node>>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +me = Bool.and(String.eq(o, "("), String.eq(t, self)) - +more = List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)]) - Bool.pick(List<&2, List<&2, Tree.Node>>, me, Calls.args(kids) <> more, more) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(body, self), pushes(rest, self)]) - case Tree.NCons{h, rest}: - pushes(rest, self) - case other: - Nil{} - -# is the chain exactly this name? -def lone_eq(nn: Tree.Node, +name: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - String.eq(t, name) - case other: - False{} - -# every self-call passes this same name at this argument -def all_same(cs: List<&2, List<&2, Tree.Node>>, +ii: Nat, +name: String) -> Bool: - match cs: - case Nil{}: - True{} - case Con{as, rest}: - +here = lone_eq(Calls.arg(as, ii), name) - +more = all_same(rest, ii, name) - Bool.and(here, more) - -# parameters every self-call passes unchanged, when there is a self-call -def carried.go( - ps: List<&2, Tree.Node>, - +cs: List<&2, List<&2, Tree.Node>>, - +ii: Nat, - +acc: List<&2, String> -) -> List<&2, String>: - match ps: - case Nil{}: - List.reverse(&2, String, acc) - case Con{p, rest}: - +nm = Calls.param_name(p) - +keep = Bool.and(Bool.not(String.is_empty(nm)), all_same(cs, ii, nm)) - carried.go(rest, cs, (ii + 1n : Nat), Bool.pick(List<&2, String>, keep, nm <> acc, acc)) - -# parameters passed through unchanged; empty when nothing recurses -def carried_of(cs: List<&2, List<&2, Tree.Node>>, +sig: Tree.Node) -> List<&2, String>: - match cs: - case Nil{}: - Nil{} - case Con{h, t}: - carried.go(Calls.params(sig), h <> t, 0n, []) - -# parameters passed through unchanged; empty when nothing recurses -def carried(sig: Tree.Node, body: Tree.Node, +self: String) -> List<&2, String>: - carried_of(pushes(body, self), sig) - -# a plain name is closed only when it is a carried parameter; a type or a dotted name is -def name_ok(kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool: - match kk: - case Lex.TName{}: - List.contains(~String, ~String.eq, carried, tt) - case other: - True{} - -# a token after an operator: a template argument is not a value, anything else is -def leaf_ok(+skip: Bool, tok: Lex.Tok, +carried: List<&2, String>) -> Bool: - match skip: - case True{}: - True{} - case False{}: - Lex.Tok{+k, +t, l, c} = tok - name_ok(k, t, carried) - -# an application is closed in its arguments; a bracketed name is a value -def value_ok(+app: Bool, kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool: - match app: - case True{}: - True{} - case False{}: - name_ok(kk, tt, carried) - -# every value name under the node is a carried parameter (callees and templates are not values) -def closed(nn: Tree.Node, +carried: List<&2, String>) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, Tree.NCons{Tree.Leaf{+tok}, tail}}: - +ok = leaf_ok(String.eq(op, "~"), tok, carried) - +aft = closed(tail, carried) - Bool.and(ok, aft) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, op, l, c}}, rest}: - closed(rest, carried) - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +ok = value_ok(String.eq(o, "("), k, t, carried) - +inn = closed(kids, carried) - +aft = closed(rest, carried) - Bool.and(ok, Bool.and(inn, aft)) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}: - +ok = List.contains(~String, ~String.eq, carried, t) - +aft = closed(rest, carried) - Bool.and(ok, aft) - case Tree.NCons{Tree.Leaf{tok}, rest}: - closed(rest, carried) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - +inn = closed(kids, carried) - +aft = closed(rest, carried) - Bool.and(inn, aft) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - +inn = closed(kids, carried) - +bod = closed(body, carried) - +aft = closed(rest, carried) - Bool.and(inn, Bool.and(bod, aft)) - case Tree.NCons{h, rest}: - closed(rest, carried) - case Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}: - List.contains(~String, ~String.eq, carried, t) - case other: - True{} - -# a top-level token of this text, not one nested in a group -def top_mark(nn: Tree.Node, +want: String) -> Bool: - match nn: - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - top_mark(rest, want) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}: - +more = top_mark(rest, want) - Bool.or(String.eq(t, want), more) - case Tree.NCons{h, rest}: - top_mark(rest, want) - case other: - False{} - -# `[v : T*n]` or `[v : T^d]`: an array, not a list -def is_array(+kids: Tree.Node) -> Bool: - +col = top_mark(kids, ":") - +star = top_mark(kids, "*") - +hat = top_mark(kids, "^") - Bool.and(col, Bool.or(star, hat)) - -# how many top-level copies of this token -def top_count(nn: Tree.Node, +want: String, +got: Nat) -> Nat: - match nn: - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - top_count(rest, want, got) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}: - +next = Bool.pick(Nat, String.eq(t, want), (got + 1n : Nat), got) - top_count(rest, want, next) - case Tree.NCons{h, rest}: - top_count(rest, want, got) - case other: - got - -# a number literal of eight or fewer -def small_num(+tt: String) -> Bool: - List.contains(~String, ~String.eq, - ["0", "1", "2", "3", "4", "5", "6", "7", "8", "0n", "1n", "2n", "3n", "4n", "5n", "6n", "7n", "8n"], tt) - -# the size after `*` is a literal of eight or fewer, or the depth after `^` (2^d slots) three or fewer -def small_size(nn: Tree.Node) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, - Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l2, c2}}, rest}}: - +here = Bool.or(Bool.and(String.eq(op, "*"), small_num(t)), - Bool.and(String.eq(op, "^"), List.contains(~String, ~String.eq, ["0", "1", "2", "3", "0n", "1n", "2n", "3n"], t))) - +more = small_size(rest) - Bool.or(here, more) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - small_size(rest) - case Tree.NCons{h, rest}: - small_size(rest) - case other: - False{} - -# more than eight cells: nine or more list elements, or an array whose size is not that small -def wide_shape(+arr: Bool, +kids: Tree.Node) -> Bool: - match arr: - case True{}: - Bool.not(small_size(kids)) - case False{}: - Nat.is_gt(top_count(kids, ",", 0n), 7n) - -# constructors that build a fixed table rather than walk one -def allowed(+tt: String) -> Bool: - List.contains(~String, ~String.eq, - ["List.replicate", "List.range", "List.map", "Array.new", "Array.map"], tt) - -# a Base constructor on the allow list, or a def of this file whose body is -# itself a wide table (`wides`); a def whose body cannot be seen is not one -def builder(kk: Lex.TokKind, +tt: String, +self: String, +wides: List<&2, String>) -> Bool: - match kk: - case Lex.TName{}: - Bool.and(Bool.not(String.eq(tt, self)), List.contains(~String, ~String.eq, wides, tt)) - case Lex.TDotted{}: - Bool.or(allowed(tt), List.contains(~String, ~String.eq, wides, tt)) - case other: - False{} - -# a bracket group that is a closed list or array of more than eight cells -def group_yes(+arr: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool: - +shape = Bool.or(top_mark(kids, ","), arr) - +ok = closed(kids, carried) - +wide = wide_shape(arr, kids) - Bool.and(shape, Bool.and(ok, wide)) - -# a bracket group that is a closed list or array -def table_group(+brack: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool: - match brack: - case False{}: - False{} - case True{}: - group_yes(is_array(kids), kids, carried) - -# the chain is exactly one number; its text, or empty -def num_text(nn: Tree.Node) -> String: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l, c}}, Tree.NNil{}}: - t - case other: - "" - -# a count is wide when it is not a literal of eight or fewer (a name may be large) -def count_wide(aa: Tree.Node) -> Bool: - Bool.not(small_num(num_text(aa))) - -# the last argument -def last_node(as: List<&2, Tree.Node>) -> Tree.Node: - match as: - case Nil{}: - Tree.NNil{} - case Con{h, Nil{}}: - h - case Con{h, rest}: - last_node(rest) - -# `List.replicate` / `Array.new` take argument 2; anything else is wide unless it is `List.range` -def call_arm(rep: Bool, +as: List<&2, Tree.Node>, +end: Tree.Node, +range: Bool) -> Bool: - match rep: - case True{}: - count_wide(Calls.arg(as, 2n)) - case False{}: - Bool.pick(Bool, range, count_wide(end), True{}) - -# `List.replicate` / `Array.new` / `List.range` are wide unless their count is a small literal -def call_wide(+tt: String, kids: Tree.Node) -> Bool: - +as = Calls.args(kids) - call_arm(Bool.or(String.eq(tt, "List.replicate"), String.eq(tt, "Array.new")), as, last_node(as), String.eq(tt, "List.range")) - -# a call that builds a table from closed arguments, and is not a small literal count -def table_built(+ok: Bool, +wide: Bool, kids: Tree.Node, +carried: List<&2, String>) -> Bool: - match ok wide: - case True{} True{}: - closed(kids, carried) - case True{} False{}: - False{} - case False{} b: - False{} - -# a call that builds a table from closed arguments -def table_call( - +app: Bool, - kk: Lex.TokKind, - +tt: String, - +kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - match app: - case False{}: - False{} - case True{}: - table_built(builder(kk, tt, self, wides), call_wide(tt, kids), kids, carried) - -# a fixed table of more than eight cells, whatever its cells read -def wide_of(nn: Tree.Node) -> Bool: - match nn: - case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: - wide_shape(is_array(kids), kids) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}: - call_wide(t, kids) - case other: - False{} - -# a def whose body is one wide fixed table (`Table.built`, and more than eight cells) -def wide_one(body: Tree.Node, +name: String, +acc: List<&2, String>) -> List<&2, String>: - match body: - case Tree.NCons{Tree.Stmt{kind, +kids, inn}, Tree.NNil{}}: - Bool.pick(List<&2, String>, Bool.and(Table.built(kids), wide_of(kids)), name <> acc, acc) - case other: - acc - -# defs of this file whose body is one wide fixed table -def wide_defs(ds: List<&2, Calls.Def>, acc: List<&2, String>) -> List<&2, String>: - match ds: - case Nil{}: - acc - case Con{Calls.Def{+name, sig, +body}, rest}: - wide_defs(rest, wide_one(body, name, acc)) - -# a list, an array, or a builder call, closed over carried parameters -def is_table(nn: Tree.Node, +self: String, +wides: List<&2, String>, +carried: List<&2, String>) -> Bool: - match nn: - case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: - table_group(String.eq(o, "["), kids, carried) - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}: - table_call(String.eq(o, "("), k, t, kids, self, wides, carried) - case other: - False{} - -# drop the right-hand side -def skip_expr(nn: Tree.Node) -> Maybe<&2, Got>: - match nn: - case other: - None{} - -# the chain after `=`, when the left side is one plain name and nothing else -def got_bad(+bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>: - match bad: - case True{}: - skip_expr(expr) - case False{}: - Some{Got{nm, expr}} - -# the chain after `=`, when exactly one plain name stands before it -def got_at(+seen: Bool, +bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>: - match seen: - case False{}: - skip_expr(expr) - case True{}: - got_bad(bad, nm, expr) - -# a second name, a constructor or a group on the left is not a plain binder -def eat(nn: Tree.Node, +seen: Bool, +bad: Bool, +nm: String) -> Maybe<&2, Got>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, rest}: - got_at(seen, bad, nm, rest) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}: - eat(rest, True{}, Bool.or(bad, seen), t) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, rest}: - eat(rest, seen, True{}, nm) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - eat(rest, seen, True{}, nm) - case Tree.NCons{h, rest}: - eat(rest, seen, bad, nm) - case other: - None{} - -# a finding on the table expression -def cite(+nn: Tree.Node, +path: String) -> List<&2, F.Finding>: - [F.Finding{path, Tree.line(nn), Tree.col(nn), U32.from_nat(String.length(Tree.text(nn))), "hoist", - "This table is rebuilt on every step from inputs that do not change; build it once, outside the recursion."}] - -# a name, and nothing after it -def wrapped_inside(nn: Tree.Node, +binder: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - String.eq(t, binder) - case other: - False{} - -# inside one pair of parentheses -def wrapped_open(+paren: Bool, kids: Tree.Node, +binder: String) -> Bool: - match paren: - case True{}: - wrapped_inside(kids, binder) - case False{}: - False{} - -# the chain is exactly this name, or that name in parentheses -def wrapped(nn: Tree.Node, +binder: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - String.eq(t, binder) - case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: - wrapped_open(String.eq(o, "("), kids, binder) - case other: - False{} - -# the collection argument is this name -def uses_arg(ss: Slot, +as: List<&2, Tree.Node>, +binder: String) -> Bool: - Slot{on, at} = ss - match on: - case False{}: - False{} - case True{}: - wrapped(Calls.arg(as, at), binder) - -# `name[` is an index; a call is an index when its collection argument is the name -def uses_here_call(+call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool: - match call: - case False{}: - False{} - case True{}: - uses_arg(slot_of(tt), Calls.args(kids), binder) - -# `name[` is an index; a call is an index when its collection argument is the name -def uses_here(+brack: Bool, +call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool: - match brack: - case True{}: - String.eq(tt, binder) - case False{}: - uses_here_call(call, tt, kids, binder) - -# the name is indexed later: `name[`, or the collection of a get or set -def uses_name(nn: Tree.Node, +binder: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +here = uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder) - +inn = uses_name(kids, binder) - +aft = uses_name(rest, binder) - Bool.or(here, Bool.or(inn, aft)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - +inn = uses_name(kids, binder) - +aft = uses_name(rest, binder) - Bool.or(inn, aft) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - +inn = uses_name(kids, binder) - +bod = uses_name(body, binder) - +aft = uses_name(rest, binder) - Bool.or(inn, Bool.or(bod, aft)) - case Tree.NCons{h, rest}: - uses_name(rest, binder) - case other: - False{} - -# indexed in the let's body or in a following statement -def uses_or(body: Tree.Node, rest: Tree.Node, +binder: String) -> Bool: - +inn = uses_name(body, binder) - +aft = uses_name(rest, binder) - Bool.or(inn, aft) - -# a finding when the table is also indexed -def let_used(+used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>: - match used: - case True{}: - cite(expr, path) - case False{}: - Nil{} - -# a finding when the right-hand side is a closed table that is indexed -def let_use(+tab: Bool, +used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>: - match tab: - case False{}: - Nil{} - case True{}: - let_used(used, expr, path) - -# a plain let of a table -def let_of( - mm: Maybe<&2, Got>, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{Got{+nm, +expr}}: - let_use(is_table(expr, self, wides, carried), uses_or(body, rest, nm), expr, path) - -# a hot plain let of a table that is indexed afterwards -def let_hit( - +hot: Bool, - kids: Tree.Node, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match hot: - case False{}: - Nil{} - case True{}: - let_of(eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) - -# a finding when this argument is itself a closed table -def one_yes(+yes: Bool, +aa: Tree.Node, +path: String) -> List<&2, F.Finding>: - match yes: - case True{}: - cite(aa, path) - case False{}: - Nil{} - -# the collection argument, when this call indexes one -def arg_table( - ss: Slot, - +as: List<&2, Tree.Node>, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - Slot{on, at} = ss - match on: - case False{}: - Nil{} - case True{}: - +aa = Calls.arg(as, at) - one_yes(is_table(aa, self, wides, carried), aa, path) - -# an inline table passed straight to a get or set, when the group is a call -def call_open( - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match open: - case False{}: - Nil{} - case True{}: - arg_table(slot_of(tt), Calls.args(kids), self, wides, carried, path) - -# an inline table passed straight to a get or set -def call_table( - +hot: Bool, - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match hot: - case False{}: - Nil{} - case True{}: - call_open(open, tt, kids, self, wides, carried, path) - -# a `=>` cools the rest of its chain: a lambda's body runs when it is called, not per step -def warm(+hot: Bool, kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TLam{}: - False{} - case other: - hot - -# tables built in a hot region -def walk( - nn: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = warm(hot, k) - +own = call_table(heat, String.eq(o, "("), t, kids, self, wides, carried, path) - +inn = walk(kids, self, wides, carried, path, heat) - +aft = walk(rest, self, wides, carried, path, heat) - List.concat(&2, F.Finding, [own, inn, aft]) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - walk(rest, self, wides, carried, path, warm(hot, k)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, - [walk(kids, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)]) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - +arm = Bool.and(hot, Calls.calls(body, self)) - List.concat(&2, F.Finding, [walk(kids, self, wides, carried, path, False{}), - walk(body, self, wides, carried, path, arm), walk(rest, self, wides, carried, path, hot)]) - case Tree.NCons{Tree.Stmt{kind, +kids, +body}, +rest}: - +own = let_hit(hot, kids, body, rest, self, wides, carried, path) - List.concat(&2, F.Finding, [own, walk(kids, self, wides, carried, path, hot), - walk(body, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)]) - case Tree.NCons{h, rest}: - walk(rest, self, wides, carried, path, hot) - case other: - Nil{} - -def check.go( - ds: List<&2, Calls.Def>, - +wide: List<&2, String>, - +path: String, - acc: List<&2, List<&2, F.Finding>> -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, wide, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, name, wide, carried(sig, body, name), path, True{})) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - +ds = Calls.defs(tree) - check.go(ds, wide_defs(ds, []), path, []) diff --git a/src/rules/suspicious/ring.bend b/src/rules/suspicious/ring.bend deleted file mode 100644 index e01b7ae..0000000 --- a/src/rules/suspicious/ring.bend +++ /dev/null @@ -1,313 +0,0 @@ -# rule ring: a self-call replaces a binder with `List.append` of `List.drop` -# (or `String.append` / `++`, or `tail`) by a constant count, passing it back -# in the binder's own parameter position. Each step copies that fixed window. -# Keep it in an Array and advance an index. A list the def matches and walks -# (as any scrutinee of a match) is variable-length input, a window dropped -# into another parameter's slot is not carried, and a one-shot trim is not a -# loop: all stay quiet. Growing an accumulator by append, with no drop of -# that same value, is `concat`. Laws and proofs do not run (a def with no -# type at all fills a law: it is a proof). -import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy - -# the first of two maybes -def or_tok(aa: Maybe<&2, Lex.Tok>, +bb: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match aa: - case Some{+t}: - Some{t} - case None{}: - bb - -# is the chain exactly one number? -def is_num(nn: Tree.Node) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, Tree.NNil{}}: - True{} - case other: - False{} - -# the chain is exactly one lowercase name; its text, or empty -def name_text(nn: Tree.Node) -> String: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - t - case other: - "" - -# this argument is a window binder -def win_name(nn: Tree.Node, +wins: List<&2, String>) -> Bool: - +tt = name_text(nn) - Bool.and(Bool.not(String.is_empty(tt)), List.contains(~String, ~String.eq, wins, tt)) - -# does any argument name the window? -def has_name(as: List<&2, Tree.Node>, +wins: List<&2, String>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, rest}: - +more = has_name(rest, wins) - Bool.or(win_name(h, wins), more) - -# is the last argument a number? -def last_num(as: List<&2, Tree.Node>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, Nil{}}: - is_num(h) - case Con{h, rest}: - last_num(rest) - -# a fixed drop or tail of a named window -def drop_ok(+tt: String, +as: List<&2, Tree.Node>, +wins: List<&2, String>) -> Bool: - +kind = List.contains(~String, ~String.eq, ["List.drop", "String.drop", "List.tail", "String.tail"], tt) - +tail = Bool.or(String.eq(tt, "List.tail"), String.eq(tt, "String.tail")) - Bool.and(kind, Bool.and(has_name(as, wins), Bool.or(tail, last_num(as)))) - -# the callee token when this call is a fixed drop -def drop_tok( - kk: Lex.TokKind, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +wins: List<&2, String> -) -> Maybe<&2, Lex.Tok>: - Bool.pick(Maybe<&2, Lex.Tok>, drop_ok(tt, Calls.args(kids), wins), Some{Lex.Tok{kk, tt, line, col}}, None{}) - -# the drop this call is, when the group is an application -def here_of( - +open: Bool, - kk: Lex.TokKind, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +wins: List<&2, String> -) -> Maybe<&2, Lex.Tok>: - match open: - case False{}: - None{} - case True{}: - drop_tok(kk, tt, line, col, kids, wins) - -# a drop anywhere under the node -def drop_in(nn: Tree.Node, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +here = here_of(String.eq(o, "("), k, t, l, c, kids, wins) - +inn = drop_in(kids, wins) - +aft = drop_in(rest, wins) - or_tok(here, or_tok(inn, aft)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - or_tok(drop_in(kids, wins), drop_in(rest, wins)) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - or_tok(drop_in(kids, wins), or_tok(drop_in(body, wins), drop_in(rest, wins))) - case Tree.NCons{h, rest}: - drop_in(rest, wins) - case other: - None{} - -# which list an append extends -def receiver_is(+list: Bool, +str: Bool, +as: List<&2, Tree.Node>, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match list str: - case True{} b: - drop_in(Calls.arg(as, 2n), wins) - case False{} True{}: - drop_in(Calls.arg(as, 0n), wins) - case False{} False{}: - None{} - -# the drop an append copies onto, when this call is List.append or String.append -def receiver(+tt: String, kids: Tree.Node, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - receiver_is(String.eq(tt, "List.append"), String.eq(tt, "String.append"), Calls.args(kids), wins) - -# a `++` whose left already dropped: that drop; otherwise nothing -def pp_found(+is_pp: Bool, seen: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match is_pp seen: - case True{} Some{+t}: - Some{t} - case True{} None{}: - None{} - case False{} s: - None{} - -# the drop still in play after this operator (`++` starts a new left side) -def pp_seen(+is_pp: Bool, +seen: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match is_pp: - case True{}: - None{} - case False{}: - seen - -# an application's append-receiver drop; a bracket is not an application -def call_recv(+open: Bool, +tt: String, kids: Tree.Node, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match open: - case False{}: - None{} - case True{}: - receiver(tt, kids, wins) - -# a drop this node itself is: the call when it is an application, else under a bracket -def call_drop( - +open: Bool, - +kk: Lex.TokKind, - +tt: String, - +line: U32, - +col: U32, - +kids: Tree.Node, - +wins: List<&2, String> -) -> Maybe<&2, Lex.Tok>: - match open: - case False{}: - drop_in(kids, wins) - case True{}: - drop_tok(kk, tt, line, col, kids, wins) - -# a drop then `++`, or an append onto a drop, in this chain -def plus_scan(nn: Tree.Node, +seen: Maybe<&2, Lex.Tok>, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, rest}: - +is_pp = String.eq(op, "++") - or_tok(pp_found(is_pp, seen), plus_scan(rest, pp_seen(is_pp, seen), wins)) - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +open = String.eq(o, "(") - +recv = call_recv(open, t, kids, wins) - +inn = plus_scan(kids, None{}, wins) - +drop = call_drop(open, k, t, l, c, kids, wins) - +aft = plus_scan(rest, or_tok(seen, drop), wins) - or_tok(recv, or_tok(inn, aft)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - +inn = plus_scan(kids, None{}, wins) - +aft = plus_scan(rest, or_tok(seen, drop_in(kids, wins)), wins) - or_tok(inn, aft) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - or_tok(plus_scan(kids, None{}, wins), or_tok(plus_scan(body, None{}, wins), plus_scan(rest, seen, wins))) - case Tree.NCons{h, rest}: - plus_scan(rest, seen, wins) - case other: - None{} - -# a finding at the drop the scan found -def slide_at(mm: Maybe<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{Lex.Tok{k, +t, l, c}}: - [F.Finding{path, l, c, U32.from_nat(String.length(t)), "ring", - "Each step drops from and appends to a fixed-size window, which copies it; keep the window in an Array and advance an index."}] - -# a finding at the drop, when the argument slides a window -def slide(aa: Tree.Node, +path: String, +wins: List<&2, String>) -> List<&2, F.Finding>: - slide_at(plus_scan(aa, None{}, wins), path) - -# the windows an argument in this parameter's slot may slide: the parameter -# itself when it is a window, else none -def slot_wins(+slot: String, +wins: List<&2, String>) -> List<&2, String>: - Bool.pick(List<&2, String>, List.contains(~String, ~String.eq, wins, slot), [slot], []) - -# every self-call argument that slides the window of its own slot (params, -# from the argument's position on) -def slides( - as: List<&2, Tree.Node>, - +params: List<&2, String>, - +path: String, - +wins: List<&2, String> -) -> List<&2, F.Finding>: - match as: - case Nil{}: - Nil{} - case Con{h, rest}: - +slot = Maybe.default(&2, String, List.head(&2, String, params), "") - List.append(&2, F.Finding, slide(h, path, slot_wins(slot, wins)), - slides(rest, List.tail(&2, String, params), path, wins)) - -# every self-call's sliding arguments -def walk( - nn: Tree.Node, - +self: String, - +params: List<&2, String>, - +path: String, - +wins: List<&2, String> -) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +me = Bool.and(String.eq(o, "("), String.eq(t, self)) - +own = Lazy.stop(List<&2, F.Finding>, Bool.not(me), [], _u => slides(Calls.args(kids), params, path, wins)) - List.concat(&2, F.Finding, [own, walk(kids, self, params, path, wins), walk(rest, self, params, path, wins)]) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, [walk(kids, self, params, path, wins), walk(rest, self, params, path, wins)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, self, params, path, wins), walk(body, self, params, path, wins), - walk(rest, self, params, path, wins)]) - case Tree.NCons{h, rest}: - walk(rest, self, params, path, wins) - case other: - Nil{} - -# the top-level names of a chain, onto acc -def names_in(nn: Tree.Node, acc: List<&2, String>) -> List<&2, String>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest}: - t <> names_in(rest, acc) - case Tree.NCons{h, rest}: - names_in(rest, acc) - case other: - acc - -# every scrutinee of `match a b ..:`, onto acc (acc alone for any other chain) -def match_names(kids: Tree.Node, +acc: List<&2, String>) -> List<&2, String>: - match kids: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TKey{}, +kw, l, c}}, +rest}: - Lazy.stop(List<&2, String>, Bool.not(String.eq(kw, "match")), acc, _u => names_in(rest, acc)) - case other: - acc - -# names the def matches, as any scrutinee: variable-length input, not a window -def matched(nn: Tree.Node, +acc: List<&2, String>) -> List<&2, String>: - match nn: - case Tree.NCons{Tree.Stmt{Tree.STerm{}, +kids, body}, rest}: - +here = match_names(kids, acc) - matched(rest, matched(body, matched(kids, here))) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - matched(rest, matched(body, matched(kids, acc))) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - matched(rest, matched(kids, acc)) - case Tree.NCons{h, rest}: - matched(rest, acc) - case other: - acc - -# parameters that are not matched as input -def windows.go(ps: List<&2, String>, +bad: List<&2, String>, +acc: List<&2, String>) -> List<&2, String>: - match ps: - case Nil{}: - List.reverse(&2, String, acc) - case Con{+p, rest}: - +known = List.contains(~String, ~String.eq, bad, p) - +keep = Bool.and(Bool.not(String.is_empty(p)), Bool.not(known)) - windows.go(rest, bad, Bool.pick(List<&2, String>, keep, p <> acc, acc)) - -# parameters that are not matched as input -def windows(sig: Tree.Node, body: Tree.Node) -> List<&2, String>: - windows.go(Calls.names(sig), matched(body, []), []) - -def check.go(ds: List<&2, Calls.Def>, +path: String, acc: List<&2, List<&2, F.Finding>>) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, name, Calls.names(sig), path, windows(sig, body))) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - check.go(Calls.defs(tree), path, []) diff --git a/src/rules/suspicious/scan.bend b/src/rules/suspicious/scan.bend deleted file mode 100644 index 38123ce..0000000 --- a/src/rules/suspicious/scan.bend +++ /dev/null @@ -1,347 +0,0 @@ -# rule scan: a def that calls itself and, on the same step, hands a -# parameter it carries unchanged to a walk. Every step walks that list again: -# O(|A| * |B|), A what the def walks and B what it carries. Index it once -# outside the loop, or merge two sorted walks. A parameter is carried when -# every self-call passes it back as that same lone name in its own position -# (hoist's notion). A call is a name token (plain or dotted), then a `(` -# group, and what it walks is some of its arguments, counted from 0. A Base -# list search walks its list: argument 2 of `List.contains`, `List.find`, -# `List.filter` and `List.length`, 3 of `List.any` and `List.all`, 4 of -# `List.foldl` and `List.foldr`; a Base walk that rebuilds the list -# (`List.map`, `List.append`, ...) makes output, not a search, and is left -# alone. A def of this file walks, when it calls itself, its first live -# parameter that some self-call does not pass back unchanged (the one it -# shrinks), unless that parameter is a `Nat` or a `String` (a count, such as -# fuel, or a text); and each parameter its body, anywhere, passes as the lone -# name of an argument a call walks, of a Base search or of a def listed -# before it. A def of another module is not seen. A call reports when it is -# not a self-call and an argument it walks is exactly the lone name of a -# carried parameter: anything else there (a literal table, an expression, a -# name the step binds) is left alone. The step is what hoist reads as hot: -# the def's body, a case arm's body only when it calls the def, never a case -# pattern, and never the rest of a chain after `=>` (a lambda's body, a -# `Lazy` thunk, runs when it is called). Laws and proofs do not run (a def -# with no type at all fills a law: it is a proof), and a law file is skipped -# whole. One finding per call, on its callee. -import Base -import ../../src.bend as Src -import ../../paths.bend as Paths -import ../../role.bend as Role -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy -import ./hoist.bend as Hoist - -# a def of this file, and one argument it walks -type Walk is Data: - Walk{name: String, at: Nat} - -# the answer as a list: none or one -def opt(mm: Maybe<&2, Nat>) -> List<&2, Nat>: - match mm: - case None{}: - Nil{} - case Some{at}: - [at] - -# the list argument of a Base list search -def base_at(+tt: String) -> List<&2, Nat>: - +two = List.contains(~String, ~String.eq, ["List.contains", "List.find", "List.filter", "List.length"], tt) - +three = List.contains(~String, ~String.eq, ["List.any", "List.all"], tt) - +four = List.contains(~String, ~String.eq, ["List.foldl", "List.foldr"], tt) - Bool.pick(List<&2, Nat>, two, [2n], Bool.pick(List<&2, Nat>, three, [3n], Bool.pick(List<&2, Nat>, four, [4n], []))) - -# the arguments the defs of this file named tt walk -def file_at(ws: List<&2, Walk>, +tt: String) -> List<&2, Nat>: - match ws: - case Nil{}: - Nil{} - case Con{Walk{+nm, +at}, rest}: - +more = file_at(rest, tt) - Bool.pick(List<&2, Nat>, String.eq(nm, tt), at <> more, more) - -# the arguments a callee walks: a Base walk's list, and those the defs of this file record -def slots(+tt: String, +ws: List<&2, Walk>) -> List<&2, Nat>: - List.append(&2, Nat, base_at(tt), file_at(ws, tt)) - -# a leaf that is a plain name: its text -def name_tok(tok: Lex.Tok) -> Maybe<&2, String>: - match tok: - case Lex.Tok{Lex.TName{}, t, l, c}: - Some{t} - case other: - None{} - -# the argument, when it is exactly one plain name -def name_of(nn: Tree.Node) -> Maybe<&2, String>: - match nn: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - name_tok(tok) - case other: - None{} - -# the name, when the list holds it -def keep_in(mm: Maybe<&2, String>, +names: List<&2, String>) -> Maybe<&2, String>: - match mm: - case None{}: - None{} - case Some{+nm}: - Bool.pick(Maybe<&2, String>, List.contains(~String, ~String.eq, names, nm), Some{nm}, None{}) - -# the first of two answers that has one -def first(aa: Maybe<&2, String>, bb: Maybe<&2, String>) -> Maybe<&2, String>: - match aa: - case None{}: - bb - case Some{nm}: - Some{nm} - -# the first walked argument that is the lone name of one of the names -def found(ss: List<&2, Nat>, +as: List<&2, Tree.Node>, +names: List<&2, String>) -> Maybe<&2, String>: - match ss: - case Nil{}: - None{} - case Con{at, rest}: - +more = found(rest, as, names) - first(keep_in(name_of(Calls.arg(as, at)), names), more) - -# where a name sits among the parameter names -def index_of(ps: List<&2, String>, +nm: String, +ii: Nat) -> Maybe<&2, Nat>: - match ps: - case Nil{}: - None{} - case Con{+p, rest}: - Lazy.stop(Maybe<&2, Nat>, String.eq(p, nm), Some{ii}, _u => index_of(rest, nm, 1n+ii)) - -# where the name sits among the parameter names, when there is a name -def index_in(mm: Maybe<&2, String>, +ps: List<&2, String>) -> Maybe<&2, Nat>: - match mm: - case None{}: - None{} - case Some{+nm}: - index_of(ps, nm, 0n) - -# the parameters that sit, as lone names, at walked arguments -def params_at(ss: List<&2, Nat>, +as: List<&2, Tree.Node>, +ps: List<&2, String>) -> List<&2, Nat>: - match ss: - case Nil{}: - Nil{} - case Con{at, rest}: - +more = params_at(rest, as, ps) - List.append(&2, Nat, opt(index_in(name_of(Calls.arg(as, at)), ps)), more) - -# a call's walked parameters, when its callee is a name and it is a call -def pass_hit( - kk: Lex.TokKind, - +open: Bool, - +tt: String, - kids: Tree.Node, - +ps: List<&2, String>, - +ws: List<&2, Walk> -) -> List<&2, Nat>: - match kk: - case Lex.TName{}: - Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => params_at(slots(tt, ws), Calls.args(kids), ps)) - case Lex.TDotted{}: - Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => params_at(slots(tt, ws), Calls.args(kids), ps)) - case other: - Nil{} - -# the parameters a body passes, anywhere, as an argument some call walks -def passes(nn: Tree.Node, +ps: List<&2, String>, +ws: List<&2, Walk>) -> List<&2, Nat>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +own = pass_hit(k, String.eq(o, "("), t, kids, ps, ws) - List.concat(&2, Nat, [own, passes(kids, ps, ws), passes(rest, ps, ws)]) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, Nat, [passes(kids, ps, ws), passes(rest, ps, ws)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, Nat, [passes(kids, ps, ws), passes(body, ps, ws), passes(rest, ps, ws)]) - case Tree.NCons{h, rest}: - passes(rest, ps, ws) - case other: - Nil{} - -# a live parameter that some self-call does not pass back unchanged -def moves(+pp: Tree.Node, +carried: List<&2, String>) -> Bool: - Bool.and(Calls.is_live(pp), Bool.not(List.contains(~String, ~String.eq, carried, Calls.param_name(pp)))) - -# a count or a text: a loop down one scans no list -def scalar(+pp: Tree.Node) -> Bool: - +hh = Calls.type_head(pp, False{}) - Bool.or(String.eq(hh, "Nat"), String.eq(hh, "String")) - -# where the parameter a def shrinks sits, unless it is a count or a text -def place(+pp: Tree.Node, +ii: Nat) -> Maybe<&2, Nat>: - Bool.pick(Maybe<&2, Nat>, scalar(pp), None{}, Some{ii}) - -def own.go(ps: List<&2, Tree.Node>, +carried: List<&2, String>, +ii: Nat) -> Maybe<&2, Nat>: - match ps: - case Nil{}: - None{} - case Con{+p, rest}: - +here = moves(p, carried) - Lazy.stop(Maybe<&2, Nat>, here, place(p, ii), _u => own.go(rest, carried, 1n+ii)) - -# where the parameter a def shrinks sits, when it calls itself and it is no count or text -def own(+sig: Tree.Node, +body: Tree.Node, +name: String) -> Maybe<&2, Nat>: - Lazy.stop(Maybe<&2, Nat>, Bool.not(Calls.calls(body, name)), None{}, - _u => own.go(Calls.params(sig), Hoist.carried(sig, body, name), 0n)) - -# one walk of the def for each argument -def tag(ns: List<&2, Nat>, +name: String, +ws: List<&2, Walk>) -> List<&2, Walk>: - match ns: - case Nil{}: - ws - case Con{at, rest}: - Walk{name, at} <> tag(rest, name, ws) - -def walks.go(ds: List<&2, Calls.Def>, +ws: List<&2, Walk>) -> List<&2, Walk>: - match ds: - case Nil{}: - ws - case Con{Calls.Def{+name, +sig, +body}, rest}: - +mine = List.append(&2, Nat, opt(own(sig, body, name)), passes(body, Calls.names(sig), ws)) - walks.go(rest, tag(mine, name, ws)) - -# the defs of this file that walk, each with an argument it walks, in order -def walks(ds: List<&2, Calls.Def>) -> List<&2, Walk>: - walks.go(ds, []) - -# the name of the parameter a def shrinks, "" for none -def lead.go(ps: List<&2, Tree.Node>, +carried: List<&2, String>) -> String: - match ps: - case Nil{}: - "" - case Con{+p, rest}: - +here = moves(p, carried) - Lazy.stop(String, here, Calls.param_name(p), _u => lead.go(rest, carried)) - -# a finding on the callee, naming the carried list -def cite( - mm: Maybe<&2, String>, - +tt: String, - +line: U32, - +col: U32, - +self: String, - +lead: String, - +path: String -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{+pp}: - [F.Finding{path, line, col, U32.from_nat(String.length(tt)), "scan", - tt ++ " walks " ++ pp ++ " on every step of " ++ self ++ ", which carries it unchanged: that is O(|" ++ lead - ++ "| * |" ++ pp ++ "|). Index it once outside the loop, or merge two sorted walks."}] - -# a hot call of a walk other than the def, a walked argument carried -def on_name( - +hot: Bool, - +open: Bool, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +self: String, - +ws: List<&2, Walk>, - +carried: List<&2, String>, - +lead: String, - +path: String -) -> List<&2, F.Finding>: - Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self))))), [], - _u => cite(found(slots(tt, ws), Calls.args(kids), carried), tt, line, col, self, lead, path)) - -# a call's finding, when its callee is a name -def call_hit( - kk: Lex.TokKind, - +hot: Bool, - +open: Bool, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +self: String, - +ws: List<&2, Walk>, - +carried: List<&2, String>, - +lead: String, - +path: String -) -> List<&2, F.Finding>: - match kk: - case Lex.TName{}: - on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case Lex.TDotted{}: - on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case other: - Nil{} - -# the walks of carried lists on a hot path -def walk( - nn: Tree.Node, - +self: String, - +ws: List<&2, Walk>, - +carried: List<&2, String>, - +lead: String, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = Hoist.warm(hot, k) - +own = call_hit(k, heat, String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, path) - +inn = walk(kids, self, ws, carried, lead, path, heat) - +aft = walk(rest, self, ws, carried, lead, path, heat) - List.concat(&2, F.Finding, [own, inn, aft]) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, - [walk(kids, self, ws, carried, lead, path, hot), walk(rest, self, ws, carried, lead, path, hot)]) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - +arm = Bool.and(hot, Calls.calls(body, self)) - List.concat(&2, F.Finding, [walk(kids, self, ws, carried, lead, path, False{}), - walk(body, self, ws, carried, lead, path, arm), walk(rest, self, ws, carried, lead, path, hot)]) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, self, ws, carried, lead, path, hot), - walk(body, self, ws, carried, lead, path, hot), walk(rest, self, ws, carried, lead, path, hot)]) - case Tree.NCons{h, rest}: - walk(rest, self, ws, carried, lead, path, hot) - case other: - Nil{} - -# one def's walk, its carried parameters and the one it shrinks read once -def run(+sig: Tree.Node, +body: Tree.Node, +name: String, +ws: List<&2, Walk>, +path: String) -> List<&2, F.Finding>: - +carried = Hoist.carried(sig, body, name) - walk(body, name, ws, carried, lead.go(Calls.params(sig), carried), path, True{}) - -def check.go( - ds: List<&2, Calls.Def>, - +ws: List<&2, Walk>, - +path: String, - acc: List<&2, List<&2, F.Finding>> -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, ws, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => run(sig, body, name, ws, path)) <> acc) - -# the findings over a file's defs, their walks read once -def check.on(+ds: List<&2, Calls.Def>, +path: String) -> List<&2, F.Finding>: - check.go(ds, walks(ds), path, []) - -# the rule; a law file is skipped whole, before its walks are read -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{+path, text, toks, tree, bound, items} = ss - Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => check.on(Calls.defs(tree), path)) - -# the rule, told the file's role (src/role.bend): a law or proof file is -# skipped whole -def check.role(rr: Role.Role, ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - Lazy.stop(List<&2, F.Finding>, Role.is_law_file(rr), [], _u => check.on(Calls.defs(tree), path)) diff --git a/src/rules/suspicious/unit.bend b/src/rules/suspicious/unit.bend deleted file mode 100644 index 94655a2..0000000 --- a/src/rules/suspicious/unit.bend +++ /dev/null @@ -1,227 +0,0 @@ -# rule unit: a multiply or divide by the literal one (`1`, `1n`, `1.0`) on a -# step that actually recurses. `*` and `/` inside `(e : T)` are `T.mul` and -# `T.div`; bare, they are `Nat`. Either form, and a direct `Nat.mul` / -# `U32.mul` / `F32.mul` or `.div`, does nothing when an operand is one -# (`/` only when the divisor is). Drop the operation. Any other factor or -# divisor is left alone, and so is a base case or a def that does not call -# itself. A base case is a case arm that does not call the def; the arms are -# judged per `case` only, so a `Bool.pick` branch beside a self-call is still -# the recursive step, and so is a lambda body inside it (a `Lazy.stop` -# thunk or a `List.map` callback runs on the step). One finding per -# operation, on the literal: `Nat.mul(1n, 1n)` is one. Laws and proofs do not -# run (a def with no type at all fills a law: it is a proof). -import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy - -# is the text the literal one? -def one_text(+tt: String) -> Bool: - Bool.or(String.eq(tt, "1"), Bool.or(String.eq(tt, "1n"), String.eq(tt, "1.0"))) - -# is the kind a number token? -def num_kind(kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TNum{}: - True{} - case other: - False{} - -# a token that is the literal one -def one_tok(tok: Lex.Tok) -> Bool: - Lex.Tok{+k, +t, l, c} = tok - Bool.and(num_kind(k), one_text(t)) - -# is the kind an operator's? -def op_kind(kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TOp{}: - True{} - case other: - False{} - -# the maybe, when it holds the literal one -def one_of(mm: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match mm: - case None{}: - None{} - case Some{+tok}: - Bool.pick(Maybe<&2, Lex.Tok>, one_tok(tok), Some{tok}, None{}) - -# the first maybe when it holds a token, else the second -def either(aa: Maybe<&2, Lex.Tok>, bb: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match aa: - case None{}: - bb - case Some{tok}: - Some{tok} - -# the literal an operator's finding points at: for `*` the factor after it -# when it is one, else the one before; for `/` the divisor when it is one -def which(+op: String, prev: Maybe<&2, Lex.Tok>, +nxt: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - +right = one_of(nxt) - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "*"), either(right, one_of(prev)), - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "/"), right, None{})) - -# a finding on that literal -def cite(mm: Maybe<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{Lex.Tok{k, +t, l, c}}: - [F.Finding{path, l, c, U32.from_nat(String.length(t)), "unit", - "Multiplying or dividing by " ++ t ++ " has no effect; drop the operation."}] - -# an argument that is exactly the literal one -def arg_one(aa: Tree.Node) -> Maybe<&2, Lex.Tok>: - match aa: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - one_of(Some{tok}) - case other: - None{} - -# the last argument, when it is the literal one -def last_one(as: List<&2, Tree.Node>) -> Maybe<&2, Lex.Tok>: - match as: - case Nil{}: - None{} - case Con{h, Nil{}}: - arg_one(h) - case Con{h, rest}: - last_one(rest) - -# the last argument that is the literal one -def any_one(as: List<&2, Tree.Node>) -> Maybe<&2, Lex.Tok>: - match as: - case Nil{}: - None{} - case Con{h, rest}: - +more = any_one(rest) - either(more, arg_one(h)) - -# a divide's divisor, when the call is a divide -def on_div_is(+div: Bool, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - match div: - case True{}: - cite(last_one(as), path) - case False{}: - Nil{} - -# a divide's divisor, when the call is a divide -def on_div(+tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - on_div_is(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), as, path) - -# a multiply (either factor) or a divide (the divisor only) -def on_op_is(+mul: Bool, +tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - match mul: - case True{}: - cite(any_one(as), path) - case False{}: - on_div(tt, as, path) - -# a multiply (either factor) or a divide (the divisor only) -def on_op(+tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - on_op_is(List.contains(~String, ~String.eq, ["Nat.mul", "U32.mul", "F32.mul"], tt), tt, as, path) - -# the call's own identity operands, when this step recurses -def on_hot(hot: Bool, +tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - match hot: - case False{}: - Nil{} - case True{}: - on_op(tt, as, path) - -# the call's own identity operands, when this step recurses -def on_call(+tt: String, kids: Tree.Node, +path: String, +hot: Bool) -> List<&2, F.Finding>: - on_hot(hot, tt, Calls.args(kids), path) - -# the call's findings when the group is an application -def own_of( - call: Bool, - +tt: String, - +kids: Tree.Node, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match call: - case True{}: - on_call(tt, kids, path, hot) - case False{}: - Nil{} - -# the first leaf, when the chain starts with one -def first_tok(nn: Tree.Node) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{tok}, rest}: - Some{tok} - case other: - None{} - -# the token a leaf leaves behind for what follows it: none past an operator -def after(tok: Lex.Tok) -> Maybe<&2, Lex.Tok>: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - Bool.pick(Maybe<&2, Lex.Tok>, op_kind(k), None{}, Some{Lex.Tok{k, t, l, c}}) - -# a leaf's own finding: an operator by one, in a hot region -def op_hit( - tok: Lex.Tok, - +prev: Maybe<&2, Lex.Tok>, - +nxt: Maybe<&2, Lex.Tok>, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match tok: - case Lex.Tok{k, +t, l, c}: - Bool.pick(List<&2, F.Finding>, Bool.and(hot, op_kind(k)), cite(which(t, prev, nxt), path), []) - -# the name a group is called by: the token right before it ("" for none) -def callee(mm: Maybe<&2, Lex.Tok>) -> String: - match mm: - case None{}: - "" - case Some{Lex.Tok{k, t, l, c}}: - t - -# `*` / `/` by one, and the same through calls, in a hot region. prev is the -# leaf right before, none past an operator, a group or a statement; a `(` -# group right after a leaf is a call of it -def walk(nn: Tree.Node, +prev: Maybe<&2, Lex.Tok>, +self: String, +path: String, +hot: Bool) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{+tok}, +rest}: - +more = walk(rest, after(tok), self, path, hot) - List.append(&2, F.Finding, op_hit(tok, prev, first_tok(rest), path, hot), more) - case Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}: - +own = own_of(String.eq(o, "("), callee(prev), kids, path, hot) - +inner = walk(kids, None{}, self, path, hot) - +later = walk(rest, None{}, self, path, hot) - List.concat(&2, F.Finding, [inner, own, later]) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - +arm = Bool.and(hot, Calls.calls(body, self)) - List.concat(&2, F.Finding, [walk(kids, None{}, self, path, False{}), - walk(body, None{}, self, path, arm), walk(rest, None{}, self, path, hot)]) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, None{}, self, path, hot), - walk(body, None{}, self, path, hot), walk(rest, None{}, self, path, hot)]) - case Tree.NCons{h, rest}: - walk(rest, prev, self, path, hot) - case other: - Nil{} - -def check.go(ds: List<&2, Calls.Def>, +path: String, acc: List<&2, List<&2, F.Finding>>) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, None{}, name, path, True{})) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - check.go(Calls.defs(tree), path, []) diff --git a/src/rules/suspicious/table.bend b/src/rules/table.bend similarity index 77% rename from src/rules/suspicious/table.bend rename to src/rules/table.bend index 6a2ffdf..ce35010 100644 --- a/src/rules/suspicious/table.bend +++ b/src/rules/table.bend @@ -1,21 +1,17 @@ -# rule table: `List.get` or `List.set` at a computed index inside a def that -# calls itself, when the list is a fixed table: a literal, an array, or -# `List.replicate` / `Array.new` / `List.range` with a constant count, written -# inline, defined beside the loop, or held by the let of that name in scope. -# The index walks that table on every step. Keep it in an Array. A let reaches -# the statements after it and the blocks under them, not a sibling case arm, -# and a later let of the name to anything else ends it. A literal index (a -# fixed slot, including 0 and 1), a growing or data-dependent list, a one-shot -# outside the recursion, and `List.head` / `List.tail` do not run. `index` -# leaves a table this rule owns, so one call is one finding. Laws and proofs -# do not run (a def with no type at all fills a law: it is a proof). +# src/rules/table: what a fixed table is, for `index`. A fixed table is a +# literal list, an array, or `List.replicate` / `Array.new` / `List.range` +# with a constant count, written inline, defined beside the loop, or held by +# the let of that name in scope; a let reaches the statements after it and +# the blocks under them, not a sibling case arm, and a later let of the name +# to anything else ends it. `index` leaves a get of one alone. This was the +# `table` rule (U008), retired: it had no public hits; `hit` still builds the +# finding that rule gave, which `index` reads only as "a fixed table". Pure. import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy +import ../finding.bend as F +import ../syntax/lex.bend as Lex +import ../syntax/tree.bend as Tree +import ./calls.bend as Calls +import ../lazy/lazy.bend as Lazy # is the chain exactly one number? def literal(nn: Tree.Node) -> Bool: @@ -254,39 +250,3 @@ def hit( +fire = Bool.and(op, Bool.and(Bool.not(literal(Calls.arg(as, 3n))), owns(Calls.arg(as, 2n), fixed))) Bool.pick(List<&2, F.Finding>, fire, [cite(get, path, line, col, U32.from_nat(String.length(tt)))], []) - -# every fixed-table get or set -def walk(nn: Tree.Node, +path: String, +fixed: List<&2, String>) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +own = hit(String.eq(o, "("), t, kids, fixed, path, l, c) - List.concat(&2, F.Finding, [own, walk(kids, path, fixed), walk(rest, path, fixed)]) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(rest, path, fixed)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(body, path, fixed), walk(rest, path, fixed)]) - case Tree.NCons{h, rest}: - walk(rest, path, fixed) - case other: - Nil{} - -def check.go( - ds: List<&2, Calls.Def>, - +mods: List<&2, String>, - +path: String, - acc: List<&2, List<&2, F.Finding>> -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, mods, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, path, scope(body, mods))) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - +ds = Calls.defs(tree) - check.go(ds, modules(ds, []), path, []) diff --git a/src/syntax/tree.bend b/src/syntax/tree.bend index 7c62bbe..d612696 100644 --- a/src/syntax/tree.bend +++ b/src/syntax/tree.bend @@ -65,17 +65,6 @@ def first(nn: Node) -> Maybe<&2, Lex.Tok>: case NCons{h, t}: first(h) -def text.of(mm: Maybe<&2, Lex.Tok>) -> String: - match mm: - case None{}: - "" - case Some{Lex.Tok{k, t, l, c}}: - t - -# the text of a node's first token ("" for none) -def text(nn: Node) -> String: - text.of(first(nn)) - def line.of(mm: Maybe<&2, Lex.Tok>) -> U32: match mm: case None{}: