Skip to content

Commit eba80d2

Browse files
committed
refactor(rescript): port a2ml prototype compiler to AffineScript
A2ml core parser/validator/renderer, Compat shim, Json encoder, CLI, vector runner/report, demos, and the WASM demo ported faithfully. This completes the estate-wide .res -> .affine migration: ZERO ReScript files remain. https://claude.ai/code/session_01GTo7dz32ZgxuHXefv8BGqn
1 parent 6a739be commit eba80d2

22 files changed

Lines changed: 903 additions & 975 deletions

a2ml/prototype/rescript/src/A2ml.affine

Lines changed: 412 additions & 0 deletions
Large diffs are not rendered by default.

a2ml/prototype/rescript/src/A2ml.res

Lines changed: 0 additions & 480 deletions
This file was deleted.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// AffineScript port of Cli.res — A2ML CLI (prototype).
3+
4+
module Cli;
5+
6+
use Compat;
7+
use A2ml;
8+
use Json;
9+
10+
extern fn argv() -> [String] = "process" "argv";
11+
extern fn read_text(path: String) -> String = "platform" "readTextFile";
12+
extern fn write_text(path: String, text: String) -> Unit = "platform" "writeTextFile";
13+
extern fn read_stdin() -> String = "platform" "readStdin";
14+
extern fn exit(code: Int) -> Unit = "platform" "exit";
15+
16+
pub let help_text = "A2ML CLI (prototype)\n\n"
17+
++ "Usage:\n a2ml <render|validate|ast> <file...|-> [options]\n\n"
18+
++ "Commands:\n render Render HTML to stdout (or --out)\n"
19+
++ " validate Validate in checked mode, exit 2 on errors\n"
20+
++ " ast Output JSON surface AST\n\n"
21+
++ "Options:\n --mode <lax|checked> Parse mode (default: lax)\n"
22+
++ " --out <path> Write output to file\n"
23+
++ " --concat Concatenate outputs when multiple inputs\n"
24+
++ " --stdin Read input from stdin (equivalent to '-')\n"
25+
++ " -h, --help Show this help\n\n"
26+
++ "Notes:\n * Use '-' as a filename to read from stdin.\n"
27+
++ " * For multiple files, --concat joins outputs in order.\n";
28+
29+
pub fn usage() -> Effect[IO] Unit {
30+
Compat.console_log(help_text);
31+
exit(1)
32+
}
33+
34+
pub fn get_arg(args: [String], name: String) -> Option<String> {
35+
let i = 0;
36+
while i < len(args) {
37+
if args[i] == name {
38+
if i + 1 < len(args) { return Some(args[i + 1]); } else { return None; }
39+
}
40+
i = i + 1;
41+
}
42+
None
43+
}
44+
45+
pub fn has_flag(args: [String], name: String) -> Bool {
46+
let i = 0;
47+
while i < len(args) { if args[i] == name { return true; } i = i + 1; }
48+
false
49+
}
50+
51+
pub fn collect_inputs(args: [String]) -> [String] {
52+
let inputs = [];
53+
let i = 3;
54+
let done = false;
55+
while i < len(args) && !done {
56+
if Compat.starts_with(args[i], "-") {
57+
done = true;
58+
} else {
59+
inputs = inputs ++ [args[i]];
60+
i = i + 1;
61+
}
62+
}
63+
inputs
64+
}
65+
66+
pub fn read_input(path: String) -> String {
67+
if path == "-" { read_stdin() } else { read_text(path) }
68+
}
69+
70+
pub fn render_doc(input: String, mode: A2ml.ParseMode) -> String {
71+
A2ml.render_html(A2ml.parse(mode, input))
72+
}
73+
74+
pub fn validate_doc(input: String, mode: A2ml.ParseMode) -> [A2ml.ParseError] {
75+
let doc = A2ml.parse(mode, input);
76+
if mode == A2ml.Checked { A2ml.validate_checked(doc) } else { [] }
77+
}
78+
79+
pub fn ast_doc(input: String, mode: A2ml.ParseMode) -> String {
80+
Json.doc_to_json(A2ml.parse(mode, input))
81+
}
82+
83+
pub fn main() -> Effect[IO] Unit {
84+
let args = argv();
85+
if has_flag(args, "-h") || has_flag(args, "--help") { Compat.console_log(help_text); exit(0); }
86+
if len(args) < 3 { usage(); }
87+
88+
let command = args[2];
89+
let inputs = collect_inputs(args);
90+
let read_from_stdin = has_flag(args, "--stdin");
91+
let mode = match get_arg(args, "--mode") { Some("checked") => A2ml.Checked, _ => A2ml.Lax };
92+
let out_path = get_arg(args, "--out");
93+
let concat = has_flag(args, "--concat");
94+
let sources = if read_from_stdin { ["-"] } else { inputs };
95+
if len(sources) == 0 { usage(); }
96+
97+
match command {
98+
"render" => {
99+
let outputs = [];
100+
let i = 0;
101+
while i < len(sources) { outputs = outputs ++ [render_doc(read_input(sources[i]), mode)]; i = i + 1; }
102+
let result = if concat { Compat.array_join(outputs, "\n") } else { Compat.array_join(outputs, "\n\n") };
103+
match out_path { Some(p) => write_text(p, result), None => Compat.console_log(result) }
104+
}
105+
"validate" => {
106+
let all_errors = [];
107+
let i = 0;
108+
while i < len(sources) {
109+
let errs = validate_doc(read_input(sources[i]), mode);
110+
let j = 0;
111+
while j < len(errs) { all_errors = all_errors ++ [errs[j]]; j = j + 1; }
112+
i = i + 1;
113+
}
114+
if len(all_errors) == 0 {
115+
Compat.console_log("ok")
116+
} else {
117+
let k = 0;
118+
while k < len(all_errors) {
119+
Compat.console_log(Compat.int_to_string(all_errors[k].line) ++ ": " ++ all_errors[k].msg);
120+
k = k + 1;
121+
}
122+
exit(2)
123+
}
124+
}
125+
"ast" => {
126+
let outputs = [];
127+
let i = 0;
128+
while i < len(sources) { outputs = outputs ++ [ast_doc(read_input(sources[i]), mode)]; i = i + 1; }
129+
let result = Compat.array_join(outputs, "\n");
130+
match out_path { Some(p) => write_text(p, result), None => Compat.console_log(result) }
131+
}
132+
_ => usage(),
133+
}
134+
}
135+
136+
main()

