Skip to content

Commit b91b31c

Browse files
hyperpolymathclaude
andcommitted
feat(lsp-phase-d): JSON-RPC LSP server (affinescript server --stdio)
Add a full Language Server Protocol 3.17 server as a new `server` subcommand. The server runs entirely in-process — no child processes are spawned for each request. lib/lsp_server.ml (new, ~310 LOC): - Content-Length framing: read_message / write_message - Document store (URI → source text) + pipeline cache (URI → symbols, refs, diagnostics) - run_pipeline: writes in-memory source to a temp file, runs the full compile pipeline (parse → resolve → typecheck → borrow → quantity), fixes up span file paths to match the editor URI, returns diagnostics + symbol table + references - lsp_range: converts 1-based compiler spans to 0-based LSP ranges - diag_to_lsp / publish_diagnostics: push diagnostics on open/change - Handlers: initialize (declares hover + definition + completion), didOpen/didChange (pipeline + cache + push), didClose (clear), textDocument/hover (markdown with type + quantity), textDocument/definition (URI + range), textDocument/completion (items with LSP CompletionItemKind mapping) - MethodNotFound reply for unknown requests; exceptions caught so the server loop never dies on a bad message bin/main.ml: server_cmd wired into the default command group. test/test_e2e.ml: 4 new tests — uri_to_path, lsp_range 0-based conversion, valid source produces no diagnostics, broken source produces at least one diagnostic. 105 total tests, 0 regressions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ccfc615 commit b91b31c

4 files changed

Lines changed: 655 additions & 4 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ project = "affinescript"
66
version = "0.1.0"
77
last-updated = "2026-04-11"
88
status = "active"
9+
session-note-2026-04-11-h = "STAGE 5 (AFFINATEA DRIVES SCENE) COMPLETE: teaDrive(engine, msgTag) is the single entry point from all 4 button handlers — update → applyView → navigate. applyView reads selected_tag via getSelected() and alpha-pulses the chosen button (0.65 → animate 1.0 over 0.22s). teaBridge loaded asynchronously on first show; graceful fallback to direct ReScript navigation when bridge is None. resize handler calls AffineTEA.setScreen(tea, width, height) to keep screen_w/h in sync with canvas. Button handlers reduced to single teaDrive call each. Navigation decision comes FROM Wasm state (selected_tag), not from button identity. Credits button wired (msgCredits) with TODO screen. IDApTIK TitleScreen is fully AffineTEA-driven for Stages 4+5."
10+
session-note-2026-04-11-g = "STAGE 4 (WASM/JS BRIDGE FOR IDAPTIK) COMPLETE: lib/tea_bridge.ml generates complete TitleScreen TEA bridge Wasm 1.0 module (512 bytes). TitleModel at offset 64: screen_w(+0), screen_h(+4), bgm_playing(+8), selected_tag(+12). 7 functions: init, update (branchless selected_tag=msg+1), get_screen_w/h/bgm_playing/selected, set_screen. affinescript.ownership custom section: update param kind=1 (Linear). affinescript.tea_layout custom section: version=1, 4 fields. affinescript tea-bridge subcommand added to bin/main.ml. 6 bridge E2E tests added; 101 total tests passing. AffineTEA.js + AffineTEA.res bindings in idaptik/src/app/tea/. titlescreen.wasm (512 bytes, WebAssembly.validate=true, end-to-end Node.js verified) in idaptik/public/assets/wasm/."
911
session-note-2026-04-11-f = "LSP PHASE C COMPLETE: completion candidates subcommand shipped. extract_prefix_at scans backward from cursor col to extract ident prefix + dot-context flag. collect_completions filters symbol table by prefix and appends affine_keywords (suppressed in dot context). emit_completions emits JSON array {name,kind,type,detail}. complete_file + complete_cmd added to bin/main.ml (FILE LINE COL args). 6 E2E tests. 101 total, 0 regressions. NEXT: LSP Phase D."
1012
session-note-2026-04-11-e = "LSP PHASE B COMPLETE (commit 79c0829): hover/goto-def subcommands shipped. span_contains, find_symbol_at (refs-first then def-spans), hover_to_json/goto_def_to_json, emit_hover/emit_goto_def, run_pipeline_for_query. 89 tests. NEXT: LSP Phase C (completion candidates from symbol table)."
1113
session-note-2026-04-11-d = "TRAITS WIRED (commit 1ca143e): trait_registry in context; TopTrait/TopImpl in forward pass; ExprField trait fallback; find_impl unification-based; TopImpl bodies typechecked; 80 tests. NEXT: LSP Phase B."
@@ -14,8 +16,8 @@ session-note-2026-04-11-b = "BUG-005 CLOSED: WasmGC silent drop+null fallbacks r
1416

