commit 26fdb2f

shrub  ·  2026-10-14 15:22:31 +0000 UTC
parent 26fdb2f
initial
8 files changed,  +482, -0
A README
+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+