a2ml/prototype/rescript/src/Cli.res

Lines changed: 0 additions & 156 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// AffineScript port of Compat.res (JS compatibility helpers).
3+
4+
module Compat;
5+
6+
extern fn console_log(v: a) -> Unit = "console" "log";
7+
extern fn str_length(s: String) -> Int = "string" "length";
8+
extern fn slice(s: String, start: Int, end: Int) -> String = "string" "slice";
9+
extern fn slice_to_end(s: String, from: Int) -> String = "string" "slice";
10+
extern fn trim(s: String) -> String = "string" "trim";
11+
extern fn index_of(s: String, needle: String) -> Int = "string" "indexOf";
12+
extern fn split(s: String, sep: String) -> [String] = "string" "split";
13+
extern fn starts_with(s: String, p: String) -> Bool = "string" "startsWith";
14+
extern fn ends_with(s: String, p: String) -> Bool = "string" "endsWith";
15+
extern fn replace(s: String, from: String, to: String) -> String = "string" "replace";
16+
extern fn int_to_string(n: Int) -> String = "global" "String";
17+
extern fn json_stringify(v: a) -> String = "JSON" "stringify";
18+
19+
pub fn array_length(arr: [a]) -> Int { len(arr) }
20+
21+
pub fn array_join(arr: [String], sep: String) -> String {
22+
let out = "";
23+
let i = 0;
24+
while i < len(arr) {
25+
out = if i == 0 { arr[i] } else { out ++ sep ++ arr[i] };
26+
i = i + 1;
27+
}
28+
out
29+
}
30+
31+
pub fn array_join_with(arr: [a], sep: String, fn_: fn(a) -> String) -> String {
32+
let parts = [];
33+
let i = 0;
34+
while i < len(arr) { parts = parts ++ [fn_(arr[i])]; i = i + 1; }
35+
array_join(parts, sep)
36+
}
37+
38+
pub fn array_get_exn(arr: [a], idx: Int) -> a { arr[idx] }
39+
40+
pub fn array_make(_size: Int, _default: a) -> [a] { [] }
41+
42+
pub type JsonValue =
43+
| JString(String)
44+
| JNumber(Float)
45+
| JBool(Bool)
46+
| JNull
47+
| JArray([JsonValue])
48+
| JObject([(String, JsonValue)])

0 commit comments

Comments
 (0)