1517
[project-context]
1618
name = "affinescript"
17-
completion-percentage = 91
18-
phase = "rattlescript-shipped-next-effects-runtime"
19+
completion-percentage = 94
20+
phase = "affinatea-stage5-complete-stage6-pending"
1921
tagline = "Rust-inspired language with affine types, dependent types, row polymorphism, and extensible effects"
2022
note = "Session 2026-04-11 final checkpoint. DONE THIS SESSION: (1) JS-face (lib/js_face.ml) + Pseudocode-face (lib/pseudocode_face.ml) — both wired into face.ml and bin/main.ml. resolve_face() wired into ALL six file handlers in bin/main.ml (parse/check/eval/compile/fmt/lint) — .rattle/.pyaff/.jsaff/.pseudoaff auto-detected without --face flag. (2) P3 packages: packages/affine-js/ (Deno ESM WASM loader, marshal/unmarshal, types.d.ts), packages/affine-ts/ (typed call helpers), packages/affine-res/ (ReScript bindings). (3) P4 educational: docs/guides/frontier-guide.adoc (6-chapter tutorial), docs/guides/warmup/ (4 warmup scripts). (4) CoffeeScript + ActionScript roadmap faces added to docs/specs/faces.md. (5) RattleScript distribution: distributions/rattlescript/ in-tree (Rust wrapper, build.rs, examples/), standalone repo hyperpolymath/rattlescript (GitHub, starred, v0.1.0-alpha released, 8 topics set, affinescript submodule, just bootstrap workflow). (6) dune build + dune runtest: clean, 0 regressions throughout. NEXT SESSION (clean thread): BUG-005 first (WasmGC silent bad codegen — replace fallback arms with CodegenError, ~30min), then effects runtime (interpreter handler dispatch + WASM codegen — the headline missing feature). Previous session note: P3 (aggregate library ecosystem) and P4 (educational materials) complete. P2 faces extended. 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."
2123

@@ -32,7 +34,7 @@ julia-codegen = "exists"
3234
lsp-phase-a = "complete"
3335
lsp-phase-b = "complete (2026-04-11, commit 79c0829): hover/goto-def subcommands shipped. json_output.ml: span_contains (1-based, single+multi-line), find_symbol_at (references-first then def-spans), hover_to_json/goto_def_to_json/not_found_json, emit_hover/emit_goto_def. bin/main.ml: hover + goto-def subcommands, run_pipeline_for_query tolerates typecheck errors. 4 E2E tests. 89 total."
3436
lsp-phase-c = "complete (2026-04-11): completion candidates subcommand shipped. json_output.ml: extract_prefix_at (scans backward from cursor col to find ident prefix + dot-context flag), collect_completions (symbol table prefix filter + affine_keywords list suppressed in dot context), emit_completions (JSON array). bin/main.ml: complete_file handler + complete_cmd (FILE LINE COL args). 6 E2E tests added (prefix extraction, prefix match, empty prefix, no-match, keyword included, dot-ctx suppresses keywords). 101 total tests, 0 regressions."
35-
lsp-phase-d = "pending"
37+
lsp-phase-d = "complete (2026-04-11): JSON-RPC LSP server shipped as `affinescript server --stdio`. lib/lsp_server.ml: Content-Length framing, document store (URI→source), pipeline cache (URI→symbols+refs+diags), run_pipeline (temp-file + span path fixup), lsp_range (1→0-based conversion), diag_to_lsp, publish_diagnostics. Handlers: initialize (hover+definition+completion caps), didOpen/didChange (pipeline+cache+push), didClose (clear), hover (markdown+type+quantity), definition (URI+range), completion (LSP kind mapping). bin/main.ml: server_cmd in default group. 4 E2E tests. 105 total, 0 regressions."
3638
stdlib = "95% (5 stubs remain as extern builtins)"
3739

3840
[stats]

bin/main.ml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,32 @@ let goto_def_cmd =
792792
let info = Cmd.info "goto-def" ~doc ~man in
793793
Cmd.v info Term.(ret (const goto_def_file $ face_arg $ path_arg $ line_arg $ col_arg))
794794

795+
(** {1 Phase D: LSP server subcommand} *)
796+
797+
(** Start the JSON-RPC LSP server on stdin/stdout.
798+
799+
Handles the full lifecycle: initialize → document sync → hover /
800+
definition / completion → shutdown / exit. The server runs
801+
entirely in-process; no child processes are spawned. *)
802+
let server_file () =
803+
Affinescript.Lsp_server.run ();
804+
`Ok ()
805+
806+
let server_cmd =
807+
let doc = "Start the JSON-RPC LSP server on stdin/stdout" in
808+
let man = [
809+
`S Manpage.s_description;
810+
`P "Starts the AffineScript Language Server Protocol server. \
811+
Communicates over stdin/stdout using JSON-RPC 2.0 with \
812+
Content-Length framing (LSP 3.17 subset).";
813+
`P "Capabilities: full document sync, hover, go-to-definition, \
814+
completion with keyword candidates.";
815+
`P "Configure your editor's LSP client to run: \
816+
$(b,affinescript server --stdio)";
817+
] in
818+
let info = Cmd.info "server" ~doc ~man in
819+
Cmd.v info Term.(ret (const server_file $ const ()))
820+
795821
(** {1 Phase C: completion subcommand} *)
796822

797823
(** Complete subcommand handler.
@@ -837,7 +863,7 @@ let default_cmd =
837863
lex_cmd; parse_cmd; check_cmd; eval_cmd; repl_cmd; compile_cmd;
838864
fmt_cmd; lint_cmd;
839865
tea_bridge_cmd;
840-
hover_cmd; goto_def_cmd; complete_cmd;
866+
hover_cmd; goto_def_cmd; complete_cmd; server_cmd;
841867
preview_python_cmd; preview_js_cmd; preview_pseudocode_cmd
842868
]
843869

0 commit comments

Comments
 (0)