From 801b72c698f2a1363c3bf4de2d1baa3dc3de35b5 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 16 May 2026 09:38:03 +0100 Subject: [PATCH 1/3] fix(faces): stop appending `;` to `--` comment lines in LucidScript The previous pipeline first called `convert_dashdash_comment` (turning `-- text` into `// text`), then passed the result to `strip_dashdash_comment`. Because `strip_dashdash_comment` scans for `--` and found none in the converted line, it returned the entire `// text` string as the "code part". That code part fell through to the expression handler, which appended `;`, producing `// text;`. Fix: apply `strip_dashdash_comment` directly to the raw (pre-conversion) line. A `-- comment` line now yields an empty code part and a populated comment_opt, so it is emitted as `// text\n` with no trailing `;`. As a side effect, `convert_dashdash_comment` becomes unused and is deleted. `is_blank_line` is updated to treat canonical `//` lines as blank (so the next-meaningful-indent scan skips them), and a new dispatch branch in `transform_source` passes canonical `//`/`/*` lines through unchanged before they can reach the equation handler. Updated snapshot `tests/faces/hello-lucid.expected.txt`. --- lib/lucid_face.ml | 47 +++++++++++++--------------- tests/faces/hello-lucid.expected.txt | 21 ++++++------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/lib/lucid_face.ml b/lib/lucid_face.ml index 165e3a43..6db03699 100644 --- a/lib/lucid_face.ml +++ b/lib/lucid_face.ml @@ -50,7 +50,7 @@ - Span fidelity: errors refer to the canonical text, not Lucid source. *) -(* ─── Character helpers ──────────────────────────────────────────────── *) +(* ─── Character helpers ──────────────────────────────────────────── *) let is_id_char c = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') @@ -70,16 +70,7 @@ let indent_of line = while !i < len && (line.[!i] = ' ' || line.[!i] = '\t') do incr i done; !i -(* ─── Comment handling ───────────────────────────────────────────────── *) - -(** Convert a leading [-- comment] to [// comment] at the original indent. *) -let convert_dashdash_comment line = - let trimmed = String.trim line in - if starts_with trimmed "--" then - let indent_len = String.length line - String.length trimmed in - let indent = String.sub line 0 indent_len in - indent ^ "//" ^ String.sub trimmed 2 (String.length trimmed - 2) - else line +(* ─── Comment handling ────────────────────────────────────────────── *) (** Strip a trailing [-- ...] comment (respecting string literals) and return [(code_part, comment_text option)]. *) @@ -105,7 +96,7 @@ let strip_dashdash_comment line = else (String.sub line 0 !cut, Some (String.sub line (!cut + 2) (len - !cut - 2))) -(* ─── Word-level keyword substitution ────────────────────────────────── *) +(* ─── Word-level keyword substitution ───────────────────────────────────── *) let replace_word ~from_w ~to_w s = let flen = String.length from_w in @@ -144,7 +135,7 @@ let apply_logic_subs s = let s = replace_word ~from_w:"not" ~to_w:"!" s in s -(* ─── Module path helpers ────────────────────────────────────────────── *) +(* ─── Module path helpers ────────────────────────────────────────────────── *) (** [Data.Map.Strict] → [Data::Map::Strict]. *) let dots_to_colons s = @@ -156,7 +147,7 @@ let dots_to_colons s = ) s; Buffer.contents buf -(* ─── Import translation ─────────────────────────────────────────────── *) +(* ─── Import translation ───────────────────────────────────────────────────── *) (** Try to transform a PureScript [import …] line. *) let transform_import_line stripped = @@ -221,7 +212,7 @@ let transform_import_line stripped = end end -(* ─── Module declaration ─────────────────────────────────────────────── *) +(* ─── Module declaration ───────────────────────────────────────────────────── *) (** [module Foo where] / [module Foo (exports) where] → [module Foo;]. Canonical AffineScript uses [module] as a file-level header (no @@ -252,7 +243,7 @@ let transform_module_line stripped = Some (Printf.sprintf "module %s;" mod_path) end -(* ─── Type signature handling ────────────────────────────────────────── *) +(* ─── Type signature handling ──────────────────────────────────────────────── *) (** A line of the form [name :: type ...] is a type signature. Lucid keeps it as a comment so the canonical type inferer is the source of truth. @@ -267,7 +258,7 @@ let is_type_signature stripped = has_dcolon && len > 0 && (stripped.[0] >= 'a' && stripped.[0] <= 'z' || stripped.[0] = '_') -(* ─── Data / class / instance declarations ───────────────────────────── *) +(* ─── Data / class / instance declarations ───────────────────────────────────── *) (** [data Foo a b = Ctor1 a | Ctor2] → [type Foo[a, b] = Ctor1(a) | Ctor2]. Best-effort: parameterised constructor arguments wrapped in parens. *) @@ -350,7 +341,7 @@ let transform_instance_decl stripped = | _ -> Some (Printf.sprintf "impl %s {" body) end -(* ─── Function equations ─────────────────────────────────────────────── *) +(* ─── Function equations ────────────────────────────────────────────────────── *) (** [f x y = expr] — wrap parameters and emit canonical [fn]. Returns [None] when the line isn't a recognisable equation. *) @@ -395,7 +386,7 @@ let transform_equation stripped = end end -(* ─── Expression-level substitutions ─────────────────────────────────── *) +(* ─── Expression-level substitutions ───────────────────────────────────────────── *) (** [\x -> body] / [\x y -> body] → [(x, y) => body]. *) let transform_lambda_inline s = @@ -535,11 +526,14 @@ let render_block_head head marker = head ^ " = {" | _ -> head -(* ─── Main transformer ───────────────────────────────────────────────── *) +(* ─── Main transformer ───────────────────────────────────────────────────────── *) let is_blank_line raw = - let (code, _) = strip_dashdash_comment (String.trim raw) in - String.trim code = "" + let t = String.trim raw in + if starts_with t "//" then true + else + let (code, _) = strip_dashdash_comment t in + String.trim code = "" let transform_source source = let lines = Array.of_list (String.split_on_char '\n' source) in @@ -571,8 +565,7 @@ let transform_source source = for i = 0 to n - 1 do let raw_line = lines.(i) in let ind = indent_of raw_line in - let line = convert_dashdash_comment raw_line in - let (code_part, comment_opt) = strip_dashdash_comment (String.trim line) in + let (code_part, comment_opt) = strip_dashdash_comment (String.trim raw_line) in let stripped = String.trim code_part in let with_comment line_text = @@ -585,6 +578,10 @@ let transform_source source = (match comment_opt with | Some c -> Buffer.add_string out ("// " ^ String.trim c ^ "\n") | None -> Buffer.add_char out '\n') + end else if starts_with stripped "//" || starts_with stripped "/*" then begin + (* Already-canonical comment lines pass through unchanged. *) + let indent_str = String.make ind ' ' in + Buffer.add_string out (indent_str ^ stripped ^ "\n") end else if is_type_signature stripped then begin (* Keep type signatures as a comment so the inferer drives types. *) let indent_str = String.make ind ' ' in @@ -661,7 +658,7 @@ let transform_source source = done; Buffer.contents out -(* ─── Entry points ───────────────────────────────────────────────────── *) +(* ─── Entry points ────────────────────────────────────────────────────────────── *) let parse_string_lucid ~file content = let canonical = transform_source content in diff --git a/tests/faces/hello-lucid.expected.txt b/tests/faces/hello-lucid.expected.txt index 7f583d13..aaa821cf 100644 --- a/tests/faces/hello-lucid.expected.txt +++ b/tests/faces/hello-lucid.expected.txt @@ -1,13 +1,13 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later; -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell; -//; -// LucidScript face. Distinctive features exercised: module declaration; -// with `where`, type signatures kept as comments, equation-style; -// function definitions, `; // ` line comments. (The unit-parameter idiom -// `main () = …` lowers to canonical `fn main() { … }`. Function; -// application uses canonical paren syntax `f(x)` rather than Haskell; -// currying `f x` — see examples/faces/README.adoc.); -// face: lucidscript; +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// +// LucidScript face. Distinctive features exercised: module declaration +// with `where`, type signatures kept as comments, equation-style +// function definitions, `--` line comments. (The unit-parameter idiom +// `main () = …` lowers to canonical `fn main() { … }`. Function +// application uses canonical paren syntax `f(x)` rather than Haskell +// currying `f x` — see examples/faces/README.adoc.) +// face: lucidscript module Hello; @@ -17,4 +17,3 @@ effect IO { // main :: -{IO}-> () fn main() { println("Hello, LucidScript!") } - From eaccd8ba97c7867014b339fd3ea53ffeb06de8aa Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 09:18:15 +0000 Subject: [PATCH 2/3] fix(faces/lucid): emit `//` (no trailing space) for blank `--` lines When a `--`-comment line has no text (e.g. just `--`), `strip_dashdash_comment` returns `Some ""`. The previous code would emit `"// " ^ "" ^ "\n"` = `"// \n"`, leaving a trailing space. The expected snapshot (and readable output) needs `"//\n"` with no trailing space. Fix: check the trimmed comment text and emit bare `"//"` when it is empty. https://claude.ai/code/session_01Vrh2f1G8tf7ZbNcKh9r5U9 --- lib/lucid_face.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/lucid_face.ml b/lib/lucid_face.ml index 6db03699..3a36ba65 100644 --- a/lib/lucid_face.ml +++ b/lib/lucid_face.ml @@ -576,8 +576,10 @@ let transform_source source = if stripped = "" then begin (match comment_opt with - | Some c -> Buffer.add_string out ("// " ^ String.trim c ^ "\n") - | None -> Buffer.add_char out '\n') + | Some c -> + let c' = String.trim c in + Buffer.add_string out (if c' = "" then "//\n" else "// " ^ c' ^ "\n") + | None -> Buffer.add_char out '\n') end else if starts_with stripped "//" || starts_with stripped "/*" then begin (* Already-canonical comment lines pass through unchanged. *) let indent_str = String.make ind ' ' in From eb3f2f6a8f23160783494dc746dd5f732299d025 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 10:16:36 +0000 Subject: [PATCH 3/3] fix(lucid-face): strip extra trailing newline from transform_source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When source ends with \n, String.split_on_char '\n' produces a trailing empty string. The loop processes it as an empty line, emitting an extra \n via Buffer.add_char — making output end with \n\n instead of \n. Strip the redundant newline at the end of transform_source so the output matches the snapshot (which was already updated to have exactly one trailing newline). https://claude.ai/code/session_01Vrh2f1G8tf7ZbNcKh9r5U9 --- lib/lucid_face.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/lucid_face.ml b/lib/lucid_face.ml index 3a36ba65..f954e394 100644 --- a/lib/lucid_face.ml +++ b/lib/lucid_face.ml @@ -658,7 +658,10 @@ let transform_source source = for _ = 1 to !toplevel_braces do Buffer.add_string out "}\n" done; - Buffer.contents out + let s = Buffer.contents out in + let l = String.length s in + if l >= 2 && s.[l-1] = '\n' && s.[l-2] = '\n' then String.sub s 0 (l-1) + else s (* ─── Entry points ────────────────────────────────────────────────────────────── *)