src/rules/calls.bend

bolt@1.20.0.0 · 269 lines · 0 definitions

1# src/rules/calls: what the recursion rules (pick, strict, tail, concat,
2# index) share. A file's defs, each with its name, its signature and its body;
3# a call's arguments; whether a def calls itself; its parameters, and the type
4# of the one it walks; and whether it is exempt (a law or a proof never runs,
5# and a def with no type fills a law: it is a proof).
6import Base
7import bolt@1.20.0.0/src/paths.bend as Paths
8import bolt@1.20.0.0/src/syntax/lex.bend as Lex
9import bolt@1.20.0.0/src/syntax/tree.bend as Tree
10import bolt@1.20.0.0/src/syntax/bind.bend as Bind
11import bolt@1.20.0.0/src/lazy/lazy.bend as Lazy
12
13# a def: its name, its own tokens (the signature) and the statements under it
14type Def is Data:
15  Def{name: String, sig: Tree.Node, body: Tree.Node}
16
17# a def with a name joins the others; one without (half-written) does not
18def defs.push(mm: Maybe<&2, String>, sig: Tree.Node, body: Tree.Node, acc: List<&2, Def>) -> List<&2, Def>:
19  match mm:
20    case None{}:
21      acc
22    case Some{name}:
23      Def{name, sig, body} <> acc
24
25# the defs of a parsed source, in order
26def defs(root: Tree.Node) -> List<&2, Def>:
27  match root:
28    case Tree.NCons{Tree.Stmt{Tree.SDef{}, +kids, body}, rest}:
29      defs.push(Bind.declared(kids), kids, body, defs(rest))
30    case Tree.NCons{h, rest}:
31      defs(rest)
32    case other:
33      Nil{}
34
35# a leaf's kind, none for anything else: kind tests read this, never a text
36def leaf.kind(nn: Tree.Node) -> Maybe<&2, Lex.TokKind>:
37  match nn:
38    case Tree.Leaf{Lex.Tok{k, t, l, c}}:
39      Some{k}
40    case other:
41      None{}
42
43# a kind test on a kind, when there is one
44def kind.of(~test: Lex.TokKind -> Bool, mm: Maybe<&2, Lex.TokKind>) -> Bool:
45  match mm:
46    case None{}:
47      False{}
48    case Some{k}:
49      test(k)
50
51# a leaf of this kind?
52def kind.leaf(~test: Lex.TokKind -> Bool, nn: Tree.Node) -> Bool:
53  kind.of(~test, leaf.kind(nn))
54
55# a `:`
56def kind.colon(kk: Lex.TokKind) -> Bool:
57  match kk:
58    case Lex.TColon{}:
59      True{}
60    case other:
61      False{}
62
63# a capitalized name
64def kind.upper(kk: Lex.TokKind) -> Bool:
65  match kk:
66    case Lex.TUpper{}:
67      True{}
68    case other:
69      False{}
70
71# `->`
72def kind.arrow(kk: Lex.TokKind) -> Bool:
73  match kk:
74    case Lex.TArrow{}:
75      True{}
76    case other:
77      False{}
78
79# a plain operator (`-`, `~`, `+`)
80def kind.oper(kk: Lex.TokKind) -> Bool:
81  match kk:
82    case Lex.TOp{}:
83      True{}
84    case other:
85      False{}
86
87# a leaf's text, "" for anything else
88def leaf.text(nn: Tree.Node) -> String:
89  match nn:
90    case Tree.Leaf{Lex.Tok{k, t, l, c}}:
91      t
92    case other:
93      ""
94
95# the arguments of a group, split at its top-level commas (each a chain)
96def args.go(kids: Tree.Node, +cur: Tree.Node, +acc: List<&2, Tree.Node>) -> List<&2, Tree.Node>:
97  match kids:
98    case Tree.NCons{+h, +rest}:
99      Lazy.either(List<&2, Tree.Node>, kind.leaf(~Lex.is_comma, h),
100        _u => args.go(rest, Tree.NNil{}, Tree.reverse(cur, Tree.NNil{}) <> acc),
101        _v => args.go(rest, Tree.NCons{h, cur}, acc))
102    case other:
103      List.reverse(&2, Tree.Node, Tree.reverse(cur, Tree.NNil{}) <> acc)
104
105# the arguments of a group
106def args(kids: Tree.Node) -> List<&2, Tree.Node>:
107  args.go(kids, Tree.NNil{}, [])
108
109# the nth argument, or nothing
110def arg(as: List<&2, Tree.Node>, nn: Nat) -> Tree.Node:
111  match as nn:
112    case Con{h, t} 0n:
113      h
114    case Con{h, t} 1n+p:
115      arg(t, p)
116    case Nil{} m:
117      Tree.NNil{}
118
119# is there a call of the name, `name(..)`, anywhere under the node?
120def calls(nn: Tree.Node, +name: String) -> Bool:
121  match nn:
122    case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, kids, _}, rest}}:
123      +a = calls(kids, name)
124      +b = calls(rest, name)
125      Bool.or(Bool.and(String.eq(o, "("), String.eq(t, name)), Bool.or(a, b))
126    case Tree.Group{open, kids, close}:
127      calls(kids, name)
128    case Tree.Stmt{kind, kids, body}:
129      +a = calls(kids, name)
130      +b = calls(body, name)
131      Bool.or(a, b)
132    case Tree.NCons{h, t}:
133      +a = calls(h, name)
134      +b = calls(t, name)
135      Bool.or(a, b)
136    case other:
137      False{}
138
139# the kids of a signature's first `(` group: the parameters
140def params.of(sig: Tree.Node) -> Tree.Node:
141  match sig:
142    case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, rest}:
143      +more = params.of(rest)
144      Bool.pick(Tree.Node, String.eq(o, "("), kids, more)
145    case Tree.NCons{h, rest}:
146      params.of(rest)
147    case other:
148      Tree.NNil{}
149
150# a def's parameters, each its chain up to a comma (`+xs: List<&2, U32>`)
151def params(sig: Tree.Node) -> List<&2, Tree.Node>:
152  args(params.of(sig))
153
154# a parameter's name: its first lowercase name before the colon ("" for a
155# type parameter like `-A: Type`)
156def param_name(pp: Tree.Node) -> String:
157  match pp:
158    case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest}:
159      t
160    case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, rest}:
161      ""
162    case Tree.NCons{h, rest}:
163      param_name(rest)
164    case other:
165      ""
166
167def names.go(ps: List<&2, Tree.Node>, acc: List<&2, String>) -> List<&2, String>:
168  match ps:
169    case Nil{}:
170      List.reverse(&2, String, acc)
171    case Con{p, rest}:
172      names.go(rest, param_name(p) <> acc)
173
174# the names of a def's parameters
175def names(sig: Tree.Node) -> List<&2, String>:
176  names.go(params(sig), [])
177
178# does a chain hold a top-level colon?
179def has_colon(pp: Tree.Node) -> Bool:
180  match pp:
181    case Tree.NCons{h, rest}:
182      Lazy.or_else(kind.leaf(~kind.colon, h), _u => has_colon(rest))
183    case other:
184      False{}
185
186# does the chain open with a `-` or `~` operator?
187def is_live.sign(pp: Tree.Node) -> Bool:
188  match pp:
189    case Tree.NCons{+h, rest}:
190      +t = leaf.text(h)
191      Bool.and(kind.leaf(~kind.oper, h), Bool.or(String.eq(t, "-"), String.eq(t, "~")))
192    case other:
193      False{}
194
195# a live parameter: typed, and neither erased (`-A: Type`) nor a template
196# (`~f: A -> B`); a bare quantity name (`a` in `List.get(a, ..)`) is not one
197def is_live(+pp: Tree.Node) -> Bool:
198  Bool.and(Bool.not(is_live.sign(pp)), has_colon(pp))
199
200# the head of a parameter's type: the first capitalized name after the colon
201# (`List` in `+xs: +List<A>`), "" for none
202def type_head(pp: Tree.Node, +after: Bool) -> String:
203  match pp:
204    case Tree.NCons{+h, rest}:
205      +more = type_head(rest, Bool.or(after, kind.leaf(~kind.colon, h)))
206      Bool.pick(String, Bool.and(after, kind.leaf(~kind.upper, h)), leaf.text(h), more)
207    case other:
208      ""
209
210def walked.go(ps: List<&2, Tree.Node>, found: Maybe<&2, String>) -> String:
211  match ps found:
212    case Con{+p, rest} None{}:
213      walked.go(rest, Lazy.stop(Maybe<&2, String>, Bool.not(is_live(p)), None{},
214        _u => Some{type_head(p, False{})}))
215    case Con{p, rest} Some{t}:
216      t
217    case Nil{} Some{t}:
218      t
219    case Nil{} None{}:
220      ""
221
222# the type head of the first live parameter: the one a self-call must shrink
223def walked(sig: Tree.Node) -> String:
224  walked.go(params(sig), None{})
225
226# does the def walk a list or a string (its first live parameter's type)?
227def seq(sig: Tree.Node) -> Bool:
228  +h = walked(sig)
229  Bool.or(String.eq(h, "List"), String.eq(h, "String"))
230
231# a `{` group after the arrow, or a proof further on
232def returns_proof.or(+brace: Bool, more: Bool) -> Bool:
233  Bool.or(brace, more)
234
235# does the signature return a proof, `-> {a == b : T}`?
236def returns_proof(sig: Tree.Node) -> Bool:
237  match sig:
238    case Tree.NCons{h, +rest}:
239      match rest:
240        case Tree.NCons{+g, r2}:
241          Lazy.either(Bool, kind.leaf(~kind.arrow, h), _u => returns_proof.or(Tree.opens(g, "{"), returns_proof(r2)),
242            _v => returns_proof(rest))
243        case _r:
244          returns_proof(rest)
245    case other:
246      False{}
247
248# does a chain hold a top-level `->`?
249def has_arrow(pp: Tree.Node) -> Bool:
250  match pp:
251    case Tree.NCons{h, rest}:
252      Lazy.or_else(kind.leaf(~kind.arrow, h), _u => has_arrow(rest))
253    case other:
254      False{}
255
256# does the signature write a type: a `:` among its parameters, or a `->`?
257def typed(+sig: Tree.Node) -> Bool:
258  Lazy.or_else(has_colon(params.of(sig)), _u => has_arrow(sig))
259
260# is the def a proof? One that returns a proof (`-> {a == b : T}`), or one
261# written with no type at all (`def f(x, y):`), which is how Bend fills the
262# law named f
263def proof(+sig: Tree.Node) -> Bool:
264  Bool.or(returns_proof(sig), Bool.not(typed(sig)))
265
266# a law file, a proof file, or a def that is a proof: none of it runs, so its
267# cost does not matter
268def exempt(+path: String, sig: Tree.Node) -> Bool:
269  Bool.or(Paths.is_law_file(path), proof(sig))