ludic/tools/ludic-cli/syntax_cli.ludic
Orkuncakilkaya 2ad6edaae3 ludic syntax, and ludic-dev syntax: every grammar written from the compiler's vocabulary and checked against it
`ludic syntax [--json] [-o FILE]` prints what `ludicc --emit-syntax` does (a line
per entry, or the JSON). `ludic-dev syntax` writes, between "ludic-dev syntax:
begin" / "end" lines, the keyword, type, phase and attribute tables of
ludic_syntax.h, LudicVocabulary's sets (JetBrains), ludic-mode.el's lists and the
language server's word tests (is_keyword_word and the rest; is_contextual_word is
every word the parser does not reserve, and every declaring or modifying one),
and every TextMate pattern marked "comment": "ludic-dev syntax: <group>" (shared
and the VS Code copy). The grammars gain module uses port bind action reducer
dispatch registry def open component prop view alias friend unsafe numbers of as
from mut system; import and extern colour as declarations; the phase clause
knows Overlay; the bitwise pattern matches | and ^ on their own again.

`ludic-dev syntax --check` - and check-vocabulary, whose old parser comparison it
replaces, and the regression suite (syntax_cases, one line per file) - fails when
a written list is behind, when a grammar lacks a keyword, type or phase, when
docs/language has no page for a keyword, type, phase or attribute, or when the
parser (a scan of selfhost/frontend: is_id / text == words, a == / ann ==
attributes) tests a word or reads an attribute vocab.ludic lacks, or the reverse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:59:30 +03:00

86 lines
3 KiB
Text

# ---- ludic syntax -------------------------------------------------------------
# The language's vocabulary as the compiler holds it (`ludicc --emit-syntax`,
# selfhost/frontend/vocab.ludic): every keyword with its role and whether it is
# reserved, every declaration's form, the built-in types, the phases, every
# attribute with where it goes, what it takes and what it means, the operators and
# the literal forms. An editor's grammar is written from it (`ludic-dev syntax`).
#
# ludic syntax one line per entry: kind, name, role
# ludic syntax --json the JSON, as the compiler writes it
# ludic syntax ... -o FILE into FILE
# the vocabulary's JSON from the compiler, or null when it cannot say
function syntax_json_text() -> pointer {
ensure_ludicc()
let js = tmp_path("syntax.json")
if not shq(`{ludicc()} --emit-syntax > {sh_single(js)} 2> {tmp_path("syntax.err")}`) { return null }
let s = read_file(js)
if s == null or slen(s) == 0 or s[0] != '{' { return null }
return s
}
function cmd_syntax() -> int {
var json = false
var dest = ""
var ai = 2
while ai < arg_count() {
let a = arg(ai)
if a == "--json" { json = true }
else if a == "-o" or a == "--out" {
if ai + 1 >= arg_count() { err(`ludic syntax: {a} needs a file\n`); return 2 }
ai += 1
dest = arg(ai)
}
else {
err(`ludic syntax: unknown argument {a}\n`)
err(" usage: ludic syntax [--json] [-o FILE]\n")
return 2
}
ai += 1
}
let s = syntax_json_text()
if s == null { err("ludic syntax: the compiler wrote no vocabulary (is it older than ludic syntax?)\n"); return 1 }
var text = s
if not json { text = syntax_lines(s) }
if dest == "" { out(text); return 0 }
if not write_file(dest, text) { err(`ludic syntax: cannot write {dest}\n`); return 1 }
return 0
}
# the plain listing, `keyword module declaration`, one entry a line - read off the JSON's own
# layout (a list opens on ` "name": [`, one entry per line after it) rather than parsed
function syntax_lines(s: pointer) -> pointer {
var o = ""
var kind = ""
let n = slen(s)
var i = 0
while i < n {
let ln = line_at(s, i)
i = i + slen(ln) + 1
if s_starts(ln, " \"") and s_contains(ln, "[") {
kind = sslice(ln, 3, s_index(ln, "\"", 3) - 1) # "keywords" -> keyword
continue
}
if not s_starts(ln, " {\"") { continue }
var name = syntax_field(ln, s_index(ln, "\": ", 0) + 3)
if kind == "attribute" { name = "@" + name }
var role = ""
let r = s_index(ln, "\"role\": ", 0)
if r >= 0 { role = syntax_field(ln, r + 8) }
let f = s_index(ln, "\"form\": ", 0)
if f >= 0 { role = syntax_field(ln, f + 8) }
o = o + kind + "\t" + name + "\t" + role + "\n"
}
return o
}
# the JSON string starting at ln[q] (its opening quote), unescaped
function syntax_field(ln: pointer, q: int) -> pointer {
var o = ""
var i = q + 1
while ln[i] != 0 and ln[i] != '"' {
if ln[i] == '\\' { i += 1 }
o = o + sslice(ln, i, i + 1)
i += 1
}
return o
}