diff --git a/dist/platform/elc b/dist/platform/elc index a4de585..44b0d57 100755 Binary files a/dist/platform/elc and b/dist/platform/elc differ diff --git a/dist/platform/elc.20260502-1104-self-host b/dist/platform/elc.20260502-1104-self-host new file mode 100755 index 0000000..44b0d57 Binary files /dev/null and b/dist/platform/elc.20260502-1104-self-host differ diff --git a/el-compiler/src/lexer.el b/el-compiler/src/lexer.el index 6f7f742..d6c6d0c 100644 --- a/el-compiler/src/lexer.el +++ b/el-compiler/src/lexer.el @@ -195,6 +195,208 @@ fn scan_ident(chars: [String], start: Int, total: Int) -> Map { { "text": text, "pos": i } } +// ── Code-bearing string detection + comment strip ──────────────────────────── +// Inline JS/CSS literals embedded in El source (e.g. blobs +// or stylesheet payloads inside string literals) carry their own line and +// block comments. Those comments leak into the served HTML and reveal build +// notes the visitor should never see. We strip them at the lexer so every +// downstream consumer (codegen-c, codegen-js, parser) gets the cleaned form. +// +// looks_like_code — heuristic gate so we only strip strings that actually +// embed JS or CSS. Plain prose, hex blobs, JSON, etc. pass through verbatim. + +fn substr_at(chars: [String], start: Int, total: Int, needle: String) -> Bool { + let nchars: [String] = native_string_chars(needle) + let nlen: Int = native_list_len(nchars) + if start + nlen > total { return false } + let i = 0 + let matched = true + while i < nlen { + let a: String = native_list_get(chars, start + i) + let b: String = native_list_get(nchars, i) + if a == b { let i = i + 1 } else { let matched = false; let i = nlen } + } + matched +} + +fn str_has(s: String, needle: String) -> Bool { + let chars: [String] = native_string_chars(s) + let total: Int = native_list_len(chars) + let i = 0 + let found = false + while i < total { + if substr_at(chars, i, total, needle) { + let found = true + let i = total + } else { + let i = i + 1 + } + } + found +} + +fn looks_like_code(s: String) -> Bool { + if str_has(s, " String { + let chars: [String] = native_string_chars(s) + let total: Int = native_list_len(chars) + let out = "" + let i = 0 + let in_squote = false + let in_dquote = false + let in_btick = false + let prev = "" + while i < total { + let ch: String = native_list_get(chars, i) + let in_js_string = false + if in_squote { let in_js_string = true } + if in_dquote { let in_js_string = true } + if in_btick { let in_js_string = true } + + if in_js_string { + // Backslash escape: consume next char verbatim regardless of which. + if ch == "\\" { + let out = out + ch + let next_i = i + 1 + if next_i < total { + let nc: String = native_list_get(chars, next_i) + let out = out + nc + let prev = nc + let i = next_i + 1 + } else { + let prev = ch + let i = next_i + } + } else { + if in_squote { + if ch == "'" { let in_squote = false } + } else { + if in_dquote { + if ch == "\"" { let in_dquote = false } + } else { + if in_btick { + if ch == "`" { let in_btick = false } + } + } + } + let out = out + ch + let prev = ch + let i = i + 1 + } + } else { + // Not in a JS string. Check for comment openers. + let next_i = i + 1 + let next_ch = "" + if next_i < total { + let next_ch: String = native_list_get(chars, next_i) + } + + if ch == "/" { + if next_ch == "/" { + // URL guard: prev char ':' means this is "://", not a comment. + if prev == ":" { + let out = out + ch + let prev = ch + let i = i + 1 + } else { + // Skip until newline (newline itself is preserved so + // surrounding line counts/structure stay sane). + let i = i + 2 + let scanning = true + while scanning { + if i >= total { + let scanning = false + } else { + let lc: String = native_list_get(chars, i) + if lc == "\n" { + let scanning = false + } else { + let i = i + 1 + } + } + } + let prev = "" + } + } else { + if next_ch == "*" { + // Skip until matching "*/". + let i = i + 2 + let scanning2 = true + while scanning2 { + if i >= total { + let scanning2 = false + } else { + let bc: String = native_list_get(chars, i) + if bc == "*" { + let after = i + 1 + if after < total { + let nc2: String = native_list_get(chars, after) + if nc2 == "/" { + let i = after + 1 + let scanning2 = false + } else { + let i = i + 1 + } + } else { + let i = i + 1 + } + } else { + let i = i + 1 + } + } + } + let prev = "" + } else { + let out = out + ch + let prev = ch + let i = i + 1 + } + } + } else { + // Open a JS string? + if ch == "'" { + let in_squote = true + let out = out + ch + let prev = ch + let i = i + 1 + } else { + if ch == "\"" { + let in_dquote = true + let out = out + ch + let prev = ch + let i = i + 1 + } else { + if ch == "`" { + let in_btick = true + let out = out + ch + let prev = ch + let i = i + 1 + } else { + let out = out + ch + let prev = ch + let i = i + 1 + } + } + } + } + } + } + out +} + // scan_string — scan a quoted string literal, handling \" escapes. // Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close } fn scan_string(chars: [String], start: Int, total: Int) -> Map { @@ -305,7 +507,15 @@ fn lex(source: String) -> [Map] { let result = scan_string(chars, i + 1, total) let str_text: String = result["text"] let new_pos: Int = result["pos"] - let tokens = native_list_append(tokens, make_tok("Str", str_text)) + // Compile-time scrub: strings that embed JS or CSS get + // their // line comments and /* block comments stripped + // before the token reaches the parser. Plain prose passes + // through untouched. + let clean_text = str_text + if looks_like_code(str_text) { + let clean_text = strip_code_comments(str_text) + } + let tokens = native_list_append(tokens, make_tok("Str", clean_text)) let i = new_pos } else { // Number literal diff --git a/elc-cli.el b/elc-cli.el index cb67683..6b9b25d 100644 --- a/elc-cli.el +++ b/elc-cli.el @@ -1,8 +1,7 @@ +// elc-cli.el — entry point for the self-hosted el compiler. +// +// All logic lives in el-compiler/src/compiler.el (which defines fn main()). +// Importing it pulls in compiler.el + parser + lexer + codegen transitively. +// The native compiler resolves imports textually and folds fn main() into +// C's int main(), so this file does not declare any top-level work itself. import "el-compiler/src/compiler.el" - -// compile() streams C source to stdout via println. -// We read source from args()[0] and compile it. -let _argv: [String] = args() -let src_path: String = native_list_get(_argv, 0) -let source: String = fs_read(src_path) -compile(source)