parse.bend

bend-osdev-apparmor@0.1.0.0 · 1,850 lines · 213 definitions

1# bend-osdev-apparmor: loading policy text (apparmor.d(5), "FORMAT").
2#
3# 1. Each file's text is split into tokens (token.bend), and the tokens into
4#    items: statements ending at a comma, variable assignments (which end at
5#    the end of their line), includes, and block openings and closings.
6# 2. Includes are replaced by the items of the included file, taken from the
7#    file map: <name> and "name" both look up name. An include is expanded
8#    at most once in each block (the file's top level, each profile, hat or
9#    qualifier block), so including a file twice is including it once and
10#    include cycles end (apparmor_parser 4.0.1 does the same; see the
11#    README, "Experiments"). A missing file fails unless the include says
12#    `if exists`.
13# 3. The preamble (before the first profile) assigns variables, @{NAME} =
14#    values or += values, and declares aliases, alias /from/ -> /to/,; abi
15#    statements are accepted and ignored.
16# 4. Profiles are built: headers, flags, rules (with variables expanded:
17#    one value as itself, several as {v1,v2}; a value's trailing '/' is
18#    dropped before a '/'; @{profile_name} is the profile's full name),
19#    child profiles and hats, and qualifier blocks. An alias adds, for each
20#    file rule whose pattern starts with its source, a copy starting with
21#    its target. Two rules on the same literal path with different exec
22#    modes fail.
23import Base
24import bend-osdev-apparmor@0.1.0.0/text.bend as X
25import bend-osdev-apparmor@0.1.0.0/glob.bend as G
26import bend-osdev-apparmor@0.1.0.0/perm.bend as P
27import bend-osdev-apparmor@0.1.0.0/token.bend as T
28import bend-osdev-apparmor@0.1.0.0/error.bend as E
29import bend-osdev-apparmor@0.1.0.0/options.bend as O
30import bend-osdev-apparmor@0.1.0.0/profile.bend as PR
31import bend-osdev-apparmor@0.1.0.0/rule.bend as R
32import bend-osdev-apparmor@0.1.0.0/rule_file.bend as F
33import bend-osdev-apparmor@0.1.0.0/rule_capability.bend as RC
34import bend-osdev-apparmor@0.1.0.0/rule_network.bend as RN
35import bend-osdev-apparmor@0.1.0.0/rule_signal.bend as RS
36import bend-osdev-apparmor@0.1.0.0/rule_ptrace.bend as RP
37import bend-osdev-apparmor@0.1.0.0/rule_mount.bend as RM
38import bend-osdev-apparmor@0.1.0.0/rule_dbus.bend as RD
39import bend-osdev-apparmor@0.1.0.0/rule_change_profile.bend as RX
40
41# a file of the file map: its name (as includes name it) and its text
42type AaSrcFile is Data:
43  AaSrcFile{name: List<&2, U32>, text: List<&2, U32>}
44
45# Items
46
47type AaItem is Data:
48  AaIStmt{toks: List<&2, T.AaTok>, src: E.AaSrc}
49  AaIVar{toks: List<&2, T.AaTok>, src: E.AaSrc}
50  AaIInclude{name: List<&2, U32>, optional: Bool, src: E.AaSrc}
51  AaIOpen{toks: List<&2, T.AaTok>, src: E.AaSrc}
52  AaIClose{src: E.AaSrc}
53  AaIError{err: E.AaError}
54
55def AaItem.rev_onto(xs: List<&2, AaItem>, acc: List<&2, AaItem>) -> List<&2, AaItem>:
56  match xs:
57    case Nil{}:
58      acc
59    case Con{h, t}:
60      AaItem.rev_onto(t, Con{h, acc})
61
62# The itemizer. Modes: normal; a candidate variable assignment (the word
63# @{NAME} alone, waiting to see = or +=); a variable assignment on a line;
64# an include waiting for its path (stage 0), after `if` (1), after `if exists` (2).
65type AaItMode is Data:
66  AaImNorm{}
67  AaImVarCand{line: U32}
68  AaImVar{line: U32}
69  AaImInc{stage: Nat, line: U32}
70
71type AaItB is Data:
72  AaItB{file: List<&2, U32>, mode: AaItMode, pend: List<&2, T.AaTok>, pdepth: Nat, out: List<&2, AaItem>}
73
74def AaItB.src(+file: List<&2, U32>, line: U32) -> E.AaSrc:
75  E.AaSrc{file, line}
76
77def AaItB.emit(b: AaItB, it: AaItem) -> AaItB:
78  match b:
79    case AaItB{f, m, pe, pd, out}:
80      AaItB{f, AaImNorm{}, Nil{}, pd, Con{it, out}}
81
82def AaItB.err(b: AaItB, e: E.AaError) -> AaItB:
83  AaItB.emit(b, AaIError{e})
84
85def AaItB.push(b: AaItB, t: T.AaTok) -> AaItB:
86  match b:
87    case AaItB{f, m, pe, pd, out}:
88      AaItB{f, m, Con{t, pe}, pd, out}
89
90def AaItB.mode(b: AaItB, m: AaItMode) -> AaItB:
91  match b:
92    case AaItB{f, m0, pe, pd, out}:
93      AaItB{f, m, pe, pd, out}
94
95def AaItB.file(b: AaItB) -> List<&2, U32>:
96  match b:
97    case AaItB{f, m, pe, pd, out}:
98      f
99
100def AaItB.pend_empty(b: AaItB) -> Bool:
101  match b:
102    case AaItB{f, m, pe, pd, out}:
103      match pe:
104        case Nil{}:
105          True{}
106        case Con{h, t}:
107          False{}
108
109def AaItB.pdepth(b: AaItB) -> Nat:
110  match b:
111    case AaItB{f, m, pe, pd, out}:
112      pd
113
114def AaItB.paren(b: AaItB, up: Bool, t: T.AaTok) -> AaItB:
115  match b:
116    case AaItB{f, m, pe, pd, out}:
117      match up:
118        case True{}:
119          AaItB{f, m, Con{t, pe}, 1n+pd, out}
120        case False{}:
121          AaItB{f, m, Con{t, pe}, Nat.sub(pd, 1n), out}
122
123def AaItB.flush.item(kind: Nat, ts: List<&2, T.AaTok>, src: E.AaSrc) -> AaItem:
124  match kind:
125    case 0n:
126      AaIStmt{ts, src}
127    case 1n+k:
128      match k:
129        case 0n:
130          AaIOpen{ts, src}
131        case 1n+j:
132          AaIVar{ts, src}
133
134# the pending tokens become an item: 0 a statement, 1 a block opening, 2 an assignment
135def AaItB.flush(b: AaItB, kind: Nat, line: U32) -> AaItB:
136  match b:
137    case AaItB{+f, m, pe, pd, out}:
138      AaItB{f, AaImNorm{}, Nil{}, pd, Con{AaItB.flush.item(kind, T.AaTok.rev_onto(pe, Nil{}), E.AaSrc{f, line}), out}}
139
140# a word starting @{: 0 not a variable word, 1 exactly @{NAME}, 2 @{NAME}= or @{NAME}+= with more
141def AaIt.var_kind.rest(+r: List<&2, U32>) -> Nat:
142  match r:
143    case Nil{}:
144      1n
145    case Con{h, t}:
146      Bool.pick(Nat, Bool.or(X.AaText.starts(r, "="), X.AaText.starts(r, "+=")), 2n, 0n)
147
148def AaIt.after_close(xs: List<&2, U32>) -> Maybe<&2, List<&2, U32>>:
149  match xs:
150    case Nil{}:
151      None{}
152    case Con{+h, +t}:
153      Bool.pick(Maybe<&2, List<&2, U32>>, U32.is_eq(h, 125), Some{t}, AaIt.after_close(t))
154
155def AaIt.var_kind.go(m: Maybe<&2, List<&2, U32>>) -> Nat:
156  match m:
157    case None{}:
158      0n
159    case Some{r}:
160      AaIt.var_kind.rest(r)
161
162def AaIt.var_kind(+w: List<&2, U32>) -> Nat:
163  Bool.pick(Nat, X.AaText.starts(w, "@{"), AaIt.var_kind.go(AaIt.after_close(w)), 0n)
164
165def AaIt.is_include(t: T.AaTok) -> Bool:
166  match t:
167    case T.AaTWord{+w, l}:
168      Bool.or(X.AaText.is(w, "#include"), X.AaText.is(w, "include"))
169    case _:
170      False{}
171
172# a token in normal mode
173def AaItB.norm.word(k: Nat, +b: AaItB, t: T.AaTok, +line: U32) -> AaItB:
174  match k:
175    case 0n:
176      AaItB.push(b, t)
177    case 1n+k1:
178      match k1:
179        case 0n:
180          AaItB.mode(AaItB.push(b, t), AaImVarCand{line})
181        case 1n+k2:
182          AaItB.mode(AaItB.push(b, t), AaImVar{line})
183
184def AaItB.norm.first(t: T.AaTok, +b: AaItB) -> AaItB:
185  match t:
186    case T.AaTWord{+w, +l}:
187      Bool.pick(AaItB, AaIt.is_include(T.AaTWord{w, l}), AaItB.mode(b, AaImInc{0n, l}), AaItB.norm.word(AaIt.var_kind(w), b, T.AaTWord{w, l}, l))
188    case _:
189      AaItB.push(b, t)
190
191def AaItB.norm.open(depth0: Bool, +b: AaItB, t: T.AaTok, +l: U32) -> AaItB:
192  match depth0:
193    case True{}:
194      AaItB.flush(b, 1n, l)
195    case False{}:
196      AaItB.push(b, t)
197
198def AaItB.norm.close(depth0: Bool, empty: Bool, +b: AaItB, t: T.AaTok, +l: U32) -> AaItB:
199  match depth0:
200    case True{}:
201      match empty:
202        case True{}:
203          AaItB.emit(b, AaIClose{E.AaSrc{AaItB.file(b), l}})
204        case False{}:
205          AaItB.err(b, E.AaErrSyntax{AaItB.file(b), l})
206    case False{}:
207      AaItB.push(b, t)
208
209def AaItB.norm.comma(depth0: Bool, empty: Bool, +b: AaItB, t: T.AaTok, +l: U32) -> AaItB:
210  match depth0:
211    case True{}:
212      match empty:
213        case True{}:
214          AaItB.err(b, E.AaErrSyntax{AaItB.file(b), l})
215        case False{}:
216          AaItB.flush(b, 0n, l)
217    case False{}:
218      AaItB.push(b, t)
219
220def AaItB.norm.go(t: T.AaTok, +d0: Bool, +empty: Bool, +b: AaItB) -> AaItB:
221  match t:
222    case T.AaTOpen{+l}:
223      AaItB.norm.open(d0, b, T.AaTOpen{l}, l)
224    case T.AaTClose{+l}:
225      AaItB.norm.close(d0, empty, b, T.AaTClose{l}, l)
226    case T.AaTComma{+l}:
227      AaItB.norm.comma(d0, empty, b, T.AaTComma{l}, l)
228    case T.AaTLParen{l}:
229      AaItB.paren(b, True{}, T.AaTLParen{l})
230    case T.AaTRParen{l}:
231      AaItB.paren(b, False{}, T.AaTRParen{l})
232    case T.AaTWord{+w, +l}:
233      Bool.pick(AaItB, Bool.and(d0, empty), AaItB.norm.first(T.AaTWord{w, l}, b), AaItB.push(b, T.AaTWord{w, l}))
234    case T.AaTQuoted{q, l}:
235      AaItB.push(b, T.AaTQuoted{q, l})
236
237def AaItB.norm(+b: AaItB, t: T.AaTok) -> AaItB:
238  AaItB.norm.go(t, Nat.is_eq(AaItB.pdepth(b), 0n), AaItB.pend_empty(b), b)
239
240# after @{NAME}: = or += makes it an assignment; anything else a rule
241def AaItB.varcand(+b: AaItB, t: T.AaTok, +line: U32) -> AaItB:
242  match t:
243    case T.AaTWord{+w, +l}:
244      Bool.pick(AaItB, Bool.or(X.AaText.starts(w, "="), X.AaText.starts(w, "+=")), AaItB.mode(AaItB.push(b, T.AaTWord{w, l}), AaImVar{line}), AaItB.norm(AaItB.mode(b, AaImNorm{}), T.AaTWord{w, l}))
245    case _:
246      AaItB.norm(AaItB.mode(b, AaImNorm{}), t)
247
248# an assignment takes the tokens of its line; a comma there is an error
249def AaItB.var.same(same: Bool, +b: AaItB, t: T.AaTok, +line: U32) -> AaItB:
250  match same:
251    case True{}:
252      match t:
253        case T.AaTWord{w, l}:
254          AaItB.push(b, T.AaTWord{w, l})
255        case T.AaTQuoted{q, l}:
256          AaItB.push(b, T.AaTQuoted{q, l})
257        case _:
258          AaItB.err(b, E.AaErrSyntax{AaItB.file(b), line})
259    case False{}:
260      AaItB.norm(AaItB.flush(b, 2n, line), t)
261
262def AaItB.var(+b: AaItB, +t: T.AaTok, +line: U32) -> AaItB:
263  AaItB.var.same(U32.is_eq(T.AaTok.line(t), line), b, t, line)
264
265# the name inside <...>
266def AaIt.magic(+w: List<&2, U32>) -> Maybe<&2, List<&2, U32>>:
267  Bool.pick(Maybe<&2, List<&2, U32>>, Bool.and(U32.is_eq(X.AaText.head(w), 60), U32.is_eq(X.AaText.last(w, 0), 62)), Some{X.AaText.init(X.AaText.tail(w))}, None{})
268
269def AaItB.inc.magic(m: Maybe<&2, List<&2, U32>>, +b: AaItB, opt: Bool, +line: U32) -> AaItB:
270  match m:
271    case None{}:
272      AaItB.err(b, E.AaErrSyntax{AaItB.file(b), line})
273    case Some{n}:
274      AaItB.emit(b, AaIInclude{n, opt, E.AaSrc{AaItB.file(b), line}})
275
276def AaItB.inc.path(+b: AaItB, t: T.AaTok, +opt: Bool, +line: U32) -> AaItB:
277  match t:
278    case T.AaTWord{w, l}:
279      AaItB.inc.magic(AaIt.magic(w), b, opt, line)
280    case T.AaTQuoted{q, l}:
281      AaItB.emit(b, AaIInclude{q, opt, E.AaSrc{AaItB.file(b), line}})
282    case _:
283      AaItB.err(b, E.AaErrSyntax{AaItB.file(b), line})
284
285def AaItB.inc(stage: Nat, +b: AaItB, +t: T.AaTok, +line: U32) -> AaItB:
286  match stage:
287    case 0n:
288      Bool.pick(AaItB, T.AaTok.is(t, "if"), AaItB.mode(b, AaImInc{1n, line}), AaItB.inc.path(b, t, False{}, line))
289    case 1n+s:
290      match s:
291        case 0n:
292          Bool.pick(AaItB, T.AaTok.is(t, "exists"), AaItB.mode(b, AaImInc{2n, line}), AaItB.err(b, E.AaErrSyntax{AaItB.file(b), line}))
293        case 1n+r:
294          AaItB.inc.path(b, t, True{}, line)
295
296def AaItB.step(b: AaItB, +t: T.AaTok) -> AaItB:
297  match b:
298    case AaItB{f, m, pe, pd, out}:
299      match m:
300        case AaImNorm{}:
301          AaItB.norm(AaItB{f, AaImNorm{}, pe, pd, out}, t)
302        case AaImVarCand{+l}:
303          AaItB.varcand(AaItB{f, AaImVarCand{l}, pe, pd, out}, t, l)
304        case AaImVar{+l}:
305          AaItB.var(AaItB{f, AaImVar{l}, pe, pd, out}, t, l)
306        case AaImInc{+s, +l}:
307          AaItB.inc(s, AaItB{f, AaImInc{s, l}, pe, pd, out}, t, l)
308
309def AaItB.run(ts: List<&2, T.AaTok>, b: AaItB) -> AaItB:
310  match ts:
311    case Nil{}:
312      b
313    case Con{t, rest}:
314      AaItB.run(rest, AaItB.step(b, t))
315
316def AaItB.finish(b: AaItB) -> List<&2, AaItem>:
317  match b:
318    case AaItB{+f, m, pe, pd, out}:
319      match m:
320        case AaImNorm{}:
321          match pe:
322            case Nil{}:
323              AaItem.rev_onto(out, Nil{})
324            case Con{h, t}:
325              AaItem.rev_onto(Con{AaIError{E.AaErrSyntax{f, T.AaTok.line(h)}}, out}, Nil{})
326        case AaImVarCand{+l}:
327          AaItem.rev_onto(Con{AaIError{E.AaErrSyntax{f, l}}, out}, Nil{})
328        case AaImVar{+l}:
329          AaItem.rev_onto(Con{AaIVar{T.AaTok.rev_onto(pe, Nil{}), E.AaSrc{f, l}}, out}, Nil{})
330        case AaImInc{+s, +l}:
331          AaItem.rev_onto(Con{AaIError{E.AaErrSyntax{f, l}}, out}, Nil{})
332
333def AaIt.of_lex(r: Result<&2, &2, U32, List<&2, T.AaTok>>, +file: List<&2, U32>) -> List<&2, AaItem>:
334  match r:
335    case Fail{l}:
336      [AaIError{E.AaErrLex{file, l}}]
337    case Done{ts}:
338      AaItB.finish(AaItB.run(ts, AaItB{file, AaImNorm{}, Nil{}, 0n, Nil{}}))
339
340# the items of a file's text; a fault becomes an error item
341def AaParse.items(+file: List<&2, U32>, text: List<&2, U32>) -> List<&2, AaItem>:
342  AaIt.of_lex(T.AaTok.lex(text), file)
343
344# Includes
345
346type AaExpOut is Data:
347  AaExpOut{items: List<&2, AaItem>, scopes: List<&2, List<&2, List<&2, U32>>>}
348
349def AaExpOut.items(o: AaExpOut) -> List<&2, AaItem>:
350  match o:
351    case AaExpOut{i, s}:
352      i
353
354def AaExpOut.scopes(o: AaExpOut) -> List<&2, List<&2, List<&2, U32>>>:
355  match o:
356    case AaExpOut{i, s}:
357      s
358
359def AaParse.find_file(fs: List<&2, AaSrcFile>, +name: List<&2, U32>) -> Maybe<&2, AaSrcFile>:
360  match fs:
361    case Nil{}:
362      None{}
363    case Con{+f, t}:
364      match f:
365        case AaSrcFile{n, x}:
366          Bool.pick(Maybe<&2, AaSrcFile>, X.AaText.eq(n, name), Some{f}, AaParse.find_file(t, name))
367
368# the file is already included in the innermost block
369def AaParse.seen(scopes: List<&2, List<&2, List<&2, U32>>>, +name: List<&2, U32>) -> Bool:
370  match scopes:
371    case Nil{}:
372      False{}
373    case Con{s, t}:
374      X.AaText.member(s, name)
375
376def AaParse.mark(scopes: List<&2, List<&2, List<&2, U32>>>, name: List<&2, U32>) -> List<&2, List<&2, List<&2, U32>>>:
377  match scopes:
378    case Nil{}:
379      [[name]]
380    case Con{s, t}:
381      Con{Con{name, s}, t}
382
383def AaParse.pop(scopes: List<&2, List<&2, List<&2, U32>>>) -> List<&2, List<&2, List<&2, U32>>>:
384  match scopes:
385    case Nil{}:
386      Nil{}
387    case Con{s, t}:
388      t
389
390# the items an include brings in
391def AaParse.inc_items(m: Maybe<&2, AaSrcFile>, opt: Bool, src: E.AaSrc) -> List<&2, AaItem>:
392  match m:
393    case None{}:
394      match opt:
395        case True{}:
396          Nil{}
397        case False{}:
398          [AaIError{E.AaSrc.include(src)}]
399    case Some{f}:
400      match f:
401        case AaSrcFile{+n, x}:
402          AaParse.items(n, x)
403
404# includes nested too deep
405def AaParse.expand0(items: List<&2, AaItem>, +scopes: List<&2, List<&2, List<&2, U32>>>, +acc: List<&2, AaItem>) -> AaExpOut:
406  match items:
407    case Nil{}:
408      AaExpOut{AaItem.rev_onto(acc, Nil{}), scopes}
409    case Con{it, rest}:
410      match it:
411        case AaIOpen{ts, src}:
412          AaParse.expand0(rest, Con{Nil{}, scopes}, Con{AaIOpen{ts, src}, acc})
413        case AaIClose{src}:
414          AaParse.expand0(rest, AaParse.pop(scopes), Con{AaIClose{src}, acc})
415        case AaIInclude{+name, opt, src}:
416          AaParse.expand0(rest, scopes, Bool.pick(List<&2, AaItem>, AaParse.seen(scopes, name), acc, Con{AaIError{E.AaSrc.include_depth(src)}, acc}))
417        case AaIStmt{ts, src}:
418          AaParse.expand0(rest, scopes, Con{AaIStmt{ts, src}, acc})
419        case AaIVar{ts, src}:
420          AaParse.expand0(rest, scopes, Con{AaIVar{ts, src}, acc})
421        case AaIError{e}:
422          AaParse.expand0(rest, scopes, Con{AaIError{e}, acc})
423
424# the items with every include replaced by the included file's items;
425# scopes: the files included so far in each open block, innermost first
426def AaParse.expand(+fuel: Nat, items: List<&2, AaItem>, +files: List<&2, AaSrcFile>, +scopes: List<&2, List<&2, List<&2, U32>>>, +acc: List<&2, AaItem>) -> AaExpOut:
427  match fuel:
428    case 0n:
429      AaParse.expand0(items, scopes, acc)
430    case 1n+f:
431      match items:
432        case Nil{}:
433          AaExpOut{AaItem.rev_onto(acc, Nil{}), scopes}
434        case Con{it, +rest}:
435          match it:
436            case AaIOpen{ts, src}:
437              AaParse.expand(fuel, rest, files, Con{Nil{}, scopes}, Con{AaIOpen{ts, src}, acc})
438            case AaIClose{src}:
439              AaParse.expand(fuel, rest, files, AaParse.pop(scopes), Con{AaIClose{src}, acc})
440            case AaIInclude{+name, opt, +src}:
441              # Bend evaluates both arms of a Bool.pick, so the recursion is
442              # one call whose arguments depend on whether the file is seen
443              +seen = AaParse.seen(scopes, name)
444              +sub = AaParse.expand(f, AaParse.inc_items(Bool.pick(Maybe<&2, AaSrcFile>, seen, None{}, AaParse.find_file(files, name)), Bool.or(seen, opt), src), files, AaParse.mark(scopes, name), Nil{})
445              AaParse.expand(fuel, rest, files, Bool.pick(List<&2, List<&2, List<&2, U32>>>, seen, scopes, AaExpOut.scopes(sub)), Bool.pick(List<&2, AaItem>, seen, acc, AaItem.rev_onto(AaExpOut.items(sub), acc)))
446            case AaIStmt{ts, src}:
447              AaParse.expand(fuel, rest, files, scopes, Con{AaIStmt{ts, src}, acc})
448            case AaIVar{ts, src}:
449              AaParse.expand(fuel, rest, files, scopes, Con{AaIVar{ts, src}, acc})
450            case AaIError{e}:
451              AaParse.expand(fuel, rest, files, scopes, Con{AaIError{e}, acc})
452
453def AaParse.first_error(items: List<&2, AaItem>) -> Maybe<&2, E.AaError>:
454  match items:
455    case Nil{}:
456      None{}
457    case Con{it, rest}:
458      match it:
459        case AaIError{e}:
460          Some{e}
461        case _:
462          AaParse.first_error(rest)
463
464# Variables
465
466def AaVars.find(env: List<&2, O.AaVar>, +name: List<&2, U32>) -> Maybe<&2, List<&2, List<&2, U32>>>:
467  match env:
468    case Nil{}:
469      None{}
470    case Con{v, t}:
471      match v:
472        case O.AaVar{n, +vals}:
473          Bool.pick(Maybe<&2, List<&2, List<&2, U32>>>, X.AaText.eq(n, name), Some{vals}, AaVars.find(t, name))
474
475def AaVars.is_some(m: Maybe<&2, List<&2, List<&2, U32>>>) -> Bool:
476  match m:
477    case None{}:
478      False{}
479    case Some{x}:
480      True{}
481
482# values added to a variable's
483def AaVars.extend(env: List<&2, O.AaVar>, +name: List<&2, U32>, +more: List<&2, List<&2, U32>>) -> List<&2, O.AaVar>:
484  match env:
485    case Nil{}:
486      Nil{}
487    case Con{v, t}:
488      match v:
489        case O.AaVar{+n, +vals}:
490          Con{Bool.pick(O.AaVar, X.AaText.eq(n, name), O.AaVar{n, X.AaText.lappend(vals, more)}, O.AaVar{n, vals}), AaVars.extend(t, name, more)}
491
492# a variable name: a letter, then letters, digits and '_'
493def AaVars.name_ok.go(xs: List<&2, U32>) -> Bool:
494  match xs:
495    case Nil{}:
496      True{}
497    case Con{+h, t}:
498      Bool.and(Bool.or(T.AaTerm.letter(h), Bool.or(Bool.and(U32.is_ge(h, 48), U32.is_le(h, 57)), U32.is_eq(h, 95))), AaVars.name_ok.go(t))
499
500def AaVars.name_ok(xs: List<&2, U32>) -> Bool:
501  match xs:
502    case Nil{}:
503      False{}
504    case Con{+h, t}:
505      Bool.and(T.AaTerm.letter(h), AaVars.name_ok.go(t))
506
507# @{NAME} at the start of a word: the name and what follows the '}'
508type AaVRef is Data:
509  AaVRef{name: List<&2, U32>, rest: List<&2, U32>}
510
511def AaVars.ref.go(xs: List<&2, U32>, +acc: List<&2, U32>) -> Maybe<&2, AaVRef>:
512  match xs:
513    case Nil{}:
514      None{}
515    case Con{+h, +t}:
516      Bool.pick(Maybe<&2, AaVRef>, U32.is_eq(h, 125), Some{AaVRef{X.AaText.rev(acc), t}}, AaVars.ref.go(t, Con{h, acc}))
517
518def AaVars.ref(+w: List<&2, U32>) -> Maybe<&2, AaVRef>:
519  Bool.pick(Maybe<&2, AaVRef>, X.AaText.starts(w, "@{"), AaVars.ref.go(X.AaText.drop(2n, w), Nil{}), None{})
520
521def AaVars.values.cons(x: List<&2, U32>, r: Result<&2, &2, E.AaError, List<&2, List<&2, U32>>>) -> Result<&2, &2, E.AaError, List<&2, List<&2, U32>>>:
522  match r:
523    case Fail{e}:
524      Fail{e}
525    case Done{xs}:
526      Done{Con{x, xs}}
527
528# the text of each word or string
529def AaVars.values(ts: List<&2, T.AaTok>, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, List<&2, U32>>>:
530  match ts:
531    case Nil{}:
532      Done{Nil{}}
533    case Con{t, rest}:
534      match t:
535        case T.AaTWord{w, l}:
536          AaVars.values.cons(w, AaVars.values(rest, src))
537        case T.AaTQuoted{q, l}:
538          AaVars.values.cons(q, AaVars.values(rest, src))
539        case _:
540          Fail{E.AaSrc.syntax(src)}
541
542# an assignment: name, whether it is +=, the values
543type AaAssign is Data:
544  AaAssign{name: List<&2, U32>, append: Bool, values: List<&2, List<&2, U32>>}
545
546# the operator at the start of x: += (True) or = (False), and what follows it
547def AaVars.op(+x: List<&2, U32>) -> Maybe<&2, AaVRef>:
548  Bool.pick(Maybe<&2, AaVRef>, X.AaText.starts(x, "+="), Some{AaVRef{[1], X.AaText.drop(2n, x)}}, Bool.pick(Maybe<&2, AaVRef>, X.AaText.starts(x, "="), Some{AaVRef{Nil{}, X.AaText.drop(1n, x)}}, None{}))
549
550def AaVars.first(rem: List<&2, U32>) -> List<&2, List<&2, U32>>:
551  match rem:
552    case Nil{}:
553      Nil{}
554    case Con{h, t}:
555      [Con{h, t}]
556
557def AaVars.assign.op(m: Maybe<&2, AaVRef>, name: List<&2, U32>, rest: List<&2, T.AaTok>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaAssign>:
558  match m:
559    case None{}:
560      Fail{E.AaSrc.syntax(src)}
561    case Some{o}:
562      match o:
563        case AaVRef{tag, rem}:
564          do Result<&2, &2, E.AaError, AaAssign>:
565            vs : List<&2, List<&2, U32>> <- AaVars.values(rest, src)
566            return AaAssign{name, Bool.not(X.AaText.is_nil(tag)), X.AaText.lappend(AaVars.first(rem), vs)}
567
568# @{NAME} then a separate = or += word
569def AaVars.assign.next(ts: List<&2, T.AaTok>, name: List<&2, U32>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaAssign>:
570  match ts:
571    case Nil{}:
572      Fail{E.AaSrc.syntax(src)}
573    case Con{t, rest}:
574      match t:
575        case T.AaTWord{+w, l}:
576          AaVars.assign.op(AaVars.op(w), name, rest, src)
577        case _:
578          Fail{E.AaSrc.syntax(src)}
579
580def AaVars.assign.ref(m: Maybe<&2, AaVRef>, +rest: List<&2, T.AaTok>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaAssign>:
581  match m:
582    case None{}:
583      Fail{E.AaSrc.syntax(src)}
584    case Some{r}:
585      match r:
586        case AaVRef{+name, +after}:
587          Bool.pick(Result<&2, &2, E.AaError, AaAssign>, AaVars.name_ok(name), Bool.pick(Result<&2, &2, E.AaError, AaAssign>, X.AaText.is_nil(after), AaVars.assign.next(rest, name, src), AaVars.assign.op(AaVars.op(after), name, rest, src)), Fail{E.AaSrc.syntax(src)})
588
589def AaVars.assign.parse(ts: List<&2, T.AaTok>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaAssign>:
590  match ts:
591    case Nil{}:
592      Fail{E.AaSrc.syntax(src)}
593    case Con{t, rest}:
594      match t:
595        case T.AaTWord{+w, l}:
596          AaVars.assign.ref(AaVars.ref(w), rest, src)
597        case _:
598          Fail{E.AaSrc.syntax(src)}
599
600# the environment after an assignment
601def AaVars.apply.go(ap: Bool, known: Bool, env: List<&2, O.AaVar>, name: List<&2, U32>, vs: List<&2, List<&2, U32>>, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, O.AaVar>>:
602  match ap:
603    case True{}:
604      match known:
605        case True{}:
606          Done{AaVars.extend(env, name, vs)}
607        case False{}:
608          Fail{E.AaSrc.var_redefined(src)}
609    case False{}:
610      match known:
611        case True{}:
612          Fail{E.AaSrc.var_redefined(src)}
613        case False{}:
614          Done{Con{O.AaVar{name, vs}, env}}
615
616def AaVars.apply(a: AaAssign, +env: List<&2, O.AaVar>, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, O.AaVar>>:
617  match a:
618    case AaAssign{+name, ap, vs}:
619      AaVars.apply.go(ap, AaVars.is_some(AaVars.find(env, name)), env, name, vs, src)
620
621# Expanding variables in a text
622
623def AaVars.unslash(+v: List<&2, U32>) -> List<&2, U32>:
624  Bool.pick(List<&2, U32>, U32.is_eq(X.AaText.last(v, 0), 47), X.AaText.init(v), v)
625
626def AaVars.value(strip: Bool, +v: List<&2, U32>) -> List<&2, U32>:
627  match strip:
628    case True{}:
629      AaVars.unslash(v)
630    case False{}:
631      v
632
633# the values, separated by commas, last character first, onto acc
634def AaVars.join(vs: List<&2, List<&2, U32>>, +strip: Bool, +first: Bool, +acc: List<&2, U32>) -> List<&2, U32>:
635  match vs:
636    case Nil{}:
637      acc
638    case Con{v, t}:
639      AaVars.join(t, strip, False{}, X.AaText.rev_onto(AaVars.value(strip, v), Bool.pick(List<&2, U32>, first, acc, Con{44, acc})))
640
641# the expansion of one reference (last character first, onto out)
642def AaVars.emit(m: Maybe<&2, List<&2, List<&2, U32>>>, +strip: Bool, out: List<&2, U32>) -> Maybe<&2, List<&2, U32>>:
643  match m:
644    case None{}:
645      None{}
646    case Some{vs}:
647      match vs:
648        case Nil{}:
649          None{}
650        case Con{v, rest}:
651          match rest:
652            case Nil{}:
653              Some{X.AaText.rev_onto(AaVars.value(strip, v), out)}
654            case Con{w, more}:
655              Some{Con{125, AaVars.join(Con{v, Con{w, more}}, strip, True{}, Con{123, out})}}
656
657def AaVars.lookup(+env: List<&2, O.AaVar>, pname: Maybe<&2, List<&2, U32>>, +name: List<&2, U32>) -> Maybe<&2, List<&2, List<&2, U32>>>:
658  match pname:
659    case None{}:
660      AaVars.find(env, name)
661    case Some{p}:
662      Bool.pick(Maybe<&2, List<&2, List<&2, U32>>>, X.AaText.is(name, "profile_name"), Some{[p]}, AaVars.find(env, name))
663
664# the substitution: mode (0 text, 1 after an at sign, 2 inside a reference),
665# the name read so far, the output (both last character first), still well formed
666type AaSb is Data:
667  AaSb{mode: Nat, name: List<&2, U32>, out: List<&2, U32>, ok: Bool}
668
669def AaSb.close(r: Maybe<&2, List<&2, U32>>, ok: Bool) -> AaSb:
670  match r:
671    case None{}:
672      AaSb{0n, Nil{}, Nil{}, False{}}
673    case Some{out}:
674      AaSb{0n, Nil{}, out, ok}
675
676def AaSb.text(+h: U32, +out: List<&2, U32>, +ok: Bool) -> AaSb:
677  Bool.pick(AaSb, U32.is_eq(h, 64), AaSb{1n, Nil{}, out, ok}, AaSb{0n, Nil{}, Con{h, out}, ok})
678
679def AaSb.step(st: AaSb, +h: U32, +next: U32, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>) -> AaSb:
680  match st:
681    case AaSb{m, +nm, +out, +ok}:
682      match m:
683        case 0n:
684          AaSb.text(h, out, ok)
685        case 1n+m1:
686          match m1:
687            case 0n:
688              Bool.pick(AaSb, U32.is_eq(h, 123), AaSb{2n, Nil{}, out, ok}, AaSb.text(h, Con{64, out}, ok))
689            case 1n+m2:
690              Bool.pick(AaSb, U32.is_eq(h, 125), AaSb.close(AaVars.emit(AaVars.lookup(env, pname, X.AaText.rev(nm)), U32.is_eq(next, 47), out), ok), AaSb{2n, Con{h, nm}, out, ok})
691
692def AaSb.run(xs: List<&2, U32>, st: AaSb, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>) -> AaSb:
693  match xs:
694    case Nil{}:
695      st
696    case Con{+h, +t}:
697      AaSb.run(t, AaSb.step(st, h, X.AaText.head(t), env, pname), env, pname)
698
699def AaSb.result(st: AaSb) -> Maybe<&2, List<&2, U32>>:
700  match st:
701    case AaSb{m, nm, out, ok}:
702      match m:
703        case 0n:
704          match ok:
705            case False{}:
706              None{}
707            case True{}:
708              Some{X.AaText.rev(out)}
709        case 1n+m1:
710          match m1:
711            case 0n:
712              match ok:
713                case False{}:
714                  None{}
715                case True{}:
716                  Some{X.AaText.rev(Con{64, out})}
717            case 1n+m2:
718              None{}
719
720# one round: every reference replaced by its values
721def AaVars.subst(xs: List<&2, U32>, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>) -> Maybe<&2, List<&2, U32>>:
722  AaSb.result(AaSb.run(xs, AaSb{0n, Nil{}, Nil{}, True{}}, env, pname))
723
724# a reference remains: an at sign before an opening brace
725def AaVars.has_ref(xs: List<&2, U32>) -> Bool:
726  match xs:
727    case Nil{}:
728      False{}
729    case Con{+h, +t}:
730      Bool.or(Bool.and(U32.is_eq(h, 64), U32.is_eq(X.AaText.head(t), 123)), AaVars.has_ref(t))
731
732# whether a text still holds a reference; None stays None
733def AaVars.more(m: Maybe<&2, List<&2, U32>>) -> Bool:
734  match m:
735    case None{}:
736      False{}
737    case Some{t}:
738      AaVars.has_ref(t)
739
740# more: m still holds a reference (computed by the caller, so that a text
741# without one is not substituted again)
742def AaVars.expand.go(fuel: Nat, +more: Bool, m: Maybe<&2, List<&2, U32>>, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>) -> Maybe<&2, List<&2, U32>>:
743  match fuel:
744    case 0n:
745      match more:
746        case True{}:
747          None{}
748        case False{}:
749          m
750    case 1n+f:
751      match more:
752        case False{}:
753          m
754        case True{}:
755          match m:
756            case None{}:
757              None{}
758            case Some{t}:
759              +n = AaVars.subst(t, env, pname)
760              AaVars.expand.go(f, AaVars.more(n), n, env, pname)
761
762# xs with its variables expanded (values may refer to variables, to a depth
763# of eight); None if a variable is not assigned
764def AaVars.expand(+xs: List<&2, U32>, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>) -> Maybe<&2, List<&2, U32>>:
765  AaVars.expand.go(8n, AaVars.has_ref(xs), Some{xs}, env, pname)
766
767def AaVars.tok.go(m: Maybe<&2, List<&2, U32>>, quoted: Bool, l: U32, src: E.AaSrc) -> Result<&2, &2, E.AaError, T.AaTok>:
768  match m:
769    case None{}:
770      Fail{E.AaSrc.var_undefined(src)}
771    case Some{x}:
772      match quoted:
773        case True{}:
774          Done{T.AaTQuoted{x, l}}
775        case False{}:
776          Done{T.AaTWord{x, l}}
777
778def AaVars.tok(t: T.AaTok, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>, src: E.AaSrc) -> Result<&2, &2, E.AaError, T.AaTok>:
779  match t:
780    case T.AaTWord{w, l}:
781      AaVars.tok.go(AaVars.expand(w, env, pname), False{}, l, src)
782    case T.AaTQuoted{q, l}:
783      AaVars.tok.go(AaVars.expand(q, env, pname), True{}, l, src)
784    case T.AaTOpen{l}:
785      Done{T.AaTOpen{l}}
786    case T.AaTClose{l}:
787      Done{T.AaTClose{l}}
788    case T.AaTLParen{l}:
789      Done{T.AaTLParen{l}}
790    case T.AaTRParen{l}:
791      Done{T.AaTRParen{l}}
792    case T.AaTComma{l}:
793      Done{T.AaTComma{l}}
794
795# a statement's tokens with variables expanded
796def AaVars.toks(ts: List<&2, T.AaTok>, +env: List<&2, O.AaVar>, +pname: Maybe<&2, List<&2, U32>>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, T.AaTok>>:
797  match ts:
798    case Nil{}:
799      Done{Nil{}}
800    case Con{t, rest}:
801      do Result<&2, &2, E.AaError, List<&2, T.AaTok>>:
802        x : T.AaTok <- AaVars.tok(t, env, pname, src)
803        xs : List<&2, T.AaTok> <- AaVars.toks(rest, env, pname, src)
804        return Con{x, xs}
805
806# The preamble: variables and aliases
807
808type AaAlias is Data:
809  AaAlias{from: List<&2, U32>, to: List<&2, U32>}
810
811type AaPre is Data:
812  AaPre{env: List<&2, O.AaVar>, aliases: List<&2, AaAlias>, depth: Nat, started: Bool, err: Maybe<&2, E.AaError>}
813
814def AaPre.fail(p: AaPre, e: E.AaError) -> AaPre:
815  match p:
816    case AaPre{v, a, d, s, err}:
817      match err:
818        case None{}:
819          AaPre{v, a, d, s, Some{e}}
820        case Some{e0}:
821          AaPre{v, a, d, s, Some{e0}}
822
823def AaPre.env(r: Result<&2, &2, E.AaError, List<&2, O.AaVar>>, p: AaPre) -> AaPre:
824  match r:
825    case Fail{e}:
826      AaPre.fail(p, e)
827    case Done{v}:
828      match p:
829        case AaPre{v0, a, d, s, err}:
830          AaPre{v, a, d, s, err}
831
832def AaPre.assign(r: Result<&2, &2, E.AaError, AaAssign>, +p: AaPre, src: E.AaSrc) -> AaPre:
833  match r:
834    case Fail{e}:
835      AaPre.fail(p, e)
836    case Done{a}:
837      match p:
838        case AaPre{+v, al, d, s, err}:
839          AaPre.env(AaVars.apply(a, v, src), AaPre{v, al, d, s, err})
840
841def AaPre.text(t: T.AaTok) -> Maybe<&2, List<&2, U32>>:
842  match t:
843    case T.AaTWord{w, l}:
844      Some{w}
845    case T.AaTQuoted{q, l}:
846      Some{q}
847    case _:
848      None{}
849
850# alias FROM -> TO
851def AaPre.alias.go(a: Maybe<&2, List<&2, U32>>, b: Maybe<&2, List<&2, U32>>, p: AaPre, src: E.AaSrc) -> AaPre:
852  match a:
853    case None{}:
854      AaPre.fail(p, E.AaSrc.syntax(src))
855    case Some{x}:
856      match b:
857        case None{}:
858          AaPre.fail(p, E.AaSrc.syntax(src))
859        case Some{y}:
860          match p:
861            case AaPre{v, al, d, s, err}:
862              AaPre{v, Con{AaAlias{x, y}, al}, d, s, err}
863
864def AaPre.alias(ts: List<&2, T.AaTok>, +p: AaPre, +src: E.AaSrc) -> AaPre:
865  match ts:
866    case Con{a, r1}:
867      match r1:
868        case Con{arrow, r2}:
869          match r2:
870            case Con{b, r3}:
871              match r3:
872                case Nil{}:
873                  Bool.pick(AaPre, T.AaTok.is(arrow, "->"), AaPre.alias.go(AaPre.text(a), AaPre.text(b), p, src), AaPre.fail(p, E.AaSrc.syntax(src)))
874                case Con{x, r4}:
875                  AaPre.fail(p, E.AaSrc.syntax(src))
876            case Nil{}:
877              AaPre.fail(p, E.AaSrc.syntax(src))
878        case Nil{}:
879          AaPre.fail(p, E.AaSrc.syntax(src))
880    case Nil{}:
881      AaPre.fail(p, E.AaSrc.syntax(src))
882
883def AaPre.depth(p: AaPre) -> Nat:
884  match p:
885    case AaPre{v, a, d, s, err}:
886      d
887
888def AaPre.started(p: AaPre) -> Bool:
889  match p:
890    case AaPre{v, a, d, s, err}:
891      s
892
893# a statement at the top level: alias (before the first profile), abi, or an error
894def AaPre.top(ts: List<&2, T.AaTok>, +p: AaPre, +src: E.AaSrc) -> AaPre:
895  match ts:
896    case Nil{}:
897      AaPre.fail(p, E.AaSrc.syntax(src))
898    case Con{+t, rest}:
899      Bool.pick(AaPre, T.AaTok.is(t, "alias"), Bool.pick(AaPre, AaPre.started(p), AaPre.fail(p, E.AaSrc.preamble(src)), AaPre.alias(rest, p, src)), Bool.pick(AaPre, T.AaTok.is(t, "abi"), p, AaPre.fail(p, E.AaSrc.syntax(src))))
900
901def AaPre.stmt(d: Nat, ts: List<&2, T.AaTok>, +p: AaPre, +src: E.AaSrc) -> AaPre:
902  match d:
903    case 0n:
904      AaPre.top(ts, p, src)
905    case 1n+e:
906      p
907
908def AaPre.var(ok: Bool, ts: List<&2, T.AaTok>, +p: AaPre, +src: E.AaSrc) -> AaPre:
909  match ok:
910    case True{}:
911      AaPre.assign(AaVars.assign.parse(ts, src), p, src)
912    case False{}:
913      AaPre.fail(p, E.AaSrc.preamble(src))
914
915def AaPre.open(p: AaPre) -> AaPre:
916  match p:
917    case AaPre{v, a, d, s, err}:
918      AaPre{v, a, 1n+d, True{}, err}
919
920def AaPre.close(p: AaPre, src: E.AaSrc) -> AaPre:
921  match p:
922    case AaPre{v, a, d, s, err}:
923      match d:
924        case 0n:
925          AaPre.fail(AaPre{v, a, 0n, s, err}, E.AaSrc.syntax(src))
926        case 1n+e:
927          AaPre{v, a, e, s, err}
928
929def AaPre.step(+p: AaPre, it: AaItem) -> AaPre:
930  match it:
931    case AaIVar{ts, src}:
932      AaPre.var(Bool.and(Nat.is_eq(AaPre.depth(p), 0n), Bool.not(AaPre.started(p))), ts, p, src)
933    case AaIStmt{ts, src}:
934      AaPre.stmt(AaPre.depth(p), ts, p, src)
935    case AaIOpen{ts, src}:
936      AaPre.open(p)
937    case AaIClose{src}:
938      AaPre.close(p, src)
939    case AaIInclude{n, o, src}:
940      p
941    case AaIError{e}:
942      AaPre.fail(p, e)
943
944def AaPre.run(items: List<&2, AaItem>, p: AaPre) -> AaPre:
945  match items:
946    case Nil{}:
947      p
948    case Con{it, rest}:
949      AaPre.run(rest, AaPre.step(p, it))
950
951# Qualifiers: [audit] [allow | deny] [owner], in that order; a block's
952# qualifiers apply to the rules inside it. access: 0 neither, 1 allow, 2 deny
953
954type AaBQ is Data:
955  AaBQ{audit: Bool, access: Nat, owner: Bool}
956
957def AaBQ.none() -> AaBQ:
958  AaBQ{False{}, 0n, False{}}
959
960# what a term is as a qualifier: 1 audit, 2 allow, 3 deny, 4 owner, 0 none
961def AaQ.kind(+t: T.AaTerm) -> Nat:
962  Bool.pick(Nat, T.AaTerm.is(t, "audit"), 1n, Bool.pick(Nat, T.AaTerm.is(t, "allow"), 2n, Bool.pick(Nat, T.AaTerm.is(t, "deny"), 3n, Bool.pick(Nat, T.AaTerm.is(t, "owner"), 4n, 0n))))
963
964# the qualifier k may come at stage s (0 start, 1 after audit, 2 after allow
965# or deny, 3 after owner), and the stage after it
966def AaQ.allowed(k: Nat, s: Nat) -> Bool:
967  match k:
968    case 0n:
969      False{}
970    case 1n+k1:
971      match k1:
972        case 0n:
973          Nat.is_eq(s, 0n)
974        case 1n+k2:
975          match k2:
976            case 0n:
977              Nat.is_le(s, 1n)
978            case 1n+k3:
979              match k3:
980                case 0n:
981                  Nat.is_le(s, 1n)
982                case 1n+k4:
983                  Nat.is_le(s, 2n)
984
985def AaQ.stage(k: Nat) -> Nat:
986  match k:
987    case 0n:
988      0n
989    case 1n+k1:
990      match k1:
991        case 0n:
992          1n
993        case 1n+k2:
994          match k2:
995            case 0n:
996              2n
997            case 1n+k3:
998              match k3:
999                case 0n:
1000                  2n
1001                case 1n+k4:
1002                  3n
1003
1004def AaQ.add(q: AaBQ, k: Nat) -> AaBQ:
1005  match q:
1006    case AaBQ{a, c, o}:
1007      match k:
1008        case 0n:
1009          AaBQ{a, c, o}
1010        case 1n+k1:
1011          match k1:
1012            case 0n:
1013              AaBQ{True{}, c, o}
1014            case 1n+k2:
1015              match k2:
1016                case 0n:
1017                  AaBQ{a, 1n, o}
1018                case 1n+k3:
1019                  match k3:
1020                    case 0n:
1021                      AaBQ{a, 2n, o}
1022                    case 1n+k4:
1023                      AaBQ{a, c, True{}}
1024
1025type AaQP is Data:
1026  AaQP{q: AaBQ, rest: List<&2, T.AaTerm>}
1027
1028def AaQ.parse.go(+k: Nat, +t: T.AaTerm, +rest: List<&2, T.AaTerm>, +stage: Nat, +q: AaBQ, more: AaQP) -> AaQP:
1029  Bool.pick(AaQP, AaQ.allowed(k, stage), more, AaQP{q, Con{t, rest}})
1030
1031# the leading qualifiers of a rule's terms, and the terms after them
1032def AaQ.parse(ts: List<&2, T.AaTerm>, +stage: Nat, +q: AaBQ) -> AaQP:
1033  match ts:
1034    case Nil{}:
1035      AaQP{q, Nil{}}
1036    case Con{+t, +rest}:
1037      +k = AaQ.kind(t)
1038      AaQ.parse.go(k, t, rest, stage, q, AaQ.parse(rest, AaQ.stage(k), AaQ.add(q, k)))
1039
1040# a block's qualifiers with a rule's (or an inner block's): allow and deny conflict
1041def AaQ.combine(b: AaBQ, r: AaBQ, src: E.AaSrc) -> Result<&2, &2, E.AaError, AaBQ>:
1042  match b:
1043    case AaBQ{a1, +c1, o1}:
1044      match r:
1045        case AaBQ{a2, +c2, o2}:
1046          Bool.pick(Result<&2, &2, E.AaError, AaBQ>, Bool.and(Bool.and(Nat.is_gt(c1, 0n), Nat.is_gt(c2, 0n)), Bool.not(Nat.is_eq(c1, c2))), Fail{E.AaSrc.syntax(src)}, Done{AaBQ{Bool.or(a1, a2), Nat.max(c1, c2), Bool.or(o1, o2)}})
1047
1048def AaBQ.qual(q: AaBQ) -> P.AaQual:
1049  match q:
1050    case AaBQ{a, c, o}:
1051      P.AaQual{a, Nat.is_eq(c, 2n), o}
1052
1053# Rules
1054
1055type AaCtx is Data:
1056  AaCtx{env: List<&2, O.AaVar>, aliases: List<&2, AaAlias>}
1057
1058def AaCtx.env(c: AaCtx) -> List<&2, O.AaVar>:
1059  match c:
1060    case AaCtx{e, a}:
1061      e
1062
1063def AaCtx.aliases(c: AaCtx) -> List<&2, AaAlias>:
1064  match c:
1065    case AaCtx{e, a}:
1066      a
1067
1068def AaParse.unsupported() -> List<&2, String>:
1069  ["link", "unix", "pivot_root", "set", "mqueue", "io_uring", "userns", "all", "rlimit"]
1070
1071# the keyword of a rule: 1 capability, 2 network, 3 signal, 4 ptrace, 5
1072# mount, 6 remount, 7 umount, 8 dbus, 9 change_profile, 10 file, 11 a rule
1073# kind outside the model, 12 a statement that belongs in the preamble, 0 none
1074# (a file rule without the keyword)
1075def AaParse.keyword(+t: T.AaTerm) -> Nat:
1076  Bool.pick(Nat, T.AaTerm.is(t, "capability"), 1n, Bool.pick(Nat, T.AaTerm.is(t, "network"), 2n, Bool.pick(Nat, T.AaTerm.is(t, "signal"), 3n, Bool.pick(Nat, T.AaTerm.is(t, "ptrace"), 4n, Bool.pick(Nat, T.AaTerm.is(t, "mount"), 5n, Bool.pick(Nat, T.AaTerm.is(t, "remount"), 6n, Bool.pick(Nat, T.AaTerm.is(t, "umount"), 7n, Bool.pick(Nat, T.AaTerm.is(t, "dbus"), 8n, Bool.pick(Nat, T.AaTerm.is(t, "change_profile"), 9n, Bool.pick(Nat, T.AaTerm.is(t, "file"), 10n, Bool.pick(Nat, T.AaTerm.is_one_of(t, AaParse.unsupported()), 11n, Bool.pick(Nat, Bool.or(T.AaTerm.is(t, "alias"), T.AaTerm.is(t, "abi")), 12n, 0n))))))))))))
1077
1078def AaParse.wrap.cap(r: Result<&2, &2, E.AaError, RC.AaCapRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1079  match r:
1080    case Fail{e}:
1081      Fail{e}
1082    case Done{x}:
1083      Done{PR.AaRules.cap(x)}
1084
1085def AaParse.wrap.net(r: Result<&2, &2, E.AaError, RN.AaNetRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1086  match r:
1087    case Fail{e}:
1088      Fail{e}
1089    case Done{x}:
1090      Done{PR.AaRules.net(x)}
1091
1092def AaParse.wrap.signal(r: Result<&2, &2, E.AaError, RS.AaSignalRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1093  match r:
1094    case Fail{e}:
1095      Fail{e}
1096    case Done{x}:
1097      Done{PR.AaRules.signal(x)}
1098
1099def AaParse.wrap.ptrace(r: Result<&2, &2, E.AaError, RP.AaPtraceRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1100  match r:
1101    case Fail{e}:
1102      Fail{e}
1103    case Done{x}:
1104      Done{PR.AaRules.ptrace(x)}
1105
1106def AaParse.wrap.mount(r: Result<&2, &2, E.AaError, RM.AaMountRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1107  match r:
1108    case Fail{e}:
1109      Fail{e}
1110    case Done{x}:
1111      Done{PR.AaRules.mount(x)}
1112
1113def AaParse.wrap.dbus(r: Result<&2, &2, E.AaError, RD.AaDbusRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1114  match r:
1115    case Fail{e}:
1116      Fail{e}
1117    case Done{x}:
1118      Done{PR.AaRules.dbus_rule(x)}
1119
1120def AaParse.wrap.change(r: Result<&2, &2, E.AaError, RX.AaChangeRule>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1121  match r:
1122    case Fail{e}:
1123      Fail{e}
1124    case Done{x}:
1125      Done{PR.AaRules.change(x)}
1126
1127def AaParse.alias.glob(m: Maybe<&2, G.AaGlob>, r: F.AaFileRule) -> List<&2, F.AaFileRule>:
1128  match m:
1129    case None{}:
1130      Nil{}
1131    case Some{g}:
1132      match r:
1133        case F.AaFileRule{q, text, g0, p, e, tg}:
1134          [F.AaFileRule{q, text, g, p, e, tg}]
1135
1136# a file rule copied under an alias whose source starts its pattern
1137def AaParse.alias.one(a: AaAlias, r: F.AaFileRule) -> List<&2, F.AaFileRule>:
1138  match a:
1139    case AaAlias{+from, +to}:
1140      match r:
1141        case F.AaFileRule{q, +text, g, p, e, tg}:
1142          Bool.pick(List<&2, F.AaFileRule>, Bool.and(Bool.not(X.AaText.is_nil(text)), X.AaText.prefix(text, from)), AaParse.alias.glob(G.AaGlob.parse(X.AaText.append(to, X.AaText.drop(X.AaText.len(from), text))), F.AaFileRule{q, X.AaText.append(to, X.AaText.drop(X.AaText.len(from), text)), g, p, e, tg}), Nil{})
1143
1144def AaParse.alias.all(als: List<&2, AaAlias>, +r: F.AaFileRule) -> List<&2, F.AaFileRule>:
1145  match als:
1146    case Nil{}:
1147      Nil{}
1148    case Con{a, t}:
1149      F.AaFileRule.append(AaParse.alias.one(a, r), AaParse.alias.all(t, r))
1150
1151def AaParse.wrap.file(r: Result<&2, &2, E.AaError, F.AaFileRule>, als: List<&2, AaAlias>) -> Result<&2, &2, E.AaError, PR.AaRules>:
1152  match r:
1153    case Fail{e}:
1154      Fail{e}
1155    case Done{+x}:
1156      Done{PR.AaRules.with_files(PR.AaRules.empty(), Con{x, AaParse.alias.all(als, x)})}
1157
1158def AaParse.dispatch(k: Nat, +t: T.AaTerm, +rest: List<&2, T.AaTerm>, +q: P.AaQual, +als: List<&2, AaAlias>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaRules>:
1159  match k:
1160    case 0n:
1161      AaParse.wrap.file(F.AaFile.parse(Con{t, rest}, q, src), als)
1162    case 1n+k1:
1163      match k1:
1164        case 0n:
1165          AaParse.wrap.cap(RC.AaCap.parse(rest, q, src))
1166        case 1n+k2:
1167          match k2:
1168            case 0n:
1169              AaParse.wrap.net(RN.AaNet.parse(rest, q, src))
1170            case 1n+k3:
1171              match k3:
1172                case 0n:
1173                  AaParse.wrap.signal(RS.AaSignal.parse(rest, q, src))
1174                case 1n+k4:
1175                  match k4:
1176                    case 0n:
1177                      AaParse.wrap.ptrace(RP.AaPtrace.parse(rest, q, src))
1178                    case 1n+k5:
1179                      match k5:
1180                        case 0n:
1181                          AaParse.wrap.mount(RM.AaMount.parse(RM.AaMountOpMount{}, rest, q, src))
1182                        case 1n+k6:
1183                          match k6:
1184                            case 0n:
1185                              AaParse.wrap.mount(RM.AaMount.parse(RM.AaMountOpRemount{}, rest, q, src))
1186                            case 1n+k7:
1187                              match k7:
1188                                case 0n:
1189                                  AaParse.wrap.mount(RM.AaMount.parse(RM.AaMountOpUmount{}, rest, q, src))
1190                                case 1n+k8:
1191                                  match k8:
1192                                    case 0n:
1193                                      AaParse.wrap.dbus(RD.AaDbus.parse(rest, q, src))
1194                                    case 1n+k9:
1195                                      match k9:
1196                                        case 0n:
1197                                          AaParse.wrap.change(RX.AaChange.parse(rest, q, src))
1198                                        case 1n+k10:
1199                                          match k10:
1200                                            case 0n:
1201                                              AaParse.wrap.file(F.AaFile.parse(rest, q, src), als)
1202                                            case 1n+k11:
1203                                              match k11:
1204                                                case 0n:
1205                                                  Fail{E.AaSrc.unsupported(src)}
1206                                                case 1n+k12:
1207                                                  Fail{E.AaSrc.preamble(src)}
1208
1209# a rule's terms (after the qualifiers), with the qualifiers in force
1210def AaParse.rule(ts: List<&2, T.AaTerm>, +q: P.AaQual, +als: List<&2, AaAlias>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaRules>:
1211  match ts:
1212    case Nil{}:
1213      Fail{E.AaSrc.syntax(src)}
1214    case Con{+t, rest}:
1215      AaParse.dispatch(AaParse.keyword(t), t, rest, q, als, src)
1216
1217def AaParse.terms(m: Maybe<&2, List<&2, T.AaTerm>>, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, T.AaTerm>>:
1218  match m:
1219    case None{}:
1220      Fail{E.AaSrc.syntax(src)}
1221    case Some{ts}:
1222      Done{ts}
1223
1224def AaParse.rule.qual(qp: AaQP, +block: AaBQ, +als: List<&2, AaAlias>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaRules>:
1225  match qp:
1226    case AaQP{q, rest}:
1227      do Result<&2, &2, E.AaError, PR.AaRules>:
1228        c : AaBQ <- AaQ.combine(block, q, src)
1229        AaParse.rule(rest, AaBQ.qual(c), als, src)
1230
1231# a statement inside a profile: its rules
1232def AaParse.stmt(ts: List<&2, T.AaTok>, +block: AaBQ, +pname: List<&2, U32>, +ctx: AaCtx, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaRules>:
1233  do Result<&2, &2, E.AaError, PR.AaRules>:
1234    xs : List<&2, T.AaTok> <- AaVars.toks(ts, AaCtx.env(ctx), Some{pname}, src)
1235    terms : List<&2, T.AaTerm> <- AaParse.terms(T.AaTerm.of_toks(xs), src)
1236    AaParse.rule.qual(AaQ.parse(terms, 0n, AaBQ.none()), block, AaCtx.aliases(ctx), src)
1237
1238# Profile heads: profile NAME [ATTACHMENT] [FLAGS], PATH [FLAGS], ^HAT
1239# [FLAGS], hat NAME [FLAGS], or the qualifiers of a qualifier block.
1240# kind: 0 a profile, 1 a hat, 2 a qualifier block
1241
1242type AaHead is Data:
1243  AaHead{kind: Nat, name: List<&2, U32>, attach: Maybe<&2, G.AaGlob>, flags: PR.AaFlags, q: AaBQ}
1244
1245# Flags: the mode seen (if any) and the audit flag
1246type AaFlB is Data:
1247  AaFlB{mode: Maybe<&2, PR.AaMode>, audit: Bool}
1248
1249def AaFl.mode_of(+w: List<&2, U32>) -> Maybe<&2, PR.AaMode>:
1250  Bool.pick(Maybe<&2, PR.AaMode>, X.AaText.is(w, "enforce"), Some{PR.AaModeEnforce{}}, Bool.pick(Maybe<&2, PR.AaMode>, X.AaText.is(w, "complain"), Some{PR.AaModeComplain{}}, Bool.pick(Maybe<&2, PR.AaMode>, X.AaText.is(w, "kill"), Some{PR.AaModeKill{}}, Bool.pick(Maybe<&2, PR.AaMode>, X.AaText.is(w, "unconfined"), Some{PR.AaModeUnconfined{}}, Bool.pick(Maybe<&2, PR.AaMode>, X.AaText.is(w, "default_allow"), Some{PR.AaModeDefaultAllow{}}, Bool.pick(Maybe<&2, PR.AaMode>, X.AaText.is(w, "prompt"), Some{PR.AaModePrompt{}}, None{}))))))
1251
1252def AaFl.misc() -> List<&2, String>:
1253  ["mediate_deleted", "attach_disconnected", "chroot_relative", "debug", "interruptible"]
1254
1255def AaFl.mode_eq(a: PR.AaMode, b: PR.AaMode) -> Bool:
1256  X.AaText.eq(X.AaText.of_string(PR.AaMode.show(a)), X.AaText.of_string(PR.AaMode.show(b)))
1257
1258def AaFl.set_mode(b: AaFlB, m: PR.AaMode, src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFlB>:
1259  match b:
1260    case AaFlB{old, a}:
1261      match old:
1262        case None{}:
1263          Done{AaFlB{Some{m}, a}}
1264        case Some{+o}:
1265          Bool.pick(Result<&2, &2, E.AaError, AaFlB>, AaFl.mode_eq(o, m), Done{AaFlB{Some{o}, a}}, Fail{E.AaSrc.flag(src)})
1266
1267def AaFl.word.mode(m: Maybe<&2, PR.AaMode>, +w: List<&2, U32>, +b: AaFlB, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFlB>:
1268  match m:
1269    case Some{x}:
1270      AaFl.set_mode(b, x, src)
1271    case None{}:
1272      match b:
1273        case AaFlB{+mo, +a}:
1274          Bool.pick(Result<&2, &2, E.AaError, AaFlB>, X.AaText.is(w, "audit"), Done{AaFlB{mo, True{}}}, Bool.pick(Result<&2, &2, E.AaError, AaFlB>, X.AaText.one_of(AaFl.misc(), w), Done{AaFlB{mo, a}}, Fail{E.AaSrc.flag(src)}))
1275
1276# kill.signal=SIGNAL and attach_disconnected.path=PATH are accepted and not modelled
1277def AaFl.step(t: T.AaTerm, +b: AaFlB, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFlB>:
1278  match t:
1279    case T.AaTmWord{+w}:
1280      AaFl.word.mode(AaFl.mode_of(w), w, b, src)
1281    case T.AaTmCond{+k, v}:
1282      Bool.pick(Result<&2, &2, E.AaError, AaFlB>, Bool.or(X.AaText.is(k, "kill.signal"), X.AaText.is(k, "attach_disconnected.path")), Done{b}, Fail{E.AaSrc.flag(src)})
1283    case _:
1284      Fail{E.AaSrc.flag(src)}
1285
1286def AaFl.run(ts: List<&2, T.AaTerm>, b: AaFlB, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFlB>:
1287  match ts:
1288    case Nil{}:
1289      Done{b}
1290    case Con{t, rest}:
1291      do Result<&2, &2, E.AaError, AaFlB>:
1292        b2 : AaFlB <- AaFl.step(t, b, src)
1293        AaFl.run(rest, b2, src)
1294
1295def AaFl.finish(b: AaFlB) -> PR.AaFlags:
1296  match b:
1297    case AaFlB{m, a}:
1298      PR.AaFlags{Maybe.default(&2, PR.AaMode, m, PR.AaModeEnforce{}), a}
1299
1300def AaFl.list(xs: List<&2, T.AaTerm>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaFlags>:
1301  do Result<&2, &2, E.AaError, PR.AaFlags>:
1302    b : AaFlB <- AaFl.run(xs, AaFlB{None{}, False{}}, src)
1303    return AaFl.finish(b)
1304
1305# the flags term: flags=(...) or (...)
1306def AaFl.term(t: T.AaTerm, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaFlags>:
1307  match t:
1308    case T.AaTmList{xs}:
1309      AaFl.list(xs, src)
1310    case T.AaTmCond{+k, v}:
1311      match v:
1312        case T.AaTmList{xs}:
1313          Bool.pick(Result<&2, &2, E.AaError, PR.AaFlags>, X.AaText.is(k, "flags"), AaFl.list(xs, src), Fail{E.AaSrc.syntax(src)})
1314        case _:
1315          Fail{E.AaSrc.syntax(src)}
1316    case _:
1317      Fail{E.AaSrc.syntax(src)}
1318
1319# optional flags, and nothing after them
1320def AaFl.opt(ts: List<&2, T.AaTerm>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaFlags>:
1321  match ts:
1322    case Nil{}:
1323      Done{PR.AaFlags.default()}
1324    case Con{t, rest}:
1325      match rest:
1326        case Nil{}:
1327          AaFl.term(t, src)
1328        case Con{x, more}:
1329          Fail{E.AaSrc.syntax(src)}
1330
1331def AaParse.alnum(+c: U32) -> Bool:
1332  Bool.or(T.AaTerm.letter(c), Bool.and(U32.is_ge(c, 48), U32.is_le(c, 57)))
1333
1334# a profile name: a quoted string, or a word starting with a letter, a digit or '/'
1335def AaParse.name(t: T.AaTerm, +src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, U32>>:
1336  match t:
1337    case T.AaTmWord{+w}:
1338      Bool.pick(Result<&2, &2, E.AaError, List<&2, U32>>, Bool.or(AaParse.alnum(X.AaText.head(w)), U32.is_eq(X.AaText.head(w), 47)), Done{w}, Fail{E.AaSrc.syntax(src)})
1339    case T.AaTmQuoted{+q}:
1340      Bool.pick(Result<&2, &2, E.AaError, List<&2, U32>>, X.AaText.is_nil(q), Fail{E.AaSrc.syntax(src)}, Done{q})
1341    case _:
1342      Fail{E.AaSrc.syntax(src)}
1343
1344def AaParse.attach.glob(r: Result<&2, &2, E.AaError, G.AaGlob>, src: E.AaSrc) -> Result<&2, &2, E.AaError, Maybe<&2, G.AaGlob>>:
1345  match r:
1346    case Fail{e}:
1347      Fail{e}
1348    case Done{+g}:
1349      Bool.pick(Result<&2, &2, E.AaError, Maybe<&2, G.AaGlob>>, F.AaFile.rooted(g), Done{Some{g}}, Fail{E.AaSrc.syntax(src)})
1350
1351# a name that is an absolute path is also the attachment
1352def AaParse.self_attach(+name: List<&2, U32>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, Maybe<&2, G.AaGlob>>:
1353  Bool.pick(Result<&2, &2, E.AaError, Maybe<&2, G.AaGlob>>, U32.is_eq(X.AaText.head(name), 47), AaParse.attach.glob(R.AaRule.glob_text(name, src), src), Done{None{}})
1354
1355def AaParse.head.make(kind: Nat, name: List<&2, U32>, attach: Maybe<&2, G.AaGlob>, r: Result<&2, &2, E.AaError, PR.AaFlags>) -> Result<&2, &2, E.AaError, AaHead>:
1356  match r:
1357    case Fail{e}:
1358      Fail{e}
1359    case Done{f}:
1360      Done{AaHead{kind, name, attach, f, AaBQ.none()}}
1361
1362def AaParse.head.flags_only(ts: List<&2, T.AaTerm>, +name: List<&2, U32>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1363  do Result<&2, &2, E.AaError, AaHead>:
1364    a : Maybe<&2, G.AaGlob> <- AaParse.self_attach(name, src)
1365    AaParse.head.make(0n, name, a, AaFl.opt(ts, src))
1366
1367def AaParse.head.attached(t: T.AaTerm, rest: List<&2, T.AaTerm>, name: List<&2, U32>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1368  do Result<&2, &2, E.AaError, AaHead>:
1369    g : G.AaGlob <- R.AaRule.glob(t, src)
1370    a : Maybe<&2, G.AaGlob> <- AaParse.attach.glob(Done{g}, src)
1371    AaParse.head.make(0n, name, a, AaFl.opt(rest, src))
1372
1373# after `profile NAME`: an attachment, then flags
1374def AaParse.head.profile.rest(ts: List<&2, T.AaTerm>, +name: List<&2, U32>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1375  match ts:
1376    case Nil{}:
1377      do Result<&2, &2, E.AaError, AaHead>:
1378        a : Maybe<&2, G.AaGlob> <- AaParse.self_attach(name, src)
1379        return AaHead{0n, name, a, PR.AaFlags.default(), AaBQ.none()}
1380    case Con{+t, +rest}:
1381      Bool.pick(Result<&2, &2, E.AaError, AaHead>, F.AaFile.is_path(t), AaParse.head.attached(t, rest, name, src), AaParse.head.flags_only(Con{t, rest}, name, src))
1382
1383def AaParse.head.profile(ts: List<&2, T.AaTerm>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1384  match ts:
1385    case Nil{}:
1386      Fail{E.AaSrc.syntax(src)}
1387    case Con{t, rest}:
1388      do Result<&2, &2, E.AaError, AaHead>:
1389        n : List<&2, U32> <- AaParse.name(t, src)
1390        AaParse.head.profile.rest(rest, n, src)
1391
1392def AaParse.head.hat.name(+n: List<&2, U32>, rest: List<&2, T.AaTerm>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1393  Bool.pick(Result<&2, &2, E.AaError, AaHead>, AaParse.alnum(X.AaText.head(n)), AaParse.head.make(1n, n, None{}, AaFl.opt(rest, src)), Fail{E.AaSrc.syntax(src)})
1394
1395# hat NAME [FLAGS]
1396def AaParse.head.hat(ts: List<&2, T.AaTerm>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1397  match ts:
1398    case Nil{}:
1399      Fail{E.AaSrc.syntax(src)}
1400    case Con{t, rest}:
1401      match t:
1402        case T.AaTmWord{w}:
1403          AaParse.head.hat.name(w, rest, src)
1404        case _:
1405          Fail{E.AaSrc.syntax(src)}
1406
1407def AaParse.head.qual(qp: AaQP, src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1408  match qp:
1409    case AaQP{q, rest}:
1410      match rest:
1411        case Nil{}:
1412          Done{AaHead{2n, Nil{}, None{}, PR.AaFlags.default(), q}}
1413        case Con{x, more}:
1414          Fail{E.AaSrc.syntax(src)}
1415
1416# a path as the head: its name and its attachment
1417def AaParse.head.path(t: T.AaTerm, rest: List<&2, T.AaTerm>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1418  do Result<&2, &2, E.AaError, AaHead>:
1419    n : List<&2, U32> <- AaParse.name(t, src)
1420    AaParse.head.flags_only(rest, n, src)
1421
1422def AaParse.head.word(+w: List<&2, U32>, +t: T.AaTerm, +rest: List<&2, T.AaTerm>, +top: Bool, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1423  Bool.pick(Result<&2, &2, E.AaError, AaHead>, X.AaText.is(w, "profile"), AaParse.head.profile(rest, src), Bool.pick(Result<&2, &2, E.AaError, AaHead>, Bool.and(Bool.not(top), X.AaText.is(w, "hat")), AaParse.head.hat(rest, src), Bool.pick(Result<&2, &2, E.AaError, AaHead>, Bool.and(Bool.not(top), U32.is_eq(X.AaText.head(w), 94)), AaParse.head.hat.name(X.AaText.tail(w), rest, src), Bool.pick(Result<&2, &2, E.AaError, AaHead>, Bool.and(top, F.AaFile.is_path(t)), AaParse.head.path(t, rest, src), Bool.pick(Result<&2, &2, E.AaError, AaHead>, Bool.not(top), AaParse.head.qual(AaQ.parse(Con{t, rest}, 0n, AaBQ.none()), src), Fail{E.AaSrc.syntax(src)})))))
1424
1425# the head of a block, at the top level or inside a profile
1426def AaParse.head(ts: List<&2, T.AaTerm>, +top: Bool, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1427  match ts:
1428    case Nil{}:
1429      Fail{E.AaSrc.syntax(src)}
1430    case Con{t, rest}:
1431      match t:
1432        case T.AaTmWord{+w}:
1433          AaParse.head.word(w, T.AaTmWord{w}, rest, top, src)
1434        case T.AaTmQuoted{+q}:
1435          Bool.pick(Result<&2, &2, E.AaError, AaHead>, top, AaParse.head.path(T.AaTmQuoted{q}, rest, src), Fail{E.AaSrc.syntax(src)})
1436        case _:
1437          Fail{E.AaSrc.syntax(src)}
1438
1439# Exec conflicts: two rules on the same literal path with different exec modes
1440
1441def AaParse.target_eq(a: Maybe<&2, List<&2, U32>>, b: Maybe<&2, List<&2, U32>>) -> Bool:
1442  match a b:
1443    case None{} None{}:
1444      True{}
1445    case Some{x} Some{y}:
1446      X.AaText.eq(x, y)
1447    case _ _:
1448      False{}
1449
1450def AaParse.xconf(a: F.AaFileRule, b: F.AaFileRule) -> Bool:
1451  match a:
1452    case F.AaFileRule{q1, t1, g1, p1, e1, tg1}:
1453      match e1:
1454        case None{}:
1455          False{}
1456        case Some{m1}:
1457          match b:
1458            case F.AaFileRule{q2, t2, g2, p2, e2, tg2}:
1459              match e2:
1460                case None{}:
1461                  False{}
1462                case Some{m2}:
1463                  Bool.and(Bool.and(G.AaGlob.is_literal(g1), G.AaGlob.is_literal(g2)), Bool.and(X.AaText.eq(t1, t2), Bool.not(Bool.and(F.AaXMode.eq(m1, m2), AaParse.target_eq(tg1, tg2)))))
1464
1465def AaParse.xconf.any(+r: F.AaFileRule, rs: List<&2, F.AaFileRule>) -> Bool:
1466  match rs:
1467    case Nil{}:
1468      False{}
1469    case Con{s, t}:
1470      Bool.or(AaParse.xconf(r, s), AaParse.xconf.any(r, t))
1471
1472def AaParse.conflicts.go(rs: List<&2, F.AaFileRule>) -> Bool:
1473  match rs:
1474    case Nil{}:
1475      False{}
1476    case Con{+r, +t}:
1477      Bool.or(AaParse.xconf.any(r, t), AaParse.conflicts.go(t))
1478
1479# the allow rules with an exec mode on a literal pattern: the only ones that can conflict
1480def AaParse.exec_literal(r: F.AaFileRule) -> Bool:
1481  match r:
1482    case F.AaFileRule{q, t, g, p, e, tg}:
1483      match e:
1484        case None{}:
1485          False{}
1486        case Some{m}:
1487          Bool.and(G.AaGlob.is_literal(g), Bool.not(P.AaQual.deny(q)))
1488
1489def AaParse.exec_rules(rs: List<&2, F.AaFileRule>) -> List<&2, F.AaFileRule>:
1490  match rs:
1491    case Nil{}:
1492      Nil{}
1493    case Con{+r, t}:
1494      F.AaFileRule.matching.go(AaParse.exec_literal(r), r, AaParse.exec_rules(t))
1495
1496def AaParse.conflicts(rs: List<&2, F.AaFileRule>) -> Bool:
1497  AaParse.conflicts.go(AaParse.exec_rules(rs))
1498
1499# change_profile rules with exec conditions must agree on safe and unsafe
1500# (apparmor_parser 4.0.1 refuses overlapping ones that differ; the package,
1501# which does not intersect patterns, refuses any allow rules that differ)
1502def AaParse.cp_applies(r: RX.AaChangeRule) -> Bool:
1503  match r:
1504    case RX.AaChangeRule{q, e, s, tg}:
1505      match e:
1506        case None{}:
1507          False{}
1508        case Some{g}:
1509          Bool.not(P.AaQual.deny(q))
1510
1511def AaParse.cp_safe(r: RX.AaChangeRule) -> Bool:
1512  match r:
1513    case RX.AaChangeRule{q, e, s, tg}:
1514      s
1515
1516def AaParse.cp_clash(applies: Bool, +s: Bool, seen: Maybe<&2, Bool>) -> Bool:
1517  match applies:
1518    case False{}:
1519      False{}
1520    case True{}:
1521      match seen:
1522        case None{}:
1523          False{}
1524        case Some{x}:
1525          Bool.xor(x, s)
1526
1527def AaParse.cp_next(applies: Bool, s: Bool, +seen: Maybe<&2, Bool>) -> Maybe<&2, Bool>:
1528  match applies:
1529    case False{}:
1530      seen
1531    case True{}:
1532      match seen:
1533        case None{}:
1534          Some{s}
1535        case Some{x}:
1536          Some{x}
1537
1538def AaParse.safe_clash(rs: List<&2, RX.AaChangeRule>, +seen: Maybe<&2, Bool>) -> Bool:
1539  match rs:
1540    case Nil{}:
1541      False{}
1542    case Con{+r, t}:
1543      Bool.or(AaParse.cp_clash(AaParse.cp_applies(r), AaParse.cp_safe(r), seen), AaParse.safe_clash(t, AaParse.cp_next(AaParse.cp_applies(r), AaParse.cp_safe(r), seen)))
1544
1545# Building: a stack of open blocks, innermost first. A profile frame holds
1546# the profile being built, whether it is a hat, its full name; every frame
1547# holds the qualifiers in force.
1548
1549type AaFrame is Data:
1550  AaFrame{prof: Maybe<&2, PR.AaProfile>, hat: Bool, full: List<&2, U32>, qual: AaBQ, src: E.AaSrc}
1551
1552type AaBuild is Data:
1553  AaBuild{frames: List<&2, AaFrame>, top: List<&2, PR.AaProfile>, err: Maybe<&2, E.AaError>}
1554
1555def AaBuild.fail(b: AaBuild, e: E.AaError) -> AaBuild:
1556  match b:
1557    case AaBuild{fs, top, err}:
1558      match err:
1559        case None{}:
1560          AaBuild{fs, top, Some{e}}
1561        case Some{e0}:
1562          AaBuild{fs, top, Some{e0}}
1563
1564# the innermost profile frame's full name
1565def AaFrames.full(fs: List<&2, AaFrame>) -> List<&2, U32>:
1566  match fs:
1567    case Nil{}:
1568      Nil{}
1569    case Con{f, t}:
1570      match f:
1571        case AaFrame{p, h, full, q, s}:
1572          match p:
1573            case None{}:
1574              AaFrames.full(t)
1575            case Some{x}:
1576              full
1577
1578def AaFrames.qual(fs: List<&2, AaFrame>) -> AaBQ:
1579  match fs:
1580    case Nil{}:
1581      AaBQ.none()
1582    case Con{f, t}:
1583      match f:
1584        case AaFrame{p, h, full, q, s}:
1585          q
1586
1587# the rules added to the innermost profile
1588def AaFrames.add_rules(fs: List<&2, AaFrame>, rs: PR.AaRules) -> List<&2, AaFrame>:
1589  match fs:
1590    case Nil{}:
1591      Nil{}
1592    case Con{f, t}:
1593      match f:
1594        case AaFrame{p, h, full, q, s}:
1595          match p:
1596            case None{}:
1597              Con{AaFrame{None{}, h, full, q, s}, AaFrames.add_rules(t, rs)}
1598            case Some{x}:
1599              Con{AaFrame{Some{PR.AaProfile.prepend(x, rs)}, h, full, q, s}, t}
1600
1601def AaFrames.names.go(ps: List<&2, PR.AaProfile>) -> List<&2, List<&2, U32>>:
1602  match ps:
1603    case Nil{}:
1604      Nil{}
1605    case Con{p, t}:
1606      Con{PR.AaProfile.name(p), AaFrames.names.go(t)}
1607
1608def AaFrames.names(+p: PR.AaProfile) -> List<&2, List<&2, U32>>:
1609  AaFrames.names.go(PR.AaProfile.list_append(PR.AaProfile.children(p), PR.AaProfile.hats(p)))
1610
1611def AaFrames.add_sub.go(dup: Bool, +x: PR.AaProfile, k: PR.AaProfile, hat: Bool) -> Maybe<&2, PR.AaProfile>:
1612  match dup:
1613    case True{}:
1614      None{}
1615    case False{}:
1616      match hat:
1617        case True{}:
1618          Some{PR.AaProfile.add_hat(x, k)}
1619        case False{}:
1620          Some{PR.AaProfile.add_child(x, k)}
1621
1622def AaFrames.add_sub.put(m: Maybe<&2, PR.AaProfile>, h: Bool, full: List<&2, U32>, q: AaBQ, s: E.AaSrc, t: List<&2, AaFrame>) -> Maybe<&2, List<&2, AaFrame>>:
1623  match m:
1624    case None{}:
1625      None{}
1626    case Some{y}:
1627      Some{Con{AaFrame{Some{y}, h, full, q, s}, t}}
1628
1629def AaFrames.add_sub.cons(f: AaFrame, m: Maybe<&2, List<&2, AaFrame>>) -> Maybe<&2, List<&2, AaFrame>>:
1630  match m:
1631    case None{}:
1632      None{}
1633    case Some{t}:
1634      Some{Con{f, t}}
1635
1636# a finished child or hat added to the innermost profile; None if the name is taken
1637def AaFrames.add_sub(fs: List<&2, AaFrame>, +k: PR.AaProfile, +hat: Bool) -> Maybe<&2, List<&2, AaFrame>>:
1638  match fs:
1639    case Nil{}:
1640      None{}
1641    case Con{f, t}:
1642      match f:
1643        case AaFrame{p, h, full, q, s}:
1644          match p:
1645            case None{}:
1646              AaFrames.add_sub.cons(AaFrame{None{}, h, full, q, s}, AaFrames.add_sub(t, k, hat))
1647            case Some{+x}:
1648              AaFrames.add_sub.put(AaFrames.add_sub.go(X.AaText.member(AaFrames.names(x), PR.AaProfile.name(k)), x, k, hat), h, full, q, s, t)
1649
1650def AaFrames.has_profile(fs: List<&2, AaFrame>) -> Bool:
1651  match fs:
1652    case Nil{}:
1653      False{}
1654    case Con{f, t}:
1655      match f:
1656        case AaFrame{p, h, full, q, s}:
1657          match p:
1658            case None{}:
1659              AaFrames.has_profile(t)
1660            case Some{x}:
1661              True{}
1662
1663def AaBuild.top_names(ps: List<&2, PR.AaProfile>) -> List<&2, List<&2, U32>>:
1664  AaFrames.names.go(ps)
1665
1666# a finished profile: checked, and added to its parent or to the top level
1667def AaBuild.finish.sub(m: Maybe<&2, List<&2, AaFrame>>, top: List<&2, PR.AaProfile>, err: Maybe<&2, E.AaError>, src: E.AaSrc) -> AaBuild:
1668  match m:
1669    case None{}:
1670      AaBuild.fail(AaBuild{Nil{}, top, err}, E.AaSrc.duplicate(src))
1671    case Some{fs}:
1672      AaBuild{fs, top, err}
1673
1674def AaBuild.finish.top(+p: PR.AaProfile, +top: List<&2, PR.AaProfile>, +err: Maybe<&2, E.AaError>, +src: E.AaSrc) -> AaBuild:
1675  Bool.pick(AaBuild, X.AaText.member(AaBuild.top_names(top), PR.AaProfile.name(p)), AaBuild.fail(AaBuild{Nil{}, top, err}, E.AaSrc.duplicate(src)), AaBuild{Nil{}, PR.AaProfile.list_append(top, [p]), err})
1676
1677def AaBuild.finish.place(nested: Bool, +p: PR.AaProfile, hat: Bool, +fs: List<&2, AaFrame>, +top: List<&2, PR.AaProfile>, +err: Maybe<&2, E.AaError>, +src: E.AaSrc) -> AaBuild:
1678  match nested:
1679    case True{}:
1680      AaBuild.finish.sub(AaFrames.add_sub(fs, p, hat), top, err, src)
1681    case False{}:
1682      AaBuild.finish.top(p, top, err, src)
1683
1684def AaBuild.finish(+p: PR.AaProfile, +hat: Bool, +fs: List<&2, AaFrame>, +top: List<&2, PR.AaProfile>, +err: Maybe<&2, E.AaError>, +src: E.AaSrc) -> AaBuild:
1685  Bool.pick(AaBuild, Bool.or(AaParse.conflicts(PR.AaRules.files(PR.AaProfile.rules(p))), AaParse.safe_clash(PR.AaRules.changes(PR.AaProfile.rules(p)), None{})), AaBuild.fail(AaBuild{fs, top, err}, E.AaSrc.exec_conflict(src)), AaBuild.finish.place(AaFrames.has_profile(fs), p, hat, fs, top, err, src))
1686
1687def AaBuild.close(b: AaBuild, src: E.AaSrc) -> AaBuild:
1688  match b:
1689    case AaBuild{fs, top, err}:
1690      match fs:
1691        case Nil{}:
1692          AaBuild.fail(AaBuild{Nil{}, top, err}, E.AaSrc.syntax(src))
1693        case Con{f, rest}:
1694          match f:
1695            case AaFrame{p, h, full, q, s}:
1696              match p:
1697                case None{}:
1698                  AaBuild{rest, top, err}
1699                case Some{x}:
1700                  AaBuild.finish(x, h, rest, top, err, s)
1701
1702def AaBuild.push.qual(r: Result<&2, &2, E.AaError, AaBQ>, fs: List<&2, AaFrame>, top: List<&2, PR.AaProfile>, err: Maybe<&2, E.AaError>, src: E.AaSrc) -> AaBuild:
1703  match r:
1704    case Fail{e}:
1705      AaBuild.fail(AaBuild{fs, top, err}, e)
1706    case Done{q}:
1707      AaBuild{Con{AaFrame{None{}, False{}, Nil{}, q, src}, fs}, top, err}
1708
1709def AaBuild.push.kind(kind: Nat, +fs: List<&2, AaFrame>, top: List<&2, PR.AaProfile>, err: Maybe<&2, E.AaError>, name: List<&2, U32>, attach: Maybe<&2, G.AaGlob>, flags: PR.AaFlags, q: AaBQ, full: List<&2, U32>, +src: E.AaSrc) -> AaBuild:
1710  match kind:
1711    case 0n:
1712      AaBuild{Con{AaFrame{Some{PR.AaProfile.new(name, attach, flags)}, False{}, full, AaBQ.none(), src}, fs}, top, err}
1713    case 1n+k:
1714      match k:
1715        case 0n:
1716          AaBuild{Con{AaFrame{Some{PR.AaProfile.new(name, attach, flags)}, True{}, full, AaBQ.none(), src}, fs}, top, err}
1717        case 1n+j:
1718          AaBuild.push.qual(AaQ.combine(AaFrames.qual(fs), q, src), fs, top, err, src)
1719
1720# a block opens
1721def AaBuild.push(r: Result<&2, &2, E.AaError, AaHead>, b: AaBuild, +src: E.AaSrc) -> AaBuild:
1722  match r:
1723    case Fail{e}:
1724      AaBuild.fail(b, e)
1725    case Done{hd}:
1726      match hd:
1727        case AaHead{kind, +name, attach, flags, q}:
1728          match b:
1729            case AaBuild{+fs, top, err}:
1730              +outer = AaFrames.full(fs)
1731              +full = Bool.pick(List<&2, U32>, X.AaText.is_nil(outer), name, X.AaText.append(outer, X.AaText.append(X.AaText.of_string("//"), name)))
1732              AaBuild.push.kind(kind, fs, top, err, name, attach, flags, q, full, src)
1733
1734def AaBuild.frames(b: AaBuild) -> List<&2, AaFrame>:
1735  match b:
1736    case AaBuild{fs, top, err}:
1737      fs
1738
1739def AaParse.head.of(ts: List<&2, T.AaTok>, +top: Bool, +pname: Maybe<&2, List<&2, U32>>, +ctx: AaCtx, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaHead>:
1740  do Result<&2, &2, E.AaError, AaHead>:
1741    xs : List<&2, T.AaTok> <- AaVars.toks(ts, AaCtx.env(ctx), pname, src)
1742    terms : List<&2, T.AaTerm> <- AaParse.terms(T.AaTerm.of_toks(xs), src)
1743    AaParse.head(terms, top, src)
1744
1745def AaBuild.open(+b: AaBuild, ts: List<&2, T.AaTok>, +ctx: AaCtx, +src: E.AaSrc) -> AaBuild:
1746  +inside = AaFrames.has_profile(AaBuild.frames(b))
1747  +pname = Bool.pick(Maybe<&2, List<&2, U32>>, inside, Some{AaFrames.full(AaBuild.frames(b))}, None{})
1748  AaBuild.push(AaParse.head.of(ts, Bool.not(inside), pname, ctx, src), b, src)
1749
1750def AaBuild.rules(r: Result<&2, &2, E.AaError, PR.AaRules>, b: AaBuild) -> AaBuild:
1751  match r:
1752    case Fail{e}:
1753      AaBuild.fail(b, e)
1754    case Done{rs}:
1755      match b:
1756        case AaBuild{fs, top, err}:
1757          AaBuild{AaFrames.add_rules(fs, rs), top, err}
1758
1759# a statement: a rule inside a profile; at the top level the preamble pass has checked it
1760def AaBuild.stmt(+b: AaBuild, ts: List<&2, T.AaTok>, +ctx: AaCtx, +src: E.AaSrc) -> AaBuild:
1761  +fs = AaBuild.frames(b)
1762  Bool.pick(AaBuild, AaFrames.has_profile(fs), AaBuild.rules(AaParse.stmt(ts, AaFrames.qual(fs), AaFrames.full(fs), ctx, src), b), b)
1763
1764def AaBuild.ok(b: AaBuild) -> Bool:
1765  match b:
1766    case AaBuild{fs, top, err}:
1767      match err:
1768        case None{}:
1769          True{}
1770        case Some{e}:
1771          False{}
1772
1773def AaBuild.step(+b: AaBuild, it: AaItem, +ctx: AaCtx) -> AaBuild:
1774  match it:
1775    case AaIOpen{ts, src}:
1776      Bool.pick(AaBuild, AaBuild.ok(b), AaBuild.open(b, ts, ctx, src), b)
1777    case AaIClose{src}:
1778      Bool.pick(AaBuild, AaBuild.ok(b), AaBuild.close(b, src), b)
1779    case AaIStmt{ts, src}:
1780      Bool.pick(AaBuild, AaBuild.ok(b), AaBuild.stmt(b, ts, ctx, src), b)
1781    case AaIVar{ts, src}:
1782      b
1783    case AaIInclude{n, o, src}:
1784      b
1785    case AaIError{e}:
1786      AaBuild.fail(b, e)
1787
1788def AaBuild.run(items: List<&2, AaItem>, b: AaBuild, +ctx: AaCtx) -> AaBuild:
1789  match items:
1790    case Nil{}:
1791      b
1792    case Con{it, rest}:
1793      AaBuild.run(rest, AaBuild.step(b, it, ctx), ctx)
1794
1795def AaBuild.result(b: AaBuild, src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaPolicy>:
1796  match b:
1797    case AaBuild{fs, top, err}:
1798      match fs:
1799        case Nil{}:
1800          match err:
1801            case Some{e}:
1802              Fail{e}
1803            case None{}:
1804              Done{PR.AaPolicy{top}}
1805        case Con{f, rest}:
1806          match err:
1807            case Some{e}:
1808              Fail{e}
1809            case None{}:
1810              Fail{E.AaSrc.syntax(src)}
1811
1812# Loading
1813
1814def AaParse.pre.result(p: AaPre, items: List<&2, AaItem>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaPolicy>:
1815  match p:
1816    case AaPre{env, als, d, s, err}:
1817      match d:
1818        case 0n:
1819          match err:
1820            case Some{e}:
1821              Fail{e}
1822            case None{}:
1823              AaBuild.result(AaBuild.run(items, AaBuild{Nil{}, Nil{}, None{}}, AaCtx{env, als}), src)
1824        case 1n+x:
1825          match err:
1826            case Some{e}:
1827              Fail{e}
1828            case None{}:
1829              Fail{E.AaSrc.syntax(src)}
1830
1831def AaParse.load.items(m: Maybe<&2, E.AaError>, +items: List<&2, AaItem>, +opts: O.AaOptions, +src: E.AaSrc) -> Result<&2, &2, E.AaError, PR.AaPolicy>:
1832  match m:
1833    case Some{e}:
1834      Fail{e}
1835    case None{}:
1836      AaParse.pre.result(AaPre.run(items, AaPre{O.AaOptions.vars(opts), Nil{}, 0n, False{}, None{}}), items, src)
1837
1838def AaParse.load.file(m: Maybe<&2, AaSrcFile>, +opts: O.AaOptions, +files: List<&2, AaSrcFile>, +main: List<&2, U32>) -> Result<&2, &2, E.AaError, PR.AaPolicy>:
1839  match m:
1840    case None{}:
1841      Fail{E.AaErrInclude{main, 0}}
1842    case Some{f}:
1843      match f:
1844        case AaSrcFile{n, text}:
1845          +items = AaExpOut.items(AaParse.expand(O.AaOptions.depth(opts), AaParse.items(main, text), files, [[main]], Nil{}))
1846          AaParse.load.items(AaParse.first_error(items), items, opts, E.AaSrc{main, 0})
1847
1848# the policy the file named main defines, reading includes from the file map
1849def AaParse.load(+opts: O.AaOptions, +files: List<&2, AaSrcFile>, +main: List<&2, U32>) -> Result<&2, &2, E.AaError, PR.AaPolicy>:
1850  AaParse.load.file(AaParse.find_file(files, main), opts, files, main)