commit 26fdb2f
shrub
·
2026-10-14 15:22:31 +0000 UTC
parent 26fdb2f
initial
8 files changed,
+482,
-0
A
Makefile
+23,
-0
1@@ -0,0 +1,23 @@
2+.POSIX:
3+
4+ATSCC = patscc
5+ATSFLAGS = -DATS_MEMALLOC_LIBC
6+PATSHOME = /usr/local/lib/ats2-postiats-0.4.2
7+LDLIBS = -L"$(PATSHOME)/ccomp/atslib/lib" -latslib
8+OBJ = main.o scan.o parse.o
9+
10+monk: $(OBJ)
11+ $(ATSCC) $(ATSFLAGS) -o $@ $(OBJ) $(LDLIBS)
12+
13+.SUFFIXES: .dats .o
14+.dats.o:
15+ $(ATSCC) $(ATSFLAGS) -c -o $@ $<
16+
17+main.o scan.o parse.o: scan.sats
18+main.o parse.o: parse.sats
19+
20+.PHONY: clean
21+clean:
22+ rm monk
23+ rm -f *.c
24+ rm -f $(OBJ)
A
README
+4,
-0
1@@ -0,0 +1,4 @@
2+monk
3+----
4+
5+(wip) implementation of ninja in ATS2!
+11,
-0
1@@ -0,0 +1,11 @@
2+# this is for testing!
3+#comment
4+rule cc
5+ command = cc $in -o $out
6+ deps = gcc
7+
8+rule shit
9+ command = shit
10+
11+build poop: piss in
12+
+19,
-0
1@@ -0,0 +1,19 @@
2+#include "share/atspre_staload.hats"
3+staload IO = "libats/libc/SATS/stdio.sats"
4+staload "scan.sats"
5+staload "parse.sats"
6+
7+implement main0 () =
8+let
9+ val manifest = fileref_open_exn("build.ninja", file_mode_r)
10+ (* initialize the scanner *)
11+ var s: scanner = @{
12+ file = manifest,
13+ cur = $IO.fgetc0(manifest),
14+ line = 1,
15+ col = 1 }
16+ val result = parse(s, nil_vt(), nil_vt())
17+in
18+ manifest_free(result);
19+ fileref_close(manifest)
20+end
+224,
-0
1@@ -0,0 +1,224 @@
2+#include "share/atspre_staload.hats"
3+#define ATS_DYNLOADFLAG 0
4+staload "scan.sats"
5+staload "parse.sats"
6+
7+exception ParseErr of ()
8+
9+fun bindings_free(b: List0_vt(binding)): void =
10+ case+ b of
11+ (* tilde means destroy the list wrapper*)
12+ | ~nil_vt() => ()
13+ | ~cons_vt(entry, rest) =>
14+ let
15+ (* construct new local variables(k,v) from
16+ * the entry record *)
17+ val @{key = k, value = v} = entry
18+ in
19+ strptr_free (k);
20+ strptr_free (v);
21+ bindings_free(rest)
22+ end
23+
24+fun rule_free(r: List0_vt(rule)): void =
25+ case+ r of
26+ | ~nil_vt() => ()
27+ | ~cons_vt(rule, rest) =>
28+ let
29+ val @{name = n, bindings = b} = rule
30+ in
31+ strptr_free (n);
32+ bindings_free(b);
33+ rule_free (rest)
34+ end
35+
36+fun paths_free(p: List0_vt(Strptr1)): void =
37+ case+ p of
38+ | ~nil_vt() => ()
39+ | ~cons_vt(path, rest) => (
40+ strptr_free(path);
41+ paths_free(rest))
42+
43+fun edge_free(e: List0_vt(edge)): void =
44+ case+ e of
45+ | ~nil_vt() => ()
46+ | ~cons_vt(edge, rest) =>
47+ let
48+ val @{output = o, rule = r, input = i} = edge
49+ in
50+ paths_free(o);
51+ strptr_free(r);
52+ paths_free(i);
53+ edge_free(rest)
54+end
55+
56+implement manifest_free(m) =
57+let
58+ val @{rules = r, edges = e} = m
59+in
60+ rule_free(r);
61+ edge_free(e);
62+end
63+
64+fun binding_val(s: &scanner): Strptr1 =
65+let
66+ val () = skipspace(s)
67+ val () = scanchar(s, char2int0('='))
68+ val text = scanvalue(s)
69+in
70+ if s.cur = char2int0('\n') then (
71+ next(s);
72+ text)
73+ else(
74+ strptr_free(text);
75+ prerrln!("expected newline after value at", s.line,":",s.col );
76+ $raise ParseErr )
77+end
78+
79+fun rulebinding(s: &scanner,
80+ b: List0_vt(binding)):
81+ List0_vt(binding) =
82+ if scanindent(s) then
83+ let
84+ val key = scanname(s)
85+ val value = binding_val(s)
86+ val entry: binding = @{key = key, value = value}
87+ val more = cons_vt(entry, b)
88+ in
89+ rulebinding(s, more)
90+ end
91+ else(list_vt_reverse(b))
92+
93+
94+fun rule(s: &scanner): rule =
95+let
96+ val () = skipspace(s)
97+ val rulename = scanname (s)
98+in
99+ skipspace(s);
100+ if s.cur = char2int0('\n') then (
101+ next(s);
102+ let val bindings = rulebinding(s, nil_vt()) in
103+ @{name = rulename, bindings = bindings} end
104+ ) else (
105+ strptr_free(rulename);
106+ prerrln!("expected newline after rule name at", s.line, ":", s.col);
107+ $raise ParseErr())
108+end
109+
110+(* collect the outputs of a build stmt assuming the scanner already
111+ * consumed 'build' *)
112+fun outputs (s: &scanner, collected: List0_vt(Strptr1)): List0_vt(Strptr1) =
113+let
114+ val () = skipspace(s)
115+in
116+ if (s.cur < 0) || (s.cur = char2int0('\n')) then (
117+ paths_free(collected);
118+ $raise ParseErr
119+ ) else if (s.cur = char2int0(':')) then
120+ list_vt_reverse(collected)
121+ else
122+ let
123+ val path = scanpath(s)
124+ val more = cons_vt(path, collected)
125+ in
126+ outputs(s, more)
127+ end
128+end
129+
130+fun inputs (s: &scanner, collected: List0_vt(Strptr1)): List0_vt(Strptr1) =
131+let
132+ val () = skipspace(s)
133+in
134+ if (s.cur < 0) || (s.cur = char2int0('\n')) then
135+ list_vt_reverse(collected)
136+ else if s.cur = char2int0(':') then (
137+ paths_free(collected);
138+ prerrln!("unexpected ':' at", s.line, ":", s.col);
139+ $raise ParseErr
140+ ) else
141+ let
142+ val path = scanpath(s)
143+ val more = cons_vt(path, collected)
144+ in
145+ inputs(s, more)
146+ end
147+end
148+
149+fun build (s: &scanner): edge =
150+let
151+ (* build out: rule in *)
152+ val o = outputs (s, nil_vt())
153+ val () = scanchar(s, char2int0(':'))
154+ val r = scanname(s)
155+ val i = inputs (s, nil_vt())
156+in
157+ if (s.cur = char2int0('\n')) then (
158+ next(s);
159+ @{output = o, rule = r, input = i}
160+ ) else (
161+ paths_free (o);
162+ paths_free (i);
163+ strptr_free(r);
164+ $raise ParseErr
165+ )
166+end
167+
168+(* does the rule refrenced in a build edge exist?*)
169+fun hasrule(rules: !List0_vt(rule), name: !Strptr1): bool =
170+ case+ rules of
171+ | nil_vt() => false
172+ (*here @ means we open the list without freeing it *)
173+ | @cons_vt(entry, rest) =>
174+ let
175+ val found =
176+ if compare_strptr_strptr(entry.name, name) = 0 then true
177+ else hasrule(rest, name)
178+ in
179+ (* close the list *)
180+ fold@(rules);
181+
182+ found
183+ end
184+
185+implement parse(s, rules, edges) (*manifest*) =
186+let
187+ val tok = scankeyword(s)
188+in
189+ (case+ tok of
190+ | ~TOKrule (line,col) => (println!("rule at: ", line,":", col);
191+ let
192+ val r = rule(s);
193+ val more = cons_vt(r, rules)
194+ in
195+ parse(s, more, edges)
196+ end)
197+ | ~TOKbuild(line,col) => (println!("build at: ", line,":", col);
198+ (let
199+ val b = build(s)
200+ in
201+ if hasrule(rules, b.rule) then
202+ let
203+ val more = cons_vt(b, edges)
204+ in
205+ parse(s, rules, more)
206+ end
207+ else (
208+ prerrln!("unknown rule ", b.rule, " at ", line,":",col);
209+ edge_free(cons_vt(b, nil_vt()));
210+ rule_free(rules);
211+ edge_free(edges);
212+ $raise ParseErr)
213+ end))
214+ | ~TOKvar (text, line,col) => (println!("var ", text, " at ", line,":", col);
215+ rule_free(rules);
216+ edge_free(edges);
217+ strptr_free(text);
218+ $raise ParseErr)
219+ | ~TOKeof(line,col) => (println!("eof at: ", line,":", col);
220+ @{
221+ rules = list_vt_reverse(rules),
222+ edges = list_vt_reverse(edges)
223+ }))
224+end
225+
+26,
-0
1@@ -0,0 +1,26 @@
2+staload "scan.sats"
3+
4+vtypedef binding = @{
5+ key = Strptr1,
6+ value = Strptr1
7+}
8+
9+vtypedef rule = @{
10+ name = Strptr1,
11+ bindings = List0_vt(binding)
12+}
13+
14+vtypedef edge = @{
15+ output = List0_vt(Strptr1),
16+ rule = Strptr1,
17+ input = List0_vt(Strptr1)
18+}
19+
20+vtypedef manifest = @{
21+ rules = List0_vt(rule),
22+ edges = List0_vt(edge)
23+}
24+
25+fun manifest_free(m: manifest): void
26+fun parse(s: &scanner, rules: List0_vt(rule), edges: List0_vt(edge)): manifest
27+
+149,
-0
1@@ -0,0 +1,149 @@
2+#include "share/atspre_staload.hats"
3+#define ATS_DYNLOADFLAG 0
4+staload STD = "libats/libc/SATS/stdlib.sats"
5+staload IO = "libats/libc/SATS/stdio.sats"
6+staload "scan.sats"
7+
8+exception ScanErr of ()
9+
10+(*get one character from a file and return the corresponding int*)
11+fun char (manifest: FILEref): int =
12+ $IO.fgetc0(manifest)
13+
14+(* stuff ninja allows *)
15+fun isnamec(c: int): bool =
16+ isalnum(c) ||
17+ (c = char2int0('_')) ||
18+ (c = char2int0('-')) ||
19+ (c = char2int0('.'))
20+
21+implement next(s) (*void*) =
22+ ((if s.cur = char2int0('\n') then (
23+ s.line := s.line + 1;
24+ s.col := 1 )
25+ else s.col := s.col +1);
26+ s.cur := char(s.file))
27+
28+fun name(s: &scanner, chars: List0_vt(charNZ)): List0_vt(charNZ) =
29+ if (s.cur >= 0) && isnamec(s.cur) then
30+ let
31+ val c = g1ofg0_char(int2char0(s.cur))
32+ in
33+ if c != '\000' then
34+ let
35+ val more = cons_vt(c, chars);
36+ in
37+ next(s);
38+ name(s, more)
39+ end
40+ else list_vt_reverse(chars)
41+ end
42+ else list_vt_reverse(chars)
43+
44+(* list of chars to string pointer *)
45+fun c2s(chars: List0_vt(charNZ)): Strptr1 =
46+let
47+ val text = string_make_list_vt(chars)
48+ prval () = lemma_strnptr_param(text)
49+in
50+ strnptr2strptr(text)
51+end
52+
53+(* do name with the initialized scanner and a fresh list *)
54+implement scanname(s) (*Strptr1*)=
55+ c2s(name(s, nil_vt()))
56+
57+implement skipspace(s) (*void*) =
58+ if (s.cur = char2int0(' ')) then (
59+ next(s); skipspace(s))
60+ else ()
61+
62+fun skipcomment(s: &scanner): void =
63+ if s.cur < 0 || s.cur = char2int0('\n') then ()
64+ else (next(s); skipcomment(s))
65+
66+implement mktok(text, line, col) (*tok*) =
67+ if text = "rule" then (
68+ strptr_free(text);
69+ TOKrule(line, col)
70+ ) else if
71+ text = "build" then (
72+ strptr_free(text);
73+ TOKbuild(line, col)
74+ ) else TOKvar(text, line, col)
75+
76+(* check if the current charachter is the wanted charachter:
77+ * if it is, consume it, if it's not, raise an error *)
78+implement scanchar(s, wanted) (*void*) =
79+ if s.cur = wanted then (
80+ next(s);
81+ skipspace(s)
82+ ) else (prerrln!("unexpected token at ",s.line, ":", s.col);
83+ $raise ScanErr())
84+
85+implement scanindent(s) (*bool*) =
86+ if s.cur = char2int0(' ') then (
87+ skipspace(s);
88+ true
89+ ) else (false)
90+
91+(*fun scankeyword(s: &scanner): tok = *)
92+implement scankeyword(s) =
93+ if s.cur < 0 then (TOKeof(s.line, s.col))
94+ else if s.cur = char2int0('\n') then (
95+ next(s);
96+ scankeyword(s))
97+ else if s.cur = char2int0('#') then (
98+ skipcomment(s);
99+ scankeyword(s))
100+ else if isnamec(s.cur) then
101+ let
102+ val sline = s.line
103+ val scol = s.col
104+ val text = scanname(s)
105+ in
106+ mktok(text, sline, scol)
107+ end
108+ else (prerrln!("expected name at ",s.line, ":", s.col);
109+ $raise ScanErr())
110+
111+fun value(s: &scanner, chars: List0_vt(charNZ)): List0_vt(charNZ) =
112+ if (s.cur >=0) && (s.cur != char2int0('\n')) then (
113+ let
114+ val c = g1ofg0_char(int2char0(s.cur))
115+ in
116+ if c != '\000' then
117+ let
118+ val more = cons_vt(c, chars);
119+ in
120+ next (s);
121+ value(s, more)
122+ end
123+ else list_vt_reverse(chars)
124+ end
125+ ) else list_vt_reverse(chars)
126+
127+implement scanvalue(s) =
128+ c2s(value(s, nil_vt()))
129+
130+fun path(s: &scanner, chars: List0_vt(charNZ)): List0_vt(charNZ) =
131+ if (s.cur >= 0) &&
132+ (s.cur != char2int0 ('\n')) &&
133+ (s.cur != char2int0 (':')) &&
134+ (s.cur != char2int0 (' ')) then (
135+ let val c = g1ofg0_char(int2char0(s.cur))
136+ in
137+ if c != '\000' then
138+ let
139+ val more = cons_vt(c, chars)
140+ in
141+ next(s);
142+ path(s, more)
143+ end
144+ else list_vt_reverse(chars)
145+ end
146+ ) else list_vt_reverse(chars)
147+
148+implement scanpath(s) =
149+ c2s(path(s, nil_vt()))
150+
+26,
-0
1@@ -0,0 +1,26 @@
2+typedef scanner = @{
3+ file = FILEref,
4+ cur = int,
5+ line = int,
6+ col = int
7+}
8+
9+(* each one carries the scanner position, var needs the var name *)
10+datavtype tok =
11+ | TOKrule of (int, int)
12+ | TOKbuild of (int, int)
13+ | TOKvar of (Strptr1, int, int)
14+ | TOKeof of (int, int)
15+
16+fun next (s: &scanner): void
17+fun scanname (s: &scanner): Strptr1
18+fun skipspace (s: &scanner): void
19+fun mktok (text: Strptr1,
20+ line: int,
21+ col: int): tok
22+fun scanchar (s: &scanner, wanted: int): void
23+fun scanindent (s: &scanner): bool
24+fun scankeyword(s: &scanner): tok
25+fun scanvalue (s: &scanner): Strptr1
26+fun scanpath (s: &scanner): Strptr1
27+