Skip to content

Commit 41480f0

Browse files
hyperpolymathclaude
andcommitted
feat: P3 aggregate packages + P4 educational materials + JS/Pseudocode faces
Completes all remaining items from the AFFINESCRIPT-NEXT.adoc session briefing. Priority 1 (conformance 12/12) and Priority 2 (python-face) were already done. This commit finishes Priority 2 (remaining faces) + Priority 3 + Priority 4. Priority 2 — remaining faces: - lib/js_face.ml: JS/TS-style preprocessor (const/let/var→let, function→fn, async function→fn/Async, null/undefined→(), ===→==, import-from→use) - lib/pseudocode_face.ml: natural-language pseudocode preprocessor (function/procedure→fn, set...to→let, if...then→if{, end→}, is→==, and/or/not, output/print→IO.println, -- comments→//) - lib/face.ml: Js and Pseudocode variants added to face type; full error vocabulary for quantity/unify/type/resolve errors in all four faces - bin/main.ml: --face js/javascript/pseudocode/pseudo options wired; preview-js and preview-pseudocode debug subcommands added - lib/dune: js_face and pseudocode_face added to modules list - docs/specs/faces.md: JS and Pseudocode faces listed as Beta Priority 3 — aggregate library ecosystem: - packages/affine-js/: Deno ESM package — AffineModule (fromFile/fromBytes/ call/runMain), marshal/unmarshal, AFFINE_TAG/AFFINE_SIZE constants, makeRuntimeImports with default IO handlers, types.d.ts declarations - packages/affine-ts/: typed call helpers (callInt/callFloat/callString/ callOption/callResult) + narrowing predicates (isOk/isErr/isSome/isNone/ expectOk/expectSome); re-exports all of affine-js - packages/affine-res/: ReScript bindings — AffineScriptValue.t variant type with toJs/ofJs/expect*/toOption helpers; AffineScript module with fromFile/ fromBytes/call/runMain/callInt/callFloat/callBool/callString/callOption/callOk Priority 4 — educational materials: - docs/guides/frontier-guide.adoc: 6-chapter unveiling tutorial covering all six problems AffineScript solves (null/mutation/errors/effects/rows/WASM) - docs/guides/warmup/: 4 annotated warmup scripts with exercises (01_basics, 02_ownership, 03_effects, 04_rows) 73/73 E2E tests: 0 regressions. Build clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cda36ef commit 41480f0

28 files changed

Lines changed: 3411 additions & 18 deletions

.machine_readable/6a2/STATE.a2ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ status = "active"
99

1010
[project-context]
1111
name = "affinescript"
12-
completion-percentage = 84
13-
phase = "faces-architecture-complete"
12+
completion-percentage = 90
13+
phase = "packages-and-educational-materials-complete"
1414
tagline = "Rust-inspired language with affine types, dependent types, row polymorphism, and extensible effects"
15-
note = "Faces architecture (ADR-010, Priority 2) complete 2026-04-11. (1) lib/python_face.ml: source-level text preprocessor mapping Python surface syntax to canonical AffineScript — indentation→braces, def/True/False/None/and/or/not/class/pass keyword subs, elif/else chains, tail-position semicolon suppression. (2) lib/face.ml: face-aware error formatter mapping canonical error terms to face-specific vocabulary — Python face uses 'Ownership error / single-use variable' for linear violations, 'Name not found' for unbound vars, 'if/else type mismatch' for branch mismatches, renders Unit as None and Bool as bool. (3) bin/main.ml: --face [canonical|python] flag on all subcommands; parse_with_face selects preprocessor path; all error sites route through Face.format_*_error. (4) Span fidelity deferred: error spans currently refer to transformed canonical text positions (planned follow-up). (5) docs/specs/faces.md: face architecture reference doc written. (6) panic-attack audit: 1 High (search.js innerHTML — false positive but fixed to char-replacement), 2 Medium (LSP unwraps/shell heredoc — both false positives), 2 Low (TODO markers — informational). 73/73 E2E tests: 0 regressions. Seam check (items 1+2) verified: --face python on @linear double-use produces correct Python-face ownership error end-to-end."
15+
note = "Session 2026-04-11 continued: P3 (aggregate library ecosystem) and P4 (educational materials) complete. P2 faces extended: JS-face (lib/js_face.ml) and Pseudocode-face (lib/pseudocode_face.ml) added, face.ml and bin/main.ml updated with all four active faces (canonical/python/js/pseudocode). aggregate packages: packages/affine-js/ (Deno ESM, WASM loader, marshal/unmarshal, types.d.ts), packages/affine-ts/ (typed call helpers, narrowing predicates), packages/affine-res/ (ReScript bindings with typed shortcuts and exhaustive matching). educational materials: docs/guides/frontier-guide.adoc (6-chapter unveiling tutorial), docs/guides/warmup/ (4 warmup scripts: basics/ownership/effects/rows). faces.md updated with JS-face and Pseudocode-face entries. Previously: Faces architecture (ADR-010, Priority 2) complete 2026-04-11. (1) lib/python_face.ml: source-level text preprocessor mapping Python surface syntax to canonical AffineScript — indentation→braces, def/True/False/None/and/or/not/class/pass keyword subs, elif/else chains, tail-position semicolon suppression. (2) lib/face.ml: face-aware error formatter mapping canonical error terms to face-specific vocabulary — Python face uses 'Ownership error / single-use variable' for linear violations, 'Name not found' for unbound vars, 'if/else type mismatch' for branch mismatches, renders Unit as None and Bool as bool. (3) bin/main.ml: --face [canonical|python] flag on all subcommands; parse_with_face selects preprocessor path; all error sites route through Face.format_*_error. (4) Span fidelity deferred: error spans currently refer to transformed canonical text positions (planned follow-up). (5) docs/specs/faces.md: face architecture reference doc written. (6) panic-attack audit: 1 High (search.js innerHTML — false positive but fixed to char-replacement), 2 Medium (LSP unwraps/shell heredoc — both false positives), 2 Low (TODO markers — informational). 73/73 E2E tests: 0 regressions. Seam check (items 1+2) verified: --face python on @linear double-use produces correct Python-face ownership error end-to-end."
1616

1717
[components]
1818
lexer = "complete-minus-affine-tokens (ZERO/ONE tokens for quantity literals NOT emitted; blocks QTT surface syntax). float_exponent now correctly named sub-regexp — scientific notation crash fixed 2026-04-11."

bin/main.ml

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ let lex_file path =
6262
(** Parse a file using the requested face. *)
6363
let parse_with_face (face : Affinescript.Face.face) path =
6464
match face with
65-
| Affinescript.Face.Canonical -> Affinescript.Parse_driver.parse_file path
66-
| Affinescript.Face.Python -> Affinescript.Python_face.parse_file_python path
65+
| Affinescript.Face.Canonical -> Affinescript.Parse_driver.parse_file path
66+
| Affinescript.Face.Python -> Affinescript.Python_face.parse_file_python path
67+
| Affinescript.Face.Js -> Affinescript.Js_face.parse_file_js path
68+
| Affinescript.Face.Pseudocode -> Affinescript.Pseudocode_face.parse_file_pseudocode path
6769

6870
(** Preview the Python-face text transform (debug tool). *)
6971
let preview_python_transform path =
@@ -73,6 +75,22 @@ let preview_python_transform path =
7375
print_string (Affinescript.Python_face.preview_transform s);
7476
`Ok ()
7577

78+
(** Preview the JS-face text transform (debug tool). *)
79+
let preview_js_transform path =
80+
let ch = open_in_bin path in
81+
let s = really_input_string ch (in_channel_length ch) in
82+
close_in ch;
83+
print_string (Affinescript.Js_face.preview_transform s);
84+
`Ok ()
85+
86+
(** Preview the pseudocode-face text transform (debug tool). *)
87+
let preview_pseudocode_transform path =
88+
let ch = open_in_bin path in
89+
let s = really_input_string ch (in_channel_length ch) in
90+
close_in ch;
91+
print_string (Affinescript.Pseudocode_face.preview_transform s);
92+
`Ok ()
93+
7694
(** Parse a file and print AST (no --json support). *)
7795
let parse_file (face : Affinescript.Face.face) path =
7896
try
@@ -359,14 +377,19 @@ let compile_file face json wasm_gc path output =
359377
`Error (false, "Parse error")
360378
end
361379

362-
(** Format a file. Only canonical face is supported — Python-face formatting
363-
requires a reverse transform that is not yet implemented. *)
380+
(** Format a file. Only canonical face is supported — non-canonical face
381+
formatting requires a reverse transform that is not yet implemented. *)
364382
let fmt_file face path =
365383
(match face with
366384
| Affinescript.Face.Python ->
367385
Format.eprintf "fmt --face python is not yet supported \
368-
(reverse Python transform is pending).@.";
369-
()
386+
(reverse Python transform is pending).@."; ()
387+
| Affinescript.Face.Js ->
388+
Format.eprintf "fmt --face js is not yet supported \
389+
(reverse JS transform is pending).@."; ()
390+
| Affinescript.Face.Pseudocode ->
391+
Format.eprintf "fmt --face pseudocode is not yet supported \
392+
(reverse pseudocode transform is pending).@."; ()
370393
| Affinescript.Face.Canonical -> ());
371394
try
372395
Affinescript.Formatter.format_file path;
@@ -457,15 +480,24 @@ let wasm_gc_arg =
457480
(** Shared --face flag: select the parser surface-syntax face. *)
458481
let face_arg =
459482
let faces = Arg.enum [
460-
("canonical", Affinescript.Face.Canonical);
461-
("python", Affinescript.Face.Python);
483+
("canonical", Affinescript.Face.Canonical);
484+
("python", Affinescript.Face.Python);
485+
("js", Affinescript.Face.Js);
486+
("javascript", Affinescript.Face.Js);
487+
("pseudocode", Affinescript.Face.Pseudocode);
488+
("pseudo", Affinescript.Face.Pseudocode);
462489
] in
463490
Arg.(value & opt faces Affinescript.Face.Canonical & info ["face"]
464491
~docv:"FACE"
465-
~doc:"Parser face (surface-syntax variant). $(docv) must be $(b,canonical) \
466-
(default, standard AffineScript) or $(b,python) (Python-style syntax: \
467-
indentation-based blocks, $(b,def)/$(b,True)/$(b,False)/$(b,None)/\
468-
$(b,and)/$(b,or)/$(b,not) etc. — compiled to the same canonical AST).")
492+
~doc:"Parser face (surface-syntax variant). \
493+
$(b,canonical) (default) — standard AffineScript. \
494+
$(b,python) — Python-style syntax ($(b,def)/indentation/$(b,True)/$(b,None)/etc.). \
495+
$(b,js) or $(b,javascript) — JavaScript-style syntax \
496+
($(b,const)/$(b,let)/$(b,function)/$(b,=>)/$(b,null)/$(b,===)/import-from). \
497+
$(b,pseudocode) or $(b,pseudo) — natural-language pseudocode \
498+
($(b,function)/$(b,set...to)/$(b,if...then)/$(b,end)/$(b,is)/$(b,and)/etc.). \
499+
All faces compile to the same canonical AST; errors are reported \
500+
in face-appropriate vocabulary.")
469501

470502
let lex_cmd =
471503
let doc = "Lex a file and print tokens" in
@@ -512,10 +544,24 @@ let preview_python_cmd =
512544
let info = Cmd.info "preview-python" ~doc in
513545
Cmd.v info Term.(ret (const preview_python_transform $ path_arg))
514546

547+
let preview_js_cmd =
548+
let doc = "Preview the JS-face text transform (debug)" in
549+
let info = Cmd.info "preview-js" ~doc in
550+
Cmd.v info Term.(ret (const preview_js_transform $ path_arg))
551+
552+
let preview_pseudocode_cmd =
553+
let doc = "Preview the pseudocode-face text transform (debug)" in
554+
let info = Cmd.info "preview-pseudocode" ~doc in
555+
Cmd.v info Term.(ret (const preview_pseudocode_transform $ path_arg))
556+
515557
let default_cmd =
516558
let doc = "The AffineScript compiler" in
517559
let info = Cmd.info "affinescript" ~version ~doc in
518560
let default = Term.(ret (const (`Help (`Pager, None)))) in
519-
Cmd.group info ~default [lex_cmd; parse_cmd; check_cmd; eval_cmd; repl_cmd; compile_cmd; fmt_cmd; lint_cmd; preview_python_cmd]
561+
Cmd.group info ~default [
562+
lex_cmd; parse_cmd; check_cmd; eval_cmd; repl_cmd; compile_cmd;
563+
fmt_cmd; lint_cmd;
564+
preview_python_cmd; preview_js_cmd; preview_pseudocode_cmd
565+
]
520566

521567
let () = exit (Cmd.eval default_cmd)

0 commit comments

Comments
 (0)