refactor(cli)!: split the contributor tool out of the ludic CLI
`ludic help` ended with a section titled "contributing to the toolchain itself", listing bootstrap, reseed, docs-gen and release tasks. None of that is available to someone who installed the language — those tasks need the repository — so the shipped tool was advertising work its user cannot do, in a namespace they have to read past to find `new` and `run`. The tasks move to a second program, dev.ludic -> bin/ludic-dev, built from a checkout and excluded from every release artifact. `ludic` keeps the project and package commands and nothing else; `ludic dev …` now explains where the tasks went instead of failing as an unknown command. What this shook out: the two programs share prelude/build/project/pkg, so the helpers each had accreted in whichever file first needed them — cc(), ensure_ludicc, the string functions, title_case, cmd_version — moved to where both can see them. The argument-shift indirection added for the `dev` namespace is gone with the namespace, so commands read argv directly again. `ludic-dev test` asserts the split rather than trusting it: the staged install must build a project, and `ludic dev build` there must fail while naming ludic-dev. install.sh keeps building older tags, whose bootstrap goes through main.ludic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f369fbd227
commit
e175619543
46 changed files with 630 additions and 558 deletions
|
|
@ -10,7 +10,7 @@ docs/
|
|||
language/<category>/<id>.md one file per symbol — keyword, type, phase,
|
||||
builtin, namespace method, operator, annotation
|
||||
language/<category>/_section.md section title + blurb + order
|
||||
language/colors/palette.json the 221 named colors (generated by `ludic dev docs-palette`)
|
||||
language/colors/palette.json the 221 named colors (generated by `ludic-dev docs-palette`)
|
||||
site/site.json landing-page messaging (hero, features, …)
|
||||
site/snippets/*.ludic the code shown on the landing page (real programs)
|
||||
tools/docgen/inventory.json the authoritative symbol set the coverage guard checks
|
||||
|
|
@ -66,9 +66,9 @@ no Python in the pipeline. `assets/` (the CSS/HTML/JS templates) and
|
|||
`inventory.json` are the only inputs the tools here still read directly.
|
||||
|
||||
```bash
|
||||
bin/ludic dev docs-gen --out build/pages # generate the whole site
|
||||
bin/ludic dev docs-check build/pages # coverage + duplicate-token + link guard
|
||||
bin/ludic dev check-docs # parse every ```ludic doc fence
|
||||
bin/ludic-dev docs-gen --out build/pages # generate the whole site
|
||||
bin/ludic-dev docs-check build/pages # coverage + duplicate-token + link guard
|
||||
bin/ludic-dev check-docs # parse every ```ludic doc fence
|
||||
```
|
||||
|
||||
`docs-check` fails CI if any symbol in `inventory.json` lacks a page, if a token
|
||||
|
|
@ -79,13 +79,13 @@ built `bin/ludic` (bootstrapped from the IR seed with clang alone).
|
|||
## Publish
|
||||
|
||||
`.forgejo/workflows/docs.yml` bootstraps the toolchain from the IR seed and runs
|
||||
`ludic dev docs-gen` + `ludic dev docs-check` on every push to `main` that touches `docs/**`,
|
||||
`ludic-dev docs-gen` + `ludic-dev docs-check` on every push to `main` that touches `docs/**`,
|
||||
`tools/docgen/**` or `tools/ludic-cli/**`, and publishes the result to the `pages` branch
|
||||
root. `index.html` + `.nojekyll` always stay at the root.
|
||||
|
||||
## Colors
|
||||
|
||||
`palette.json` and `selfhost/backend/stdlib/emit_color.ludic` are both generated
|
||||
by `bin/ludic dev docs-palette` from a single palette table — the `pal_add(...)` rows in
|
||||
by `bin/ludic-dev docs-palette` from a single palette table — the `pal_add(...)` rows in
|
||||
`tools/ludic-cli/docgen.ludic`. Edit the table there and regenerate; do not hand-edit the
|
||||
generated files.
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ it needs no path configuration.
|
|||
From a checkout, build them with:
|
||||
|
||||
```bash
|
||||
bin/ludic dev tools
|
||||
bin/ludic-dev tools
|
||||
```
|
||||
|
||||
Produces `bin/ludic-fmt` and `bin/ludic-lsp`. Add `--install` to symlink both
|
||||
into `~/.local/bin`, `--test` to run `bin/ludic dev test-tools` afterwards.
|
||||
into `~/.local/bin`, `--test` to run `bin/ludic-dev test-tools` afterwards.
|
||||
|
||||
## What you get, in any editor
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ ln -sf ../../tools/git-hooks/pre-commit .git/hooks/pre-commit
|
|||
|
||||
The hook runs `ludic-fmt --check` over the staged `.ludic` and `.md` files only,
|
||||
and does nothing at all when `bin/ludic-fmt` has not been built — so it never
|
||||
blocks a commit on a machine that has not run `bin/ludic dev tools`.
|
||||
blocks a commit on a machine that has not run `bin/ludic-dev tools`.
|
||||
|
||||
## Why the formatter is not `ludicc --fmt`
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ both spellings are what the language documents and uses:
|
|||
(`const R_DIR: int = 0 # 0 up`) survive a save;
|
||||
- `id=Root` inside a `ui` block and `{Enemy}` inside a query stay tight.
|
||||
|
||||
`bin/ludic dev test-tools` checks the property that matters: formatting every file in
|
||||
`bin/ludic-dev test-tools` checks the property that matters: formatting every file in
|
||||
the tree and re-running the *compiler's* canonical dump produces byte-identical
|
||||
output. The formatter cannot change what a program means.
|
||||
|
||||
|
|
@ -149,8 +149,8 @@ output. The formatter cannot change what a program means.
|
|||
The vocabulary is written down in five places that cannot include each other —
|
||||
the compiler's two tables, `ludic_syntax.h`, the TextMate grammar (JSON), and the
|
||||
JetBrains lexer (Kotlin). Adding a builtin and forgetting the rest is silent
|
||||
failure, so `bin/ludic dev check-vocabulary` (written in Ludic) compares all five, and
|
||||
`bin/ludic dev test-tools` runs it.
|
||||
failure, so `bin/ludic-dev check-vocabulary` (written in Ludic) compares all five, and
|
||||
`bin/ludic-dev test-tools` runs it.
|
||||
|
||||
When you add a keyword or builtin: put it in `ludic_syntax.h`, then run
|
||||
`bin/ludic dev test-tools` and let it tell you which copies still need it.
|
||||
`bin/ludic-dev test-tools` and let it tell you which copies still need it.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ The first build downloads an IntelliJ IDEA Community distribution (over 1 GB),
|
|||
so expect it to take a while; later builds take seconds. Once the cache is warm,
|
||||
|
||||
```bash
|
||||
LUDIC_TEST_JETBRAINS=1 bin/ludic dev test-tools
|
||||
LUDIC_TEST_JETBRAINS=1 bin/ludic-dev test-tools
|
||||
```
|
||||
|
||||
includes the plugin build in the toolchain's own test run.
|
||||
|
|
@ -55,4 +55,4 @@ LSP4IJ is a required dependency; the IDE offers to install it for you.
|
|||
|
||||
**Settings -> Languages & Frameworks -> Ludic** — the path to `ludic-lsp` and to
|
||||
`ludicc`. Both default to `bin/` under the project root, which is where
|
||||
`bin/ludic dev tools` puts them.
|
||||
`bin/ludic-dev tools` puts them.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import java.io.File
|
|||
|
||||
/**
|
||||
* Where to find the toolchain. Both paths may be left blank, in which case the
|
||||
* plugin looks under the project root for what `bin/ludic dev tools` produces —
|
||||
* plugin looks under the project root for what `bin/ludic-dev tools` produces —
|
||||
* which is the case for anyone working in the Ludic repository itself.
|
||||
*/
|
||||
@Service(Service.Level.PROJECT)
|
||||
|
|
@ -121,7 +121,7 @@ class LudicConnectionProvider(private val project: Project) : ProcessStreamConne
|
|||
append(" on PATH, and in ~/.ludic/bin.\n")
|
||||
}
|
||||
append("\nInstall the toolchain (curl -fsSL https://workshopsoft.pages.workshopsoft.io/ludic/install.sh | sh), ")
|
||||
append("build it with `bin/ludic dev tools` from a checkout, ")
|
||||
append("build it with `bin/ludic-dev tools` from a checkout, ")
|
||||
append("or set the path in Settings | Languages & Frameworks | Ludic. ")
|
||||
append("Syntax highlighting works without it; completion and diagnostics do not.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
name="Ludic"
|
||||
factoryClass="io.ludic.ide.LudicLanguageServerFactory">
|
||||
<description><![CDATA[
|
||||
The Ludic language server. Built by <code>bin/ludic dev tools</code>.
|
||||
The Ludic language server. Built by <code>bin/ludic-dev tools</code>.
|
||||
]]></description>
|
||||
</server>
|
||||
<languageMapping language="Ludic" serverId="ludic"/>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ for the language reference.
|
|||
Build the toolchain once, from the repository root:
|
||||
|
||||
```bash
|
||||
bin/ludic dev tools
|
||||
bin/ludic-dev tools
|
||||
```
|
||||
|
||||
That produces `bin/ludic-lsp` and `bin/ludic-fmt`, which the extension finds
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ async function start(context) {
|
|||
// Not an error worth a modal: the grammar still highlights, and plenty of
|
||||
// people open a .ludic file without having built the toolchain.
|
||||
output.appendLine(
|
||||
'ludic-lsp not found. Install the toolchain (curl -fsSL https://ludic.dev/install.sh | sh), build it from a checkout with `bin/ludic dev tools`, or set "ludic.server.path". ' +
|
||||
'ludic-lsp not found. Install the toolchain (curl -fsSL https://ludic.dev/install.sh | sh), build it from a checkout with `bin/ludic-dev tools`, or set "ludic.server.path". ' +
|
||||
'Syntax highlighting works without it; completion and diagnostics do not.'
|
||||
);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,56 +1,30 @@
|
|||
# build.ludic — compiling: a user's program (ludic build / ludic run) and the
|
||||
# toolchain's own binaries (ludic dev build).
|
||||
# build.ludic — turning a .ludic file into an executable.
|
||||
#
|
||||
# compile_app is the one place a .ludic file becomes an executable, so a project
|
||||
# build and the repo's own example builds cannot drift apart. Everything the
|
||||
# toolchain owns — the compiler, the engine runtime it splices — is addressed
|
||||
# through ludic_home(), so the same code path works from a checkout and from an
|
||||
# installed ~/.ludic.
|
||||
# compile_app is the one place that happens, shared by `ludic build` / `ludic
|
||||
# run` and by the contributor tool, so a user's build and the repo's own example
|
||||
# builds cannot drift apart. Everything the toolchain owns — the compiler, the
|
||||
# engine runtime it splices — is addressed through ludic_home(), so the same code
|
||||
# path works from a checkout and from an installed ~/.ludic.
|
||||
|
||||
# compile a Ludic source to a native binary in bin/ via ludicc + clang (-O2).
|
||||
# Returns true on success. Used for x itself and the editor tools.
|
||||
function build_tool(name: pointer, src: pointer) -> bool {
|
||||
let ll = `build/{name}.ll`
|
||||
if not shq(`bin/ludicc {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
||||
if not shq(`{cc()} -O2 {ll} -o bin/{name}.tmp`) { print(`build failed: {name} (link)`); return false }
|
||||
run(`mv -f bin/{name}.tmp bin/{name} && rm -f {ll}`)
|
||||
return true
|
||||
}
|
||||
|
||||
# ---- build-cli: the self-hosted front-end binaries ---------------------------
|
||||
# Both ludicc and ludic are the SAME multi-call binary assembled from the IR seed
|
||||
# with clang alone; invoked as `ludic` it compiles-and-runs, as `ludicc` it just
|
||||
# compiles.
|
||||
function cmd_dev_build_cli() -> int {
|
||||
# ensure bin/ludicc exists and is current with the seed. Built from the checked-in
|
||||
# IR seed with clang alone — no C compiler is ever involved.
|
||||
function ensure_ludicc() -> void {
|
||||
if not in_toolchain_repo() { return }
|
||||
run("mkdir -p bin build")
|
||||
print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) { err("ludic: the seed did not assemble\n"); return 1 }
|
||||
run("chmod +x bin/ludicc")
|
||||
print("done. built bin/ludicc")
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- build: the whole toolchain (ludicc, ludic, ludic-fmt, ludic-lsp) -------
|
||||
function cmd_dev_build() -> int {
|
||||
if cmd_dev_build_cli() != 0 { return 1 }
|
||||
print("ludicc: tools/ludic-cli/main.ludic -> bin/ludic (the CLI rebuilds itself)")
|
||||
if not build_tool("ludic", "tools/ludic-cli/main.ludic") { return 1 }
|
||||
print("ludicc: tools/ludic-tools/fmt.ludic -> bin/ludic-fmt")
|
||||
if not build_tool("ludic-fmt", "tools/ludic-tools/fmt.ludic") { return 1 }
|
||||
print("ludicc: tools/ludic-tools/lsp.ludic -> bin/ludic-lsp")
|
||||
if not build_tool("ludic-lsp", "tools/ludic-tools/lsp.ludic") { return 1 }
|
||||
sync_vscode_grammar()
|
||||
print("done. toolchain in bin/ (ludicc, ludic, ludic-fmt, ludic-lsp)")
|
||||
return 0
|
||||
if (not is_exec("bin/ludicc")) or newer("selfhost/ludicc.seed.ll", "bin/ludicc") {
|
||||
print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) {
|
||||
err("ludic: could not assemble the seed\n"); exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---- compiling one program to a native binary -------------------------------
|
||||
#
|
||||
# mode 1 = windowed (a game opens a real window), 2 = headless (the last frame is
|
||||
# rendered to build/out.ppm from piped input — deterministic output for tests).
|
||||
# Returns true on success. `ludic build`, `ludic run` and `ludic dev app` all go
|
||||
# through here.
|
||||
# Returns true on success. `ludic build`, `ludic run` and the contributor tool all
|
||||
# go through here.
|
||||
function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool {
|
||||
ensure_ludicc() # in a checkout: assemble the seed if bin/ludicc is missing or stale
|
||||
let home = ludic_home()
|
||||
|
|
@ -91,7 +65,7 @@ function dir_of_path(p: pointer) -> pointer {
|
|||
# build/out.ppm), and any stray *.tmp/*.ll left behind by a failed build. A
|
||||
# legacy out.ppm in the root (from an older toolchain) is swept too. Binaries in
|
||||
# bin/ are kept so the running CLI survives; in the toolchain repo, rebuild them
|
||||
# any time with `ludic dev build`.
|
||||
# any time with `ludic-dev build`.
|
||||
function cmd_clean() -> int {
|
||||
run("rm -rf build")
|
||||
run("rm -f out.ppm bin/*.tmp")
|
||||
|
|
|
|||
|
|
@ -9,75 +9,6 @@
|
|||
# plain indexing; `==` on pointers is a byte-string compare (the whole toolchain
|
||||
# leans on this).
|
||||
|
||||
# ---- small string helpers ---------------------------------------------------
|
||||
|
||||
# length of a NUL-terminated buffer
|
||||
function slen(s: pointer) -> int { var n = 0; while s[n] != 0 { n += 1 }; return n }
|
||||
|
||||
# a fresh NUL-terminated copy of s[start .. end) (end exclusive)
|
||||
function sslice(s: pointer, start: int, end: int) -> pointer {
|
||||
if end < start { return "" }
|
||||
let n = end - start
|
||||
let b = bytes(n + 1)
|
||||
var i = 0
|
||||
while i < n { b[i] = s[start + i]; i += 1 }
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
|
||||
# index of the first byte of `needle` in `hay` at or after `from`, else -1
|
||||
function s_index(hay: pointer, needle: pointer, from: int) -> int {
|
||||
let hn = slen(hay)
|
||||
let nn = slen(needle)
|
||||
if nn == 0 { return from }
|
||||
var i = from
|
||||
while i + nn <= hn {
|
||||
var j = 0
|
||||
while j < nn and hay[i + j] == needle[j] { j += 1 }
|
||||
if j == nn { return i }
|
||||
i += 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function s_contains(hay: pointer, needle: pointer) -> bool { return s_index(hay, needle, 0) >= 0 }
|
||||
|
||||
# does `hay` contain `needle` exactly at position `at`?
|
||||
function s_starts_at(hay: pointer, at: int, needle: pointer) -> bool {
|
||||
let nn = slen(needle)
|
||||
var i = 0
|
||||
while i < nn { if hay[at + i] != needle[i] { return false }; i += 1 }
|
||||
return true
|
||||
}
|
||||
|
||||
# does `s` (a whole line) begin with `pre`?
|
||||
function s_starts(s: pointer, pre: pointer) -> bool {
|
||||
let pn = slen(pre)
|
||||
var i = 0
|
||||
while i < pn { if s[i] != pre[i] { return false }; i += 1 }
|
||||
return true
|
||||
}
|
||||
|
||||
# is byte c an ASCII space/tab?
|
||||
function is_ws(c: int) -> bool { return c == ' ' or c == '\t' }
|
||||
|
||||
# the substring from `start` up to the next '\n' (or end)
|
||||
function line_at(s: pointer, start: int) -> pointer {
|
||||
var e = start
|
||||
while s[e] != 0 and s[e] != '\n' { e += 1 }
|
||||
return sslice(s, start, e)
|
||||
}
|
||||
|
||||
# trim leading/trailing ASCII whitespace (space, tab, cr, nl)
|
||||
function s_trim(s: pointer) -> pointer {
|
||||
let n = slen(s)
|
||||
var a = 0
|
||||
while a < n and (is_ws(s[a]) or s[a] == '\n' or s[a] == '\r') { a += 1 }
|
||||
var b = n
|
||||
while b > a and (is_ws(s[b - 1]) or s[b - 1] == '\n' or s[b - 1] == '\r') { b -= 1 }
|
||||
return sslice(s, a, b)
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# check-docs — every ```ludic fence in the docs must parse (or be marked)
|
||||
# ============================================================================
|
||||
|
|
@ -177,7 +108,7 @@ function count_nl(s: pointer, upto: int) -> int {
|
|||
return n
|
||||
}
|
||||
|
||||
# usage: ludic dev check-docs (scans the user-facing docs + docs/language/**)
|
||||
# usage: ludic-dev check-docs (scans the user-facing docs + docs/language/**)
|
||||
function cmd_check_docs() -> int {
|
||||
ensure_ludicc()
|
||||
DC_OK = 0; DC_BAD = 0; DC_SKIP = 0; DC_FAILS = ""
|
||||
|
|
@ -361,7 +292,7 @@ var CI_PROB: pointer = ""
|
|||
var CI_NPROB: int = 0
|
||||
function ci_problem(msg: pointer) -> void { CI_PROB = CI_PROB + " - " + msg + "\n"; CI_NPROB += 1 }
|
||||
|
||||
# usage: ludic dev check-impl
|
||||
# usage: ludic-dev check-impl
|
||||
function cmd_check_impl() -> int {
|
||||
CI_PROB = ""; CI_NPROB = 0
|
||||
let allsrc = read_all_selfhost()
|
||||
|
|
@ -568,7 +499,7 @@ function set_union(a: []pointer, b: []pointer) -> []pointer {
|
|||
return out
|
||||
}
|
||||
|
||||
# usage: ludic dev check-vocabulary
|
||||
# usage: ludic-dev check-vocabulary
|
||||
function cmd_check_vocab() -> int {
|
||||
CV_PROB = ""; CV_N = 0
|
||||
let h = read_file("tools/ludic-tools/ludic_syntax.h")
|
||||
|
|
@ -702,10 +633,10 @@ function xml_valid(text: pointer) -> bool {
|
|||
return sp == 0
|
||||
}
|
||||
|
||||
# usage: ludic dev lint-asset <file> (validates one .json or .xml editor asset)
|
||||
# usage: ludic-dev lint-asset <file> (validates one .json or .xml editor asset)
|
||||
function cmd_lint_asset() -> int {
|
||||
if arg_total() < 3 { err("usage: ludic dev lint-asset <file.json|file.xml>\n"); return 2 }
|
||||
let path = arg_n(2)
|
||||
if arg_count() < 3 { err("usage: ludic-dev lint-asset <file.json|file.xml>\n"); return 2 }
|
||||
let path = arg(2)
|
||||
let t = read_file(path)
|
||||
if t == null { err("lint-asset: cannot read " + path + "\n"); return 1 }
|
||||
if s_index(path, ".json", 0) >= 0 {
|
||||
|
|
|
|||
137
tools/ludic-cli/dev.ludic
Normal file
137
tools/ludic-cli/dev.ludic
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# ludic-dev — the contributor tool for the Ludic toolchain itself: building the
|
||||
# compiler from its IR seed, the regression suites, the docs site, releases.
|
||||
#
|
||||
# It is a separate binary from `ludic` on purpose. Those two have different
|
||||
# audiences and nothing in common but the compile step: a person who installed
|
||||
# the language cannot bootstrap a compiler they do not have the source of, and
|
||||
# should not have to read past a screen of tasks that only mean something inside
|
||||
# this repository. So `ludic` ships and `ludic-dev` does not — it is built from a
|
||||
# checkout, lives in bin/, and is left out of every release artifact.
|
||||
#
|
||||
# From a clean checkout (the one step Ludic cannot do for itself, since compiling
|
||||
# Ludic needs a compiler):
|
||||
#
|
||||
# mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
||||
# bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
||||
# bin/ludic-dev build
|
||||
#
|
||||
# Thereafter `ludic-dev build` rebuilds the whole toolchain — bin/ludic, this
|
||||
# tool, the compiler, the editor tools. Run it from the repository root.
|
||||
program LudicDev {
|
||||
import "prelude.ludic"
|
||||
import "build.ludic"
|
||||
import "toolchain.ludic"
|
||||
import "selfhost.ludic"
|
||||
import "tools.ludic"
|
||||
import "selfhost_test.ludic"
|
||||
import "test.ludic"
|
||||
import "json.ludic"
|
||||
import "lsp_test.ludic"
|
||||
import "forgejo.ludic"
|
||||
import "checks.ludic"
|
||||
import "docgen.ludic"
|
||||
import "docgen_gen.ludic"
|
||||
import "docgen_check.ludic"
|
||||
import "release.ludic"
|
||||
import "pkg.ludic"
|
||||
import "pkg_test.ludic"
|
||||
|
||||
function usage() -> void {
|
||||
print("ludic-dev — tasks for a checkout of the Ludic toolchain (run from the repository root)")
|
||||
print("")
|
||||
print("This is the contributor tool. The language's own command line is `ludic`,")
|
||||
print("which ships to users and carries none of the below.")
|
||||
print("")
|
||||
print("build:")
|
||||
print(" build build the whole toolchain into bin/ (ludicc, ludic, ludic-dev, ludic-fmt, ludic-lsp)")
|
||||
print(" build-cli build just bin/ludicc from the IR seed")
|
||||
print(" tools [--install] [--test] build the editor toolchain (ludic-fmt, ludic-lsp)")
|
||||
print(" clean remove build/")
|
||||
print("")
|
||||
print("test:")
|
||||
print(" test the full regression suite")
|
||||
print(" test --coverage per-file line coverage over the test specs")
|
||||
print(" test-pkg the package-manager suite (hermetic git repos, offline)")
|
||||
print(" selfhost-test the self-hosting suite (correctness + bootstrap fixpoints)")
|
||||
print(" test-tools the editor-toolchain suite")
|
||||
print(" test-lsp drive bin/ludic-lsp over real LSP traffic (exit 2 = no server)")
|
||||
print(" golden regenerate selfhost/golden/renders.sha256 (review with git diff)")
|
||||
print("")
|
||||
print("doc / lint checks (Ludic, no Python):")
|
||||
print(" check-docs every ```ludic doc fence parses (or is marked skip/expect-error)")
|
||||
print(" check-impl every implemented feature has a docs/language page")
|
||||
print(" check-vocabulary the vocabulary is in sync across grammar / lexer / header / parser")
|
||||
print(" lint-asset <file> validate one editor .json / .xml asset")
|
||||
print(" docs-gen [--out DIR] generate the documentation site (default build/pages)")
|
||||
print(" docs-check [DIR] coverage/integrity guard over a generated docs site")
|
||||
print(" docs-palette [--check] regenerate emit_color.ludic + palette.json from the palette table")
|
||||
print("")
|
||||
print("release:")
|
||||
print(" release [major|minor|patch] [--dry-run] [--publish]")
|
||||
print(" cut a release: CHANGELOG + VERSION bump + tag (+ Forgejo release)")
|
||||
print(" publish [vX.Y.Z] publish an already-tagged release; what CI runs on a tag push")
|
||||
print(" changelog-section <ver> print that release's CHANGELOG.md section")
|
||||
print(" changelog-render <ver> <date> <dir>")
|
||||
print(" render a CHANGELOG section from a directory of changesets")
|
||||
print("")
|
||||
print("self-host internals:")
|
||||
print(" selfhost-build [ludicc] [out] assemble + compile the self-host compiler")
|
||||
print(" bootstrap the self-hosting fixpoint proof (seeded from bin/ludicc)")
|
||||
print(" bootstrap-cfree rebuild the compiler from the seed with no C compiler")
|
||||
print(" reseed regenerate selfhost/ludicc.seed.ll after a compiler change")
|
||||
print(" game-build <ludicc> <game.ludic> <out> compile a Ludic game (headless)")
|
||||
print(" sh-compile <ludicc> <in.ludic> <out> compile + link one .ludic file")
|
||||
}
|
||||
|
||||
function dispatch(cmd: pointer) -> int {
|
||||
if (cmd == "build") { return cmd_dev_build() }
|
||||
if (cmd == "build-cli") { return cmd_dev_build_cli() }
|
||||
if (cmd == "tools") { return cmd_tools() }
|
||||
if (cmd == "clean") { return cmd_clean() }
|
||||
if (cmd == "test") {
|
||||
if (argn(2, "") == "--coverage") { return cmd_test_coverage() }
|
||||
return cmd_dev_test()
|
||||
}
|
||||
if (cmd == "selfhost-test") { return cmd_selfhost_test() }
|
||||
if (cmd == "test-tools") { return cmd_test_tools() }
|
||||
if (cmd == "test-lsp") { return cmd_test_lsp() }
|
||||
if (cmd == "test-pkg") { return cmd_test_pkg() }
|
||||
if (cmd == "check-docs") { return cmd_check_docs() }
|
||||
if (cmd == "check-impl") { return cmd_check_impl() }
|
||||
if (cmd == "check-vocabulary") { return cmd_check_vocab() }
|
||||
if (cmd == "lint-asset") { return cmd_lint_asset() }
|
||||
if (cmd == "docs-palette") { return cmd_docs_palette() }
|
||||
if (cmd == "docs-gen") { return cmd_docs_gen() }
|
||||
if (cmd == "docs-check") { return cmd_docs_check() }
|
||||
if (cmd == "golden") { return cmd_golden() }
|
||||
if (cmd == "bootstrap") { return cmd_bootstrap() }
|
||||
if (cmd == "bootstrap-cfree") { return cmd_bootstrap_cfree() }
|
||||
if (cmd == "reseed") { return cmd_reseed() }
|
||||
if (cmd == "selfhost-build") { return cmd_selfhost_build(argn(2, "bin/ludicc"), argn(3, "build/selfhost")) }
|
||||
if (cmd == "sh-compile") {
|
||||
if (arg_count() < 5) { err("usage: ludic-dev sh-compile <ludicc> <in.ludic> <out>\n"); exit(1) }
|
||||
return cmd_sh_compile(arg(2), arg(3), arg(4))
|
||||
}
|
||||
if (cmd == "game-build") {
|
||||
if (arg_count() < 5) { err("usage: ludic-dev game-build <ludicc> <game.ludic> <out>\n"); exit(1) }
|
||||
return cmd_game_build(arg(2), arg(3), arg(4))
|
||||
}
|
||||
if (cmd == "release") { return cmd_release() }
|
||||
if (cmd == "changelog-render") { return cmd_changelog_render() }
|
||||
if (cmd == "changelog-section") { return cmd_changelog_section() }
|
||||
if (cmd == "publish") { return cmd_publish() }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") { usage(); return 0 }
|
||||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
if (arg_count() < 2) { usage(); exit(1) }
|
||||
let cmd = arg(1)
|
||||
let code = dispatch(cmd)
|
||||
tmp_cleanup()
|
||||
if code >= 0 { exit(code) }
|
||||
err(`ludic-dev: unknown task '{cmd}'\n`)
|
||||
usage()
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
# docgen.ludic — the Ludic documentation-site generator and its guards, ported
|
||||
# off Python (gen.py / check.py / palette.py). Three x subcommands:
|
||||
#
|
||||
# ludic dev docs-palette emit selfhost/backend/stdlib/emit_color.ludic +
|
||||
# ludic-dev docs-palette emit selfhost/backend/stdlib/emit_color.ludic +
|
||||
# docs/language/colors/palette.json from the palette
|
||||
# table below (the named-colour source of truth).
|
||||
# ludic dev docs-gen [--out DIR] parse docs/language/** + docs/site/*, read the asset
|
||||
# ludic-dev docs-gen [--out DIR] parse docs/language/** + docs/site/*, read the asset
|
||||
# templates, emit every page + symbols.json + the
|
||||
# highlighter + .nojekyll into DIR (default build/pages).
|
||||
# ludic dev docs-check [DIR] the coverage/integrity guard over a generated site.
|
||||
# ludic-dev docs-check [DIR] the coverage/integrity guard over a generated site.
|
||||
#
|
||||
# String work is over NUL-terminated byte buffers; helpers here mirror gen.py's
|
||||
# html.escape / json.dumps / front-matter parsing closely enough to reproduce the
|
||||
# Python generator's output byte-for-byte (verified against the oracle in ludic dev test).
|
||||
# Python generator's output byte-for-byte (verified against the oracle in ludic-dev test).
|
||||
|
||||
# ---- a growable byte buffer (avoids O(n^2) string building) ------------------
|
||||
property Sb { data: pointer = null, len: int = 0, cap: int = 0 }
|
||||
|
|
@ -87,40 +87,6 @@ function lstrip_nl(s: pointer) -> pointer {
|
|||
while s[i] == '\n' { i += 1 }
|
||||
return sslice(s, i, slen(s))
|
||||
}
|
||||
# lowercase ASCII A-Z
|
||||
function lower_ascii(s: pointer) -> pointer {
|
||||
let n = slen(s)
|
||||
let b = bytes(n + 1)
|
||||
var i = 0
|
||||
while i < n {
|
||||
var c = s[i]
|
||||
if c >= 'A' and c <= 'Z' { c += 32 }
|
||||
b[i] = c
|
||||
i += 1
|
||||
}
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
# Python str.title(): capitalise the first letter of each alpha run, lower the rest
|
||||
function title_case(s: pointer) -> pointer {
|
||||
let n = slen(s)
|
||||
let b = bytes(n + 1)
|
||||
var i = 0
|
||||
var prev_alpha = false
|
||||
while i < n {
|
||||
var c = s[i]
|
||||
let al = (c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z')
|
||||
if al {
|
||||
if prev_alpha { if c >= 'A' and c <= 'Z' { c += 32 } }
|
||||
else { if c >= 'a' and c <= 'z' { c -= 32 } }
|
||||
}
|
||||
b[i] = c
|
||||
prev_alpha = al
|
||||
i += 1
|
||||
}
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
# lexicographic byte compare: <0, 0, >0
|
||||
function str_cmp(a: pointer, b: pointer) -> int {
|
||||
var i = 0
|
||||
|
|
@ -563,7 +529,7 @@ function palette_emit_ludic(path: pointer) -> bool {
|
|||
sb_puts(b, "# no allocation, identical codegen to writing the hex by hand. Unknown names are\n")
|
||||
sb_puts(b, "# a compile error (color_lookup returns -1, which emit_expr reports).\n")
|
||||
sb_puts(b, "#\n")
|
||||
sb_puts(b, "# GENERATED by `ludic dev docs-palette` from the single source-of-truth palette table\n")
|
||||
sb_puts(b, "# GENERATED by `ludic-dev docs-palette` from the single source-of-truth palette table\n")
|
||||
sb_puts(b, "# in tools/ludic-cli/docgen.ludic. Edit the palette there and regenerate; do not hand-edit.\n")
|
||||
sb_puts(b, "# ============================================================================\n")
|
||||
sb_puts(b, "\n")
|
||||
|
|
@ -620,11 +586,11 @@ function palette_emit_json(path: pointer) -> bool {
|
|||
return write_file(path, sb_str(b))
|
||||
}
|
||||
|
||||
# `ludic dev docs-palette` rewrites the two tracked outputs; `ludic dev docs-palette --check`
|
||||
# `ludic-dev docs-palette` rewrites the two tracked outputs; `ludic-dev docs-palette --check`
|
||||
# regenerates into scratch files and compares them to the tracked ones instead,
|
||||
# so the drift guard judges the working tree, not git HEAD.
|
||||
function cmd_docs_palette() -> int {
|
||||
let check = arg_total() > 2 and arg_n(2) == "--check"
|
||||
let check = arg_count() > 2 and arg(2) == "--check"
|
||||
palette_init()
|
||||
# guard: duplicate names
|
||||
let seen = new []pointer
|
||||
|
|
@ -640,8 +606,8 @@ function cmd_docs_palette() -> int {
|
|||
if not palette_emit_ludic(ludic_out) { err("cannot write emit_color.ludic\n"); return 1 }
|
||||
if not palette_emit_json(json_out) { err("cannot write palette.json\n"); return 1 }
|
||||
if check {
|
||||
if not shq(`cmp -s {ludic_out} selfhost/backend/stdlib/emit_color.ludic`) { err("emit_color.ludic drifted from the palette table (run: ludic dev docs-palette)\n"); return 1 }
|
||||
if not shq(`cmp -s {json_out} docs/language/colors/palette.json`) { err("palette.json drifted from the palette table (run: ludic dev docs-palette)\n"); return 1 }
|
||||
if not shq(`cmp -s {ludic_out} selfhost/backend/stdlib/emit_color.ludic`) { err("emit_color.ludic drifted from the palette table (run: ludic-dev docs-palette)\n"); return 1 }
|
||||
if not shq(`cmp -s {json_out} docs/language/colors/palette.json`) { err("palette.json drifted from the palette table (run: ludic-dev docs-palette)\n"); return 1 }
|
||||
}
|
||||
print(`OK {string(len(pal_names))} colors`)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# docgen_check.ludic — `ludic dev docs-check [DIR]`: the coverage / integrity guard over
|
||||
# docgen_check.ludic — `ludic-dev docs-check [DIR]`: the coverage / integrity guard over
|
||||
# a generated docs site, ported from tools/docgen/check.py. Fails (exit 1) if the
|
||||
# pages contract is broken, an inventory symbol lacks a source file or page, a
|
||||
# token is documented on two pages, a namespace/section is split across dirs, or
|
||||
|
|
@ -94,7 +94,7 @@ function drop_md(fn: pointer) -> pointer {
|
|||
|
||||
function cmd_docs_check() -> int {
|
||||
var site = "build/pages"
|
||||
if arg_total() >= 3 { site = arg_n(2) }
|
||||
if arg_count() >= 3 { site = arg(2) }
|
||||
let problems = new []pointer
|
||||
let warnings = new []pointer
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# docgen_gen.ludic — `ludic dev docs-gen`: the documentation-site generator, ported from
|
||||
# docgen_gen.ludic — `ludic-dev docs-gen`: the documentation-site generator, ported from
|
||||
# tools/docgen/gen.py. Parses docs/language/** (front-matter + bodies) and
|
||||
# docs/site/*, reads the asset templates in tools/docgen/assets, and emits the
|
||||
# whole pages-branch payload (per-symbol pages, namespace/color/api pages, the
|
||||
# landing page, ludic-highlight.js, symbols.json, .nojekyll) into --out. Output
|
||||
# is byte-for-byte identical to the Python generator (gated in ludic dev test).
|
||||
# is byte-for-byte identical to the Python generator (gated in ludic-dev test).
|
||||
#
|
||||
# Shared helpers (Sb, hesc/hescattr, the JSON writer, codeify, string utils)
|
||||
# live in docgen.ludic; the JSON reader (json_parse/j_get) in json.ludic.
|
||||
|
|
@ -951,11 +951,11 @@ function render_highlighter(sym: JVal) -> pointer {
|
|||
function cmd_docs_gen() -> int {
|
||||
var out = "build/pages"
|
||||
var i = 2
|
||||
while i < arg_total() {
|
||||
let a = arg_n(i)
|
||||
while i < arg_count() {
|
||||
let a = arg(i)
|
||||
if a == "--out" {
|
||||
i += 1
|
||||
if i < arg_total() { out = arg_n(i) }
|
||||
if i < arg_count() { out = arg(i) }
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# lsp_test.ludic — drive bin/ludic-lsp over real LSP traffic against the real
|
||||
# source tree (`ludic dev test-lsp`; replaces tools/test-lsp.py). Every assertion is
|
||||
# source tree (`ludic-dev test-lsp`; replaces tools/test-lsp.py). Every assertion is
|
||||
# about a feature an editor actually calls: if this passes, VS Code, JetBrains,
|
||||
# Neovim and the rest are exercising code paths that have been checked, because
|
||||
# they all speak to this one binary.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# ludic — the command-line interface to the Ludic toolchain. One native binary
|
||||
# ludic — the command-line interface to the Ludic language. One native binary
|
||||
# (bin/ludic) written in Ludic and compiled by Ludic; the unix tools, clang and
|
||||
# the compiler it drives are invoked through the `run` intrinsic.
|
||||
#
|
||||
# Two audiences, one binary. A user of the language sees a small surface —
|
||||
# new, build, run, test, add, fmt — that works from any directory against an
|
||||
# installed toolchain. A contributor to the toolchain itself gets everything
|
||||
# else under `ludic dev`: bootstrapping the compiler from its IR seed, the
|
||||
# regression suites, the docs site, releases. Splitting them by namespace keeps
|
||||
# `ludic help` about the language rather than about the repository.
|
||||
# This is everything a user of the language does: create a project, build it, run
|
||||
# it, test it, add a package, format, upgrade. Nothing here is about the
|
||||
# toolchain's own repository — building the compiler, reseeding it, generating
|
||||
# the docs site, cutting a release live in a separate tool (dev.ludic ->
|
||||
# bin/ludic-dev) that only exists in a checkout and is never installed. Someone
|
||||
# who installs Ludic should not have to read past commands they cannot use.
|
||||
#
|
||||
# Installing (macOS, Linux):
|
||||
#
|
||||
|
|
@ -17,29 +17,13 @@
|
|||
# compiling Ludic needs a compiler:
|
||||
#
|
||||
# mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
||||
# bin/ludicc tools/ludic-cli/main.ludic -o bin/ludic
|
||||
# bin/ludic dev build
|
||||
#
|
||||
# Thereafter `ludic dev build` rebuilds the whole toolchain — including the CLI
|
||||
# itself. The `dev` tasks run from the repository root.
|
||||
# bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
||||
# bin/ludic-dev build
|
||||
program Ludic {
|
||||
import "prelude.ludic"
|
||||
import "selfhost.ludic"
|
||||
import "build.ludic"
|
||||
import "project.ludic"
|
||||
import "tools.ludic"
|
||||
import "selfhost_test.ludic"
|
||||
import "test.ludic"
|
||||
import "json.ludic"
|
||||
import "lsp_test.ludic"
|
||||
import "forgejo.ludic"
|
||||
import "checks.ludic"
|
||||
import "docgen.ludic"
|
||||
import "docgen_gen.ludic"
|
||||
import "docgen_check.ludic"
|
||||
import "release.ludic"
|
||||
import "pkg.ludic"
|
||||
import "pkg_test.ludic"
|
||||
|
||||
function usage() -> void {
|
||||
print("ludic — the toolchain for the Ludic language")
|
||||
|
|
@ -71,61 +55,9 @@ program Ludic {
|
|||
print(" lsp run the language server on stdio (what editors spawn)")
|
||||
print(" help this message")
|
||||
print("")
|
||||
print("contributing to the toolchain itself:")
|
||||
print(" dev <task> build, test, bootstrap, docs, release (ludic dev help)")
|
||||
print("")
|
||||
print("A file argument runs it: ludic mygame.ludic")
|
||||
}
|
||||
|
||||
function dev_usage() -> void {
|
||||
print("ludic dev — tasks for a checkout of the Ludic toolchain (run from the repository root)")
|
||||
print("")
|
||||
print("build:")
|
||||
print(" dev build build the whole toolchain into bin/ (ludicc, ludic, ludic-fmt, ludic-lsp)")
|
||||
print(" dev build-cli build just bin/ludicc from the IR seed")
|
||||
print(" dev tools [--install] [--test] build the editor toolchain (ludic-fmt, ludic-lsp)")
|
||||
print("")
|
||||
print("test:")
|
||||
print(" dev test the full regression suite")
|
||||
print(" dev test --coverage per-file line coverage over the test specs")
|
||||
print(" dev test-pkg the package-manager suite (hermetic git repos, offline)")
|
||||
print(" dev selfhost-test the self-hosting suite (correctness + bootstrap fixpoints)")
|
||||
print(" dev test-tools the editor-toolchain suite")
|
||||
print(" dev test-lsp drive bin/ludic-lsp over real LSP traffic (exit 2 = no server)")
|
||||
print(" dev golden regenerate selfhost/golden/renders.sha256 (review with git diff)")
|
||||
print("")
|
||||
print("doc / lint checks (Ludic, no Python):")
|
||||
print(" dev check-docs every ```ludic doc fence parses (or is marked skip/expect-error)")
|
||||
print(" dev check-impl every implemented feature has a docs/language page")
|
||||
print(" dev check-vocabulary the vocabulary is in sync across grammar / lexer / header / parser")
|
||||
print(" dev lint-asset <file> validate one editor .json / .xml asset")
|
||||
print(" dev docs-gen [--out DIR] generate the documentation site (default build/pages)")
|
||||
print(" dev docs-check [DIR] coverage/integrity guard over a generated docs site")
|
||||
print(" dev docs-palette [--check] regenerate emit_color.ludic + palette.json from the palette table")
|
||||
print("")
|
||||
print("release:")
|
||||
print(" dev release [major|minor|patch] [--dry-run] [--publish]")
|
||||
print(" cut a release: CHANGELOG + VERSION bump + tag (+ Forgejo release)")
|
||||
print(" dev publish [vX.Y.Z] publish an already-tagged release; what CI runs on a tag push")
|
||||
print(" dev changelog-section <ver> print that release's CHANGELOG.md section")
|
||||
print(" dev changelog-render <ver> <date> <dir>")
|
||||
print(" render a CHANGELOG section from a directory of changesets")
|
||||
print("")
|
||||
print("self-host internals:")
|
||||
print(" dev selfhost-build [ludicc] [out] assemble + compile the self-host compiler")
|
||||
print(" dev bootstrap the self-hosting fixpoint proof (seeded from bin/ludicc)")
|
||||
print(" dev bootstrap-cfree rebuild the compiler from the seed with no C compiler")
|
||||
print(" dev reseed regenerate selfhost/ludicc.seed.ll after a compiler change")
|
||||
print(" dev game-build <ludicc> <game.ludic> <out> compile a Ludic game (headless)")
|
||||
print(" dev sh-compile <ludicc> <in.ludic> <out> compile + link one .ludic file")
|
||||
}
|
||||
|
||||
# a positional argument, or a default when absent
|
||||
function argn(i: int, dflt: pointer) -> pointer {
|
||||
if (i < arg_count()) { return arg(i) }
|
||||
return dflt
|
||||
}
|
||||
|
||||
# does `s` end in ".ludic"? — `ludic mygame.ludic` runs that file, the shape
|
||||
# the compiler-runner had before the CLI grew subcommands.
|
||||
function is_ludic_file(s: pointer) -> bool {
|
||||
|
|
@ -134,55 +66,6 @@ program Ludic {
|
|||
return sslice(s, n - 6, n) == ".ludic"
|
||||
}
|
||||
|
||||
# ---- ludic dev <task> — the toolchain repo's own tasks ---------------------
|
||||
# Every task here reads its arguments one position further along than a
|
||||
# top-level command, so the shift is set once, here.
|
||||
function dispatch_dev(cmd: pointer) -> int {
|
||||
g_shift = 1
|
||||
if (cmd == "build") { return cmd_dev_build() }
|
||||
if (cmd == "build-cli") { return cmd_dev_build_cli() }
|
||||
if (cmd == "tools") { return cmd_tools() }
|
||||
if (cmd == "clean") { return cmd_clean() }
|
||||
if (cmd == "test") {
|
||||
if (argn(3, "") == "--coverage") { return cmd_test_coverage() }
|
||||
return cmd_dev_test()
|
||||
}
|
||||
if (cmd == "selfhost-test") { return cmd_selfhost_test() }
|
||||
if (cmd == "test-tools") { return cmd_test_tools() }
|
||||
if (cmd == "test-lsp") { return cmd_test_lsp() }
|
||||
if (cmd == "test-pkg") { return cmd_test_pkg() }
|
||||
if (cmd == "check-docs") { return cmd_check_docs() }
|
||||
if (cmd == "check-impl") { return cmd_check_impl() }
|
||||
if (cmd == "check-vocabulary") { return cmd_check_vocab() }
|
||||
if (cmd == "lint-asset") { return cmd_lint_asset() }
|
||||
if (cmd == "docs-palette") { return cmd_docs_palette() }
|
||||
if (cmd == "docs-gen") { return cmd_docs_gen() }
|
||||
if (cmd == "docs-check") { return cmd_docs_check() }
|
||||
if (cmd == "golden") { return cmd_golden() }
|
||||
if (cmd == "bootstrap") { return cmd_bootstrap() }
|
||||
if (cmd == "bootstrap-cfree") { return cmd_bootstrap_cfree() }
|
||||
if (cmd == "reseed") { return cmd_reseed() }
|
||||
if (cmd == "selfhost-build") { return cmd_selfhost_build(argn(3, "bin/ludicc"), argn(4, "build/selfhost")) }
|
||||
if (cmd == "sh-compile") {
|
||||
if (arg_count() < 6) { err("usage: ludic dev sh-compile <ludicc> <in.ludic> <out>\n"); exit(1) }
|
||||
return cmd_sh_compile(arg(3), arg(4), arg(5))
|
||||
}
|
||||
if (cmd == "game-build") {
|
||||
if (arg_count() < 6) { err("usage: ludic dev game-build <ludicc> <game.ludic> <out>\n"); exit(1) }
|
||||
return cmd_game_build(arg(3), arg(4), arg(5))
|
||||
}
|
||||
if (cmd == "release") { return cmd_release() }
|
||||
if (cmd == "changelog-render") { return cmd_changelog_render() }
|
||||
if (cmd == "changelog-section") { return cmd_changelog_section() }
|
||||
if (cmd == "publish") { return cmd_publish() }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") or (cmd == "") { dev_usage(); return 0 }
|
||||
# reported here rather than by entry, so the message names the dev task the
|
||||
# user actually typed and the usage shown is the dev one
|
||||
err(`ludic dev: unknown task '{cmd}'\n`)
|
||||
dev_usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
# route one subcommand to its implementation and return its exit code; -1 when
|
||||
# the name is unknown. Returning (rather than exiting in place) lets entry tear
|
||||
# down the per-process scratch directory before the process ends.
|
||||
|
|
@ -206,13 +89,23 @@ program Ludic {
|
|||
if (cmd == "link-flags") { return cmd_pkg_link_flags() }
|
||||
|
||||
if (cmd == "version") or (cmd == "--version") or (cmd == "-v") { return cmd_version() }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") { usage(); return 0 }
|
||||
if (cmd == "dev") { return dispatch_dev(argn(2, "")) }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") { usage(); return 0 }
|
||||
|
||||
# The toolchain's own tasks used to live here under `ludic dev`. Say where
|
||||
# they went rather than printing "unknown command" at someone who read an
|
||||
# older README or a commit message.
|
||||
if (cmd == "dev") {
|
||||
err("ludic: the toolchain's own tasks live in `ludic-dev`, which is built from a\n")
|
||||
err(" checkout of the Ludic repository and is not part of an install:\n")
|
||||
err(" clang selfhost/ludicc.seed.ll -o bin/ludicc\n")
|
||||
err(" bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev && bin/ludic-dev help\n")
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
if (arg_count() < 2) { usage(); exit(1) }
|
||||
if (arg_count() < 2) { usage(); exit(1) }
|
||||
let cmd = arg(1)
|
||||
# `ludic mygame.ludic` — build and run that file
|
||||
if is_ludic_file(cmd) {
|
||||
|
|
|
|||
|
|
@ -539,8 +539,8 @@ function set_require(module: pointer, ver: pointer) -> void {
|
|||
|
||||
# ludic add <module>[@version] — add/update a dependency then install
|
||||
function cmd_pkg_add() -> int {
|
||||
if arg_total() < 3 { err("usage: ludic add <module>[@version]\n"); return 1 }
|
||||
let spec = split_spec(arg_n(2))
|
||||
if arg_count() < 3 { err("usage: ludic add <module>[@version]\n"); return 1 }
|
||||
let spec = split_spec(arg(2))
|
||||
let module = spec[0]
|
||||
var ver = spec[1]
|
||||
if slen(ver) == 0 {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
# prelude.ludic — the shared runtime for `ludic`, the command-line interface.
|
||||
# prelude.ludic — the shared runtime for both command-line programs: `ludic`,
|
||||
# which a user of the language runs, and `ludic-dev`, the contributor tool that
|
||||
# builds the toolchain itself. Both are native binaries written in Ludic and
|
||||
# compiled by Ludic, driving clang, the compiler and the unix tools through
|
||||
# `run`. This fragment is the tiny standard library their commands lean on:
|
||||
# process control, file IO, strings and a colored PASS/FAIL test harness. It
|
||||
# carries no ECS, so both link as plain CLI programs.
|
||||
#
|
||||
# `ludic` is the one tool a user of the language ever runs: it creates projects,
|
||||
# compiles and runs them, resolves packages, formats, tests, and (under
|
||||
# `ludic dev`) drives every build/bootstrap/release task of the toolchain repo
|
||||
# itself. It is a single native binary written in Ludic and compiled by Ludic,
|
||||
# driving clang, the compiler and the unix tools through `run`. This fragment is
|
||||
# the tiny standard library the commands lean on: process control, file IO,
|
||||
# string trimming and a colored PASS/FAIL test harness. It carries no ECS, so it
|
||||
# links as a plain CLI program.
|
||||
#
|
||||
# The user-facing commands work from any directory. The `ludic dev` tasks run
|
||||
# relative to the current directory and expect the toolchain repo root.
|
||||
# `ludic`'s commands work from any directory, against whatever toolchain is
|
||||
# installed. `ludic-dev`'s tasks run relative to the current directory and expect
|
||||
# the toolchain repo root.
|
||||
|
||||
# ---- file IO ----------------------------------------------------------------
|
||||
|
||||
|
|
@ -53,7 +51,7 @@ function shq(cmd: pointer) -> bool { return exit_code(run(cmd)) == 0 }
|
|||
|
||||
# ---- scratch files ------------------------------------------------------------
|
||||
# Every scratch file the runner writes lives under one per-process directory
|
||||
# (`$TMPDIR/x_<pid>`), so `ludic dev test` and an `x check-*` can run side by side without
|
||||
# (`$TMPDIR/x_<pid>`), so `ludic-dev test` and an `x check-*` can run side by side without
|
||||
# clobbering each other's captures. main removes it on the way out.
|
||||
var x_tmp: pointer = null
|
||||
|
||||
|
|
@ -175,17 +173,11 @@ function report() -> int {
|
|||
return 1
|
||||
}
|
||||
|
||||
# ---- command arguments -------------------------------------------------------
|
||||
#
|
||||
# A command reads its own arguments as arg_n(1), arg_n(2)… whether it was
|
||||
# reached as `ludic get` or as `ludic dev docs-check DIR`. main sets the shift
|
||||
# once per dispatch, so no command has to know how deep its namespace is —
|
||||
# getting that wrong is how `docs-check DIR` silently checked the wrong
|
||||
# directory.
|
||||
var g_shift: int = 0
|
||||
|
||||
function arg_n(i: int) -> pointer { return arg(i + g_shift) }
|
||||
function arg_total() -> int { return arg_count() - g_shift }
|
||||
# a positional argument, or a default when it is absent
|
||||
function argn(i: int, dflt: pointer) -> pointer {
|
||||
if (i < arg_count()) { return arg(i) }
|
||||
return dflt
|
||||
}
|
||||
|
||||
# ---- the toolchain install ---------------------------------------------------
|
||||
#
|
||||
|
|
@ -249,8 +241,121 @@ function tool(name: pointer) -> pointer {
|
|||
|
||||
function ludicc() -> pointer { return tool("ludicc") }
|
||||
|
||||
# true in a checkout of the toolchain repo itself, where the `ludic dev` tasks
|
||||
# have something to work on.
|
||||
# true in a checkout of the toolchain repo itself, where a seed is present to
|
||||
# assemble the compiler from.
|
||||
function in_toolchain_repo() -> bool {
|
||||
return file_exists("selfhost/ludicc.seed.ll") and file_exists("tools/ludic-cli/main.ludic")
|
||||
}
|
||||
|
||||
# ---- strings ----------------------------------------------------------------
|
||||
# Shared by every command, so they live here rather than in whichever file
|
||||
# happened to need them first.
|
||||
|
||||
# length of a NUL-terminated buffer
|
||||
function slen(s: pointer) -> int { var n = 0; while s[n] != 0 { n += 1 }; return n }
|
||||
|
||||
# a fresh NUL-terminated copy of s[start .. end) (end exclusive)
|
||||
function sslice(s: pointer, start: int, end: int) -> pointer {
|
||||
if end < start { return "" }
|
||||
let n = end - start
|
||||
let b = bytes(n + 1)
|
||||
var i = 0
|
||||
while i < n { b[i] = s[start + i]; i += 1 }
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
|
||||
# index of the first byte of `needle` in `hay` at or after `from`, else -1
|
||||
function s_index(hay: pointer, needle: pointer, from: int) -> int {
|
||||
let hn = slen(hay)
|
||||
let nn = slen(needle)
|
||||
if nn == 0 { return from }
|
||||
var i = from
|
||||
while i + nn <= hn {
|
||||
var j = 0
|
||||
while j < nn and hay[i + j] == needle[j] { j += 1 }
|
||||
if j == nn { return i }
|
||||
i += 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function s_contains(hay: pointer, needle: pointer) -> bool { return s_index(hay, needle, 0) >= 0 }
|
||||
|
||||
# does `hay` contain `needle` exactly at position `at`?
|
||||
function s_starts_at(hay: pointer, at: int, needle: pointer) -> bool {
|
||||
let nn = slen(needle)
|
||||
var i = 0
|
||||
while i < nn { if hay[at + i] != needle[i] { return false }; i += 1 }
|
||||
return true
|
||||
}
|
||||
|
||||
# does `s` (a whole line) begin with `pre`?
|
||||
function s_starts(s: pointer, pre: pointer) -> bool {
|
||||
let pn = slen(pre)
|
||||
var i = 0
|
||||
while i < pn { if s[i] != pre[i] { return false }; i += 1 }
|
||||
return true
|
||||
}
|
||||
|
||||
# is byte c an ASCII space/tab?
|
||||
function is_ws(c: int) -> bool { return c == ' ' or c == '\t' }
|
||||
|
||||
# the substring from `start` up to the next '\n' (or end)
|
||||
function line_at(s: pointer, start: int) -> pointer {
|
||||
var e = start
|
||||
while s[e] != 0 and s[e] != '\n' { e += 1 }
|
||||
return sslice(s, start, e)
|
||||
}
|
||||
|
||||
# trim leading/trailing ASCII whitespace (space, tab, cr, nl)
|
||||
function s_trim(s: pointer) -> pointer {
|
||||
let n = slen(s)
|
||||
var a = 0
|
||||
while a < n and (is_ws(s[a]) or s[a] == '\n' or s[a] == '\r') { a += 1 }
|
||||
var b = n
|
||||
while b > a and (is_ws(s[b - 1]) or s[b - 1] == '\n' or s[b - 1] == '\r') { b -= 1 }
|
||||
return sslice(s, a, b)
|
||||
}
|
||||
|
||||
# lowercase ASCII A-Z
|
||||
function lower_ascii(s: pointer) -> pointer {
|
||||
let n = slen(s)
|
||||
let b = bytes(n + 1)
|
||||
var i = 0
|
||||
while i < n {
|
||||
var c = s[i]
|
||||
if c >= 'A' and c <= 'Z' { c += 32 }
|
||||
b[i] = c
|
||||
i += 1
|
||||
}
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
|
||||
# Python str.title(): capitalise the first letter of each alpha run, lower the rest
|
||||
function title_case(s: pointer) -> pointer {
|
||||
let n = slen(s)
|
||||
let b = bytes(n + 1)
|
||||
var i = 0
|
||||
var prev_alpha = false
|
||||
while i < n {
|
||||
var c = s[i]
|
||||
let al = (c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z')
|
||||
if al {
|
||||
if prev_alpha { if c >= 'A' and c <= 'Z' { c += 32 } }
|
||||
else { if c >= 'a' and c <= 'z' { c -= 32 } }
|
||||
}
|
||||
b[i] = c
|
||||
prev_alpha = al
|
||||
i += 1
|
||||
}
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
|
||||
# ---- the C toolchain --------------------------------------------------------
|
||||
# clang assembles the emitted IR and drives the linker. -Wno-override-module for
|
||||
# the same reason the compiler itself passes it (see selfhost/main.ludic): the IR
|
||||
# names no target triple, so clang substitutes the host's and warns every time.
|
||||
function cc() -> pointer { return getenv_or("LUDIC_CC", "clang") + " -Wno-override-module" }
|
||||
|
|
|
|||
|
|
@ -131,11 +131,11 @@ function template_readme(name: pointer) -> pointer {
|
|||
|
||||
# ludic new <name> — scaffold a project that builds and runs as it stands.
|
||||
function cmd_new() -> int {
|
||||
if arg_total() < 3 {
|
||||
if arg_count() < 3 {
|
||||
err("usage: ludic new <name>\n")
|
||||
return 1
|
||||
}
|
||||
let name = arg_n(2)
|
||||
let name = arg(2)
|
||||
if file_exists(name) {
|
||||
err(`ludic new: {name} already exists\n`)
|
||||
return 1
|
||||
|
|
@ -173,12 +173,12 @@ function parse_build_args(start: int) -> pointer {
|
|||
g_out = ""
|
||||
g_save = false
|
||||
var ai = start
|
||||
while ai < arg_total() {
|
||||
let a = arg_n(ai)
|
||||
while ai < arg_count() {
|
||||
let a = arg(ai)
|
||||
if a == "--headless" { g_mode = 2 }
|
||||
else if a == "--windowed" { g_mode = 1 }
|
||||
else if a == "--save-temps" { g_save = true }
|
||||
else if a == "-o" { ai += 1; if ai < arg_total() { g_out = arg_n(ai) } }
|
||||
else if a == "-o" { ai += 1; if ai < arg_count() { g_out = arg(ai) } }
|
||||
else if a[0] != '-' { src = a }
|
||||
ai += 1
|
||||
}
|
||||
|
|
@ -245,7 +245,7 @@ function test_files() -> []pointer {
|
|||
function cmd_test() -> int {
|
||||
var files = new []pointer
|
||||
var ai = 2
|
||||
while ai < arg_total() { if arg_n(ai)[0] != '-' { push(files, arg_n(ai)) }; ai += 1 }
|
||||
while ai < arg_count() { if arg(ai)[0] != '-' { push(files, arg(ai)) }; ai += 1 }
|
||||
if len(files) == 0 { files = test_files() }
|
||||
if len(files) == 0 {
|
||||
err("ludic test: no tests found (expected tests/*.ludic or src/**/*_test.ludic)\n")
|
||||
|
|
@ -298,7 +298,7 @@ function strip_ext(p: pointer) -> pointer {
|
|||
function cmd_fmt() -> int {
|
||||
var args = ""
|
||||
var ai = 2
|
||||
while ai < arg_total() { args = `{args} {arg_n(ai)}`; ai += 1 }
|
||||
while ai < arg_count() { args = `{args} {arg(ai)}`; ai += 1 }
|
||||
if args == "" {
|
||||
let found = capture_line("find src tests -name '*.ludic' 2>/dev/null | sort")
|
||||
if found == "" {
|
||||
|
|
@ -315,7 +315,7 @@ function cmd_fmt() -> int {
|
|||
function cmd_lsp() -> int {
|
||||
var args = ""
|
||||
var ai = 2
|
||||
while ai < arg_total() { args = `{args} {arg_n(ai)}`; ai += 1 }
|
||||
while ai < arg_count() { args = `{args} {arg(ai)}`; ai += 1 }
|
||||
return sh(`exec {tool("ludic-lsp")}{args}`)
|
||||
}
|
||||
|
||||
|
|
@ -377,6 +377,25 @@ function cmd_doctor() -> int {
|
|||
return 1
|
||||
}
|
||||
|
||||
# ludic version — report the version of the toolchain this CLI belongs to.
|
||||
#
|
||||
# It asks the compiler (proving that path works), addressed through ludic_home()
|
||||
# rather than as a relative bin/ludicc: `ludic version` is run from a user's
|
||||
# project, not from the toolchain's own directory, and looking beside the current
|
||||
# directory found nothing there and reported "(version unknown)" for a perfectly
|
||||
# good install.
|
||||
function cmd_version() -> int {
|
||||
let cc = ludicc()
|
||||
if is_exec(cc) {
|
||||
run(`{cc} --version`)
|
||||
return 0
|
||||
}
|
||||
let v = read_file(`{ludic_home()}VERSION`)
|
||||
if v != null { print(`ludic {s_trim(v)}`); return 0 }
|
||||
print("ludic (version unknown)")
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- ludic upgrade ----------------------------------------------------------
|
||||
|
||||
function install_url() -> pointer { return getenv_or("LUDIC_INSTALL_URL", "https://workshopsoft.pages.workshopsoft.io/ludic/install.sh") }
|
||||
|
|
@ -386,7 +405,7 @@ function install_url() -> pointer { return getenv_or("LUDIC_INSTALL_URL", "https
|
|||
# drift from a fresh install.
|
||||
function cmd_upgrade() -> int {
|
||||
var ver = ""
|
||||
if arg_total() >= 3 { ver = arg_n(2) }
|
||||
if arg_count() >= 3 { ver = arg(2) }
|
||||
if not shq("command -v curl >/dev/null 2>&1") {
|
||||
err("ludic upgrade: needs curl\n")
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# release.ludic — versioning + release cutting for the toolchain.
|
||||
#
|
||||
# ludic version print the toolchain version (from the VERSION file)
|
||||
# ludic dev release [level] cut a release: aggregate changes/ into CHANGELOG.md,
|
||||
# ludic-dev release [level] cut a release: aggregate changes/ into CHANGELOG.md,
|
||||
# bump VERSION, commit, and tag vX.Y.Z. `level` is
|
||||
# major|minor|patch; omitted, it is derived from the
|
||||
# highest `bump:` among the pending changesets.
|
||||
# ludic dev release [level] --dry-run render the changelog section to stdout and
|
||||
# ludic-dev release [level] --dry-run render the changelog section to stdout and
|
||||
# stop: nothing is written, committed, tagged or pushed.
|
||||
# ludic dev release [level] --publish ...then push main + the tag and create a
|
||||
# ludic-dev release [level] --publish ...then push main + the tag and create a
|
||||
# Forgejo release with source + toolchain tarballs.
|
||||
# Needs FORGEJO_TOKEN in the environment.
|
||||
#
|
||||
|
|
@ -49,24 +49,6 @@ function compute_next(cur: pointer, level: pointer) -> pointer {
|
|||
return `{maj}.{min}.{p2}`
|
||||
}
|
||||
|
||||
# ludic version — report the version of the toolchain this CLI belongs to.
|
||||
#
|
||||
# It asks the compiler (proving that path works), addressed through ludic_home()
|
||||
# rather than as a relative bin/ludicc: `ludic version` is run from a user's
|
||||
# project, not from the toolchain's own directory, and looking beside the current
|
||||
# directory found nothing there and reported "(version unknown)" for a perfectly
|
||||
# good install.
|
||||
function cmd_version() -> int {
|
||||
let cc = ludicc()
|
||||
if is_exec(cc) {
|
||||
run(`{cc} --version`)
|
||||
return 0
|
||||
}
|
||||
let v = read_file(`{ludic_home()}VERSION`)
|
||||
if v != null { print(`ludic {s_trim(v)}`); return 0 }
|
||||
print(`ludic {read_version_or("(version unknown)")}`)
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- changelog rendering ----------------------------------------------------
|
||||
#
|
||||
|
|
@ -241,16 +223,16 @@ function build_section(ver: pointer) -> void {
|
|||
write_file(tmp_path("rel_section.md"), render_section(ver, date, "changes"))
|
||||
}
|
||||
|
||||
# ludic dev changelog-render <version> <date> <dir> — print the CHANGELOG section that
|
||||
# ludic-dev changelog-render <version> <date> <dir> — print the CHANGELOG section that
|
||||
# `dir`'s changesets would produce. Used to re-render the sections of releases
|
||||
# cut before the renderer preserved markdown structure: check the changesets out
|
||||
# of the tag's parent commit, point this at them, and splice the result back in.
|
||||
function cmd_changelog_render() -> int {
|
||||
if arg_total() < 5 {
|
||||
err("usage: ludic dev changelog-render <version> <date> <changesets-dir>\n")
|
||||
if arg_count() < 5 {
|
||||
err("usage: ludic-dev changelog-render <version> <date> <changesets-dir>\n")
|
||||
return 1
|
||||
}
|
||||
out(render_section(arg_n(2), arg_n(3), arg_n(4)))
|
||||
out(render_section(arg(2), arg(3), arg(4)))
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +240,7 @@ function cmd_changelog_render() -> int {
|
|||
# release section (or at the end of the header if this is the first release).
|
||||
function prepend_changelog() -> void {
|
||||
if not file_exists("CHANGELOG.md") {
|
||||
run("printf '# Changelog\n\nAll notable changes to the Ludic toolchain, newest first. Generated from the\nchangesets under changes/ by ludic dev release; do not edit released sections by hand.\n\n' > CHANGELOG.md")
|
||||
run("printf '# Changelog\n\nAll notable changes to the Ludic toolchain, newest first. Generated from the\nchangesets under changes/ by ludic-dev release; do not edit released sections by hand.\n\n' > CHANGELOG.md")
|
||||
}
|
||||
let ln = capture_line("grep -n '^## ' CHANGELOG.md | head -1 | cut -d: -f1")
|
||||
if (ln == "") {
|
||||
|
|
@ -297,11 +279,11 @@ function changelog_section(ver: pointer) -> pointer {
|
|||
return sslice(text, start, n)
|
||||
}
|
||||
|
||||
# ludic dev changelog-section <version> — print that release's CHANGELOG section.
|
||||
# ludic-dev changelog-section <version> — print that release's CHANGELOG section.
|
||||
function cmd_changelog_section() -> int {
|
||||
if arg_total() < 3 { err("usage: ludic dev changelog-section <version>\n"); return 1 }
|
||||
let sec = changelog_section(arg_n(2))
|
||||
if slen(sec) == 0 { err(`changelog-section: no section for v{arg_n(2)} in CHANGELOG.md\n`); return 1 }
|
||||
if arg_count() < 3 { err("usage: ludic-dev changelog-section <version>\n"); return 1 }
|
||||
let sec = changelog_section(arg(2))
|
||||
if slen(sec) == 0 { err(`changelog-section: no section for v{arg(2)} in CHANGELOG.md\n`); return 1 }
|
||||
out(sec)
|
||||
return 0
|
||||
}
|
||||
|
|
@ -343,7 +325,7 @@ function build_artifacts(ver: pointer) -> bool {
|
|||
let stage = `{tmp_dir()}/{name}`
|
||||
run(`rm -rf {stage} && mkdir -p {stage}/bin`)
|
||||
if not shq(`cp bin/ludic bin/ludicc bin/ludic-fmt bin/ludic-lsp {stage}/bin/`) {
|
||||
err("release: the toolchain is not built (run: ludic dev build)\n")
|
||||
err("release: the toolchain is not built (run: ludic-dev build)\n")
|
||||
return false
|
||||
}
|
||||
run(`cp -R runtime {stage}/runtime`)
|
||||
|
|
@ -366,10 +348,10 @@ function build_artifacts(ver: pointer) -> bool {
|
|||
return true
|
||||
}
|
||||
|
||||
# ludic dev publish [vX.Y.Z] — publish an already-tagged release: build the artifacts,
|
||||
# ludic-dev publish [vX.Y.Z] — publish an already-tagged release: build the artifacts,
|
||||
# take the notes from that version's CHANGELOG section, and create the Forgejo
|
||||
# release. Defaults to the version in VERSION. This is what CI runs on a tag
|
||||
# push, and what `ludic dev release --publish` calls once it has tagged.
|
||||
# push, and what `ludic-dev release --publish` calls once it has tagged.
|
||||
function publish_tag(ver: pointer) -> int {
|
||||
if getenv_or("FORGEJO_TOKEN", "") == "" { err("publish: set FORGEJO_TOKEN (a Forgejo access token)\n"); return 1 }
|
||||
let body = tmp_path("rel_notes.md")
|
||||
|
|
@ -386,8 +368,8 @@ function publish_tag(ver: pointer) -> int {
|
|||
|
||||
function cmd_publish() -> int {
|
||||
var ver = read_version_or("")
|
||||
if arg_total() >= 3 {
|
||||
var a = arg_n(2)
|
||||
if arg_count() >= 3 {
|
||||
var a = arg(2)
|
||||
if s_starts(a, "v") { a = sslice(a, 1, slen(a)) }
|
||||
ver = a
|
||||
}
|
||||
|
|
@ -409,8 +391,8 @@ function cmd_release() -> int {
|
|||
var publish = false
|
||||
var dry = false
|
||||
var ai = 2
|
||||
while ai < arg_total() {
|
||||
let a = arg_n(ai)
|
||||
while ai < arg_count() {
|
||||
let a = arg(ai)
|
||||
if (a == "--publish") { publish = true }
|
||||
else { if (a == "--dry-run") { dry = true }
|
||||
else { if (a == "major") or (a == "minor") or (a == "patch") { level = a }
|
||||
|
|
@ -437,7 +419,7 @@ function cmd_release() -> int {
|
|||
let sec = read_file(tmp_path("rel_section.md"))
|
||||
if sec != null { out(sec) }
|
||||
print("")
|
||||
print(` dry run — nothing written. cut it with: ludic dev release {level}`)
|
||||
print(` dry run — nothing written. cut it with: ludic-dev release {level}`)
|
||||
return 0
|
||||
}
|
||||
prepend_changelog()
|
||||
|
|
@ -452,7 +434,7 @@ function cmd_release() -> int {
|
|||
print(` committed + tagged v{ver}`)
|
||||
|
||||
if publish { return publish_release(ver) }
|
||||
print(` local release ready. publish with: FORGEJO_TOKEN=… ludic dev release {level} --publish`)
|
||||
print(` local release ready. publish with: FORGEJO_TOKEN=… ludic-dev release {level} --publish`)
|
||||
print(` (or push: git push origin HEAD && git push origin v{ver})`)
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@
|
|||
# now lives here once, in selfhost_frags().
|
||||
|
||||
# the C toolchain driver (clang) that assembles and links the emitted IR
|
||||
# The C toolchain used to assemble and link IR. -Wno-override-module for the
|
||||
# same reason the compiler itself passes it (see selfhost/main.ludic): the IR
|
||||
# names no target triple, so clang substitutes the host's and warns every time.
|
||||
function cc() -> pointer { return getenv_or("LUDIC_CC", "clang") + " -Wno-override-module" }
|
||||
|
||||
# the self-host compiler's source fragments, in link order. This is THE list;
|
||||
# the old scripts each carried their own copy.
|
||||
|
|
@ -95,21 +91,9 @@ function line_count(path: pointer) -> pointer {
|
|||
return capture_line(`wc -l < {path}`)
|
||||
}
|
||||
|
||||
# ensure bin/ludicc exists and is current with the seed. Built from the checked-in
|
||||
# IR seed with clang alone — no C compiler is ever involved.
|
||||
function ensure_ludicc() -> void {
|
||||
if not in_toolchain_repo() { return }
|
||||
run("mkdir -p bin build")
|
||||
if (not is_exec("bin/ludicc")) or newer("selfhost/ludicc.seed.ll", "bin/ludicc") {
|
||||
print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) {
|
||||
err("ludic: could not assemble the seed\n"); exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---- selfhost-build: assemble + compile the self-host compiler ---------------
|
||||
# usage: ludic dev selfhost-build [ludicc] [outbin] (defaults: bin/ludicc, build/selfhost)
|
||||
# usage: ludic-dev selfhost-build [ludicc] [outbin] (defaults: bin/ludicc, build/selfhost)
|
||||
function cmd_selfhost_build(lc: pointer, outbin: pointer) -> int {
|
||||
run("mkdir -p build")
|
||||
let src = "build/selfhost.ludic"
|
||||
|
|
@ -123,7 +107,7 @@ function cmd_selfhost_build(lc: pointer, outbin: pointer) -> int {
|
|||
}
|
||||
|
||||
# ---- sh-compile: compile one .ludic with a given selfhost binary and link ----
|
||||
# usage: ludic dev sh-compile <selfhost-binary> <input.ludic> <output-binary>
|
||||
# usage: ludic-dev sh-compile <selfhost-binary> <input.ludic> <output-binary>
|
||||
function cmd_sh_compile(shbin: pointer, in: pointer, outbin: pointer) -> int {
|
||||
if not shq(`{shbin} {in} > {outbin}.ll`) { return 1 }
|
||||
if not shq(`{cc()} {outbin}.ll -o {outbin}`) { return 1 }
|
||||
|
|
@ -144,7 +128,7 @@ function game_build_ok(shbin: pointer, game: pointer, outbin: pointer) -> bool {
|
|||
return true
|
||||
}
|
||||
|
||||
# usage: ludic dev game-build <selfhost-binary> <game.ludic> <out-binary>
|
||||
# usage: ludic-dev game-build <selfhost-binary> <game.ludic> <out-binary>
|
||||
function cmd_game_build(shbin: pointer, game: pointer, outbin: pointer) -> int {
|
||||
if game_build_ok(shbin, game, outbin) { print(`built {outbin}`); return 0 }
|
||||
print("build failed:")
|
||||
|
|
@ -194,7 +178,7 @@ function cmd_bootstrap_cfree() -> int {
|
|||
print(" out.ll == seed.ll — the compiler rebuilds itself with no C compiler")
|
||||
return 0
|
||||
}
|
||||
print(" out.ll != seed.ll (seed is stale — regenerate with: ludic dev reseed)")
|
||||
print(" out.ll != seed.ll (seed is stale — regenerate with: ludic-dev reseed)")
|
||||
out(capture("diff build/cfree/out.ll selfhost/ludicc.seed.ll | head"))
|
||||
return 1
|
||||
}
|
||||
|
|
@ -203,7 +187,7 @@ function cmd_bootstrap_cfree() -> int {
|
|||
# Uses the CURRENT seed to build the new one, compiling twice so the seed is a
|
||||
# fixed point of the NEW compiler, not a one-step image of the old. Falls back to
|
||||
# bin/ludicc only if the seed itself no longer assembles. Verify with
|
||||
# `ludic dev bootstrap-cfree` afterwards.
|
||||
# `ludic-dev bootstrap-cfree` afterwards.
|
||||
function cmd_reseed() -> int {
|
||||
run("mkdir -p build/cfree")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function sh_case(name: pointer, exp: pointer) -> void {
|
|||
# framebuffer's SHA-256 against the text baseline in selfhost/golden/renders.sha256.
|
||||
# A hash is tiny and text-diffable — a regression shows as a changed hex line in a
|
||||
# review, not an opaque "binary files differ". Regenerate the baseline with
|
||||
# `bin/ludic dev golden` (see cmd_golden below).
|
||||
# `bin/ludic-dev golden` (see cmd_golden below).
|
||||
function game_case(path: pointer, keys: pointer) -> void {
|
||||
let nm = flat(path)
|
||||
if not game_build_ok("bin/ludicc", `examples/{path}.ludic`, `{tmp_dir()}/g_{nm}`) { bad(`{path}: build`); return }
|
||||
|
|
@ -32,13 +32,13 @@ function game_case(path: pointer, keys: pointer) -> void {
|
|||
if not is_darwin() { skip(`{path} render (golden hash blessed on macOS)`); return }
|
||||
let got = capture_line("shasum -a 256 build/out.ppm | cut -d' ' -f1")
|
||||
let want = capture_line(`grep '^{path} ' selfhost/golden/renders.sha256 | cut -d' ' -f2`)
|
||||
if (want == "") { bad2(path, "no golden hash (run: bin/ludic dev golden)"); return }
|
||||
if (want == "") { bad2(path, "no golden hash (run: bin/ludic-dev golden)"); return }
|
||||
if (got == want) { ok(`{path} matches the golden render`) }
|
||||
else { bad2(path, `render hash {got} != golden {want}`) }
|
||||
}
|
||||
|
||||
# render one golden game and return its "<path> <sha256>\n" manifest line (empty
|
||||
# string on build failure). Shared by `bin/ludic dev golden` and nothing else.
|
||||
# string on build failure). Shared by `bin/ludic-dev golden` and nothing else.
|
||||
function bless_render(path: pointer, keys: pointer) -> pointer {
|
||||
let nm = flat(path)
|
||||
if not game_build_ok("bin/ludicc", `examples/{path}.ludic`, `{tmp_dir()}/bg_{nm}`) {
|
||||
|
|
@ -117,12 +117,12 @@ function cmd_selfhost_test() -> int {
|
|||
game_case("rendering/draw_prims", "")
|
||||
|
||||
print("== the bootstrap fixpoint (seeded from bin/ludicc) ==")
|
||||
if shq(`bin/ludic dev bootstrap > {tmp_dir()}/boot.out 2>&1`) { ok("gen2.ll == gen3.ll (compiler reproduces itself)") }
|
||||
if shq(`bin/ludic-dev bootstrap > {tmp_dir()}/boot.out 2>&1`) { ok("gen2.ll == gen3.ll (compiler reproduces itself)") }
|
||||
else { bad("fixpoint not reached"); out(capture(`cat {tmp_dir()}/boot.out`)) }
|
||||
|
||||
print("== the C-free bootstrap (from the checked-in IR seed, no C compiler) ==")
|
||||
if shq(`bin/ludic dev bootstrap-cfree > {tmp_dir()}/cfree.out 2>&1`) { ok("seed.ll rebuilds the compiler and is its own fixed point") }
|
||||
else { bad("C-free bootstrap failed (seed stale? run: ludic dev reseed)"); out(capture(`cat {tmp_dir()}/cfree.out`)) }
|
||||
if shq(`bin/ludic-dev bootstrap-cfree > {tmp_dir()}/cfree.out 2>&1`) { ok("seed.ll rebuilds the compiler and is its own fixed point") }
|
||||
else { bad("C-free bootstrap failed (seed stale? run: ludic-dev reseed)"); out(capture(`cat {tmp_dir()}/cfree.out`)) }
|
||||
|
||||
return report()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,16 @@ function install_layout_case() -> void {
|
|||
if ver != `ludic {s_trim(read_file("VERSION"))}` {
|
||||
bad2("an installed `ludic version` reports the install's VERSION", `got [{ver}]`); return
|
||||
}
|
||||
# and the shipped CLI carries none of the contributor tasks: `ludic dev …` is
|
||||
# not a command there, it is a pointer at the tool that has them.
|
||||
let devlog = `{work}/dev.out`
|
||||
let devrc = sh(`cd {work}/demo && {envp} ludic dev build > {devlog} 2>&1`)
|
||||
var devout = read_file(devlog)
|
||||
if devout == null { devout = "" }
|
||||
if devrc == 0 or not s_contains(devout, "ludic-dev") {
|
||||
bad2("an installed `ludic` must not carry the toolchain's own tasks", `rc {string(devrc)}, said [{s_trim(devout)}]`)
|
||||
return
|
||||
}
|
||||
ok(lbl)
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +171,7 @@ function panic_case() -> void {
|
|||
else { bad2("panic", `rc={string(rc)} err=[{msg}]`) }
|
||||
}
|
||||
|
||||
# issue #45: `bin/ludic dev test --coverage`. Compile each test-spec with `--coverage`,
|
||||
# issue #45: `bin/ludic-dev test --coverage`. Compile each test-spec with `--coverage`,
|
||||
# run it with LUDIC_COVERAGE pointed at a per-file dump, then aggregate the dumps
|
||||
# into a clean per-file line-coverage report. The instrumentation is flag-gated,
|
||||
# so this reuses the same seed-built bin/ludicc the rest of the suite does.
|
||||
|
|
@ -211,7 +221,7 @@ function cmd_test_coverage() -> int {
|
|||
}
|
||||
COV_COVERED = 0
|
||||
COV_TOTAL = 0
|
||||
print("== line coverage (bin/ludic dev test --coverage) ==")
|
||||
print("== line coverage (bin/ludic-dev test --coverage) ==")
|
||||
cov_one("library/coverage", `{tmp_dir()}/cov_out`)
|
||||
cov_one("library/testing", `{tmp_dir()}/cov_out`)
|
||||
var pct = 100
|
||||
|
|
@ -244,11 +254,11 @@ function cmd_dev_test() -> int {
|
|||
game_case("games/chronorift", "ddddwwwwaassK")
|
||||
|
||||
print("== self-hosting and the bootstrap fixpoints ==")
|
||||
run(`bin/ludic dev selfhost-test > {tmp_dir()}/sh.out 2>&1`)
|
||||
run(`bin/ludic-dev selfhost-test > {tmp_dir()}/sh.out 2>&1`)
|
||||
if shq(`grep -q 'gen2.ll == gen3.ll' {tmp_dir()}/sh.out`) { ok("the compiler reproduces itself (fixpoint)") } else { bad("fixpoint") }
|
||||
if shq(`grep -q 'no C compiler' {tmp_dir()}/sh.out`) { ok("the compiler rebuilds itself from the seed, C-free") } else { bad("C-free bootstrap") }
|
||||
let corr = capture_line(`grep -c PASS {tmp_dir()}/sh.out`)
|
||||
ok(`self-host suite: {corr} checks passed (see: ludic dev selfhost-test)`)
|
||||
ok(`self-host suite: {corr} checks passed (see: ludic-dev selfhost-test)`)
|
||||
|
||||
print("== documented syntax stays compilable (guards against spec/compiler drift) ==")
|
||||
qsmoke("lang/qdecl")
|
||||
|
|
@ -405,9 +415,9 @@ function cmd_dev_test() -> int {
|
|||
print("== the compiler and the CLI (ludicc / ludic) ==")
|
||||
# ludicc comes out of the IR seed with clang alone; the CLI is then compiled
|
||||
# by it, from Ludic.
|
||||
if shq(`bin/ludic dev build-cli > {tmp_dir()}/cli.out 2>&1`) and is_exec("bin/ludicc") {
|
||||
ok("ludic dev build-cli builds bin/ludicc from the seed (clang-only)")
|
||||
} else { bad2("ludic dev build-cli", capture_line(`tail -1 {tmp_dir()}/cli.out`)) }
|
||||
if shq(`bin/ludic-dev build-cli > {tmp_dir()}/cli.out 2>&1`) and is_exec("bin/ludicc") {
|
||||
ok("ludic-dev build-cli builds bin/ludicc from the seed (clang-only)")
|
||||
} else { bad2("ludic-dev build-cli", capture_line(`tail -1 {tmp_dir()}/cli.out`)) }
|
||||
|
||||
# ludicc -o drives clang to a native binary. A game links windowed by default
|
||||
# (LUDIC_HOME points the linker at runtime/native/cocoa.ll) — that path is
|
||||
|
|
@ -456,27 +466,27 @@ function cmd_dev_test() -> int {
|
|||
# store + ludic_modules/ links + build, run as its own hermetic suite (it
|
||||
# stands up throwaway git repos, so like selfhost-test it runs as a sub-process
|
||||
# and this stage checks its footer).
|
||||
print("== package manager (ludic dev test-pkg) ==")
|
||||
run(`bin/ludic dev test-pkg > {tmp_dir()}/pkg.out 2>&1`)
|
||||
print("== package manager (ludic-dev test-pkg) ==")
|
||||
run(`bin/ludic-dev test-pkg > {tmp_dir()}/pkg.out 2>&1`)
|
||||
if shq(`grep -q '0 failed' {tmp_dir()}/pkg.out`) {
|
||||
let pc = capture_line(`grep -c PASS {tmp_dir()}/pkg.out`)
|
||||
ok(`package-manager suite: {pc} checks passed (see: ludic dev test-pkg)`)
|
||||
} else { bad2("ludic dev test-pkg", capture_line(`grep -i fail {tmp_dir()}/pkg.out | head -1`)) }
|
||||
ok(`package-manager suite: {pc} checks passed (see: ludic-dev test-pkg)`)
|
||||
} else { bad2("ludic-dev test-pkg", capture_line(`grep -i fail {tmp_dir()}/pkg.out | head -1`)) }
|
||||
|
||||
# docs site generator + guard, in Ludic (no Python). docs-gen emits the whole
|
||||
# pages payload; docs-check is its coverage/integrity guard; docs-palette is
|
||||
# the named-colour source of truth (its --check mode regenerates the tracked
|
||||
# emit_color.ludic + palette.json into scratch files and compares).
|
||||
print("== docs site (Ludic generator, no Python) ==")
|
||||
if shq(`bin/ludic dev docs-gen --out {tmp_dir()}/docs > {tmp_dir()}/docs.out 2>&1`) {
|
||||
ok("ludic dev docs-gen -> the pages-branch payload")
|
||||
} else { bad2("ludic dev docs-gen", capture_line(`tail -1 {tmp_dir()}/docs.out`)) }
|
||||
if shq(`bin/ludic dev docs-check {tmp_dir()}/docs > {tmp_dir()}/docs_chk.out 2>&1`) {
|
||||
ok("ludic dev docs-check passes on the generated site")
|
||||
} else { bad2("ludic dev docs-check", capture_line(`tail -1 {tmp_dir()}/docs_chk.out`)) }
|
||||
if shq(`bin/ludic dev docs-palette --check > {tmp_dir()}/pal.out 2>&1`) {
|
||||
ok("ludic dev docs-palette regenerates emit_color.ludic + palette.json byte-identically")
|
||||
} else { bad2("ludic dev docs-palette --check", capture_line(`tail -1 {tmp_dir()}/pal.out`)) }
|
||||
if shq(`bin/ludic-dev docs-gen --out {tmp_dir()}/docs > {tmp_dir()}/docs.out 2>&1`) {
|
||||
ok("ludic-dev docs-gen -> the pages-branch payload")
|
||||
} else { bad2("ludic-dev docs-gen", capture_line(`tail -1 {tmp_dir()}/docs.out`)) }
|
||||
if shq(`bin/ludic-dev docs-check {tmp_dir()}/docs > {tmp_dir()}/docs_chk.out 2>&1`) {
|
||||
ok("ludic-dev docs-check passes on the generated site")
|
||||
} else { bad2("ludic-dev docs-check", capture_line(`tail -1 {tmp_dir()}/docs_chk.out`)) }
|
||||
if shq(`bin/ludic-dev docs-palette --check > {tmp_dir()}/pal.out 2>&1`) {
|
||||
ok("ludic-dev docs-palette regenerates emit_color.ludic + palette.json byte-identically")
|
||||
} else { bad2("ludic-dev docs-palette --check", capture_line(`tail -1 {tmp_dir()}/pal.out`)) }
|
||||
|
||||
return report()
|
||||
}
|
||||
|
|
|
|||
44
tools/ludic-cli/toolchain.ludic
Normal file
44
tools/ludic-cli/toolchain.ludic
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# toolchain.ludic — building the toolchain's own binaries. Contributor-only:
|
||||
# this is what `ludic-dev build` does, and none of it ships to a user.
|
||||
# compile a Ludic source to a native binary in bin/ via ludicc + clang (-O2).
|
||||
# Returns true on success. Used for the CLI, the contributor tool and the editor
|
||||
# tools — every binary the toolchain builds of itself.
|
||||
function build_tool(name: pointer, src: pointer) -> bool {
|
||||
let ll = `build/{name}.ll`
|
||||
if not shq(`bin/ludicc {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
||||
if not shq(`{cc()} -O2 {ll} -o bin/{name}.tmp`) { print(`build failed: {name} (link)`); return false }
|
||||
run(`mv -f bin/{name}.tmp bin/{name} && rm -f {ll}`)
|
||||
return true
|
||||
}
|
||||
|
||||
# ---- build-cli: the self-hosted front-end binaries ---------------------------
|
||||
# Both ludicc and ludic are the SAME multi-call binary assembled from the IR seed
|
||||
# with clang alone; invoked as `ludic` it compiles-and-runs, as `ludicc` it just
|
||||
# compiles.
|
||||
function cmd_dev_build_cli() -> int {
|
||||
run("mkdir -p bin build")
|
||||
print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) { err("ludic: the seed did not assemble\n"); return 1 }
|
||||
run("chmod +x bin/ludicc")
|
||||
print("done. built bin/ludicc")
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- build: the whole toolchain --------------------------------------------
|
||||
# bin/ludic is what a user installs; bin/ludic-dev is this tool, which exists
|
||||
# only in a checkout and is never part of a release.
|
||||
function cmd_dev_build() -> int {
|
||||
if cmd_dev_build_cli() != 0 { return 1 }
|
||||
print("ludicc: tools/ludic-cli/main.ludic -> bin/ludic (the CLI users install)")
|
||||
if not build_tool("ludic", "tools/ludic-cli/main.ludic") { return 1 }
|
||||
print("ludicc: tools/ludic-cli/dev.ludic -> bin/ludic-dev (this tool, rebuilding itself)")
|
||||
if not build_tool("ludic-dev", "tools/ludic-cli/dev.ludic") { return 1 }
|
||||
print("ludicc: tools/ludic-tools/fmt.ludic -> bin/ludic-fmt")
|
||||
if not build_tool("ludic-fmt", "tools/ludic-tools/fmt.ludic") { return 1 }
|
||||
print("ludicc: tools/ludic-tools/lsp.ludic -> bin/ludic-lsp")
|
||||
if not build_tool("ludic-lsp", "tools/ludic-tools/lsp.ludic") { return 1 }
|
||||
sync_vscode_grammar()
|
||||
print("done. toolchain in bin/ (ludicc, ludic, ludic-dev, ludic-fmt, ludic-lsp)")
|
||||
return 0
|
||||
}
|
||||
|
|
@ -27,12 +27,12 @@ function sync_vscode_grammar() -> void {
|
|||
# is `flag` present anywhere in argv?
|
||||
function has_flag(flag: pointer) -> bool {
|
||||
var i = 2
|
||||
while i < arg_total() { if (arg_n(i) == flag) { return true }; i += 1 }
|
||||
while i < arg_count() { if (arg(i) == flag) { return true }; i += 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# ---- tools: build the editor toolchain --------------------------------------
|
||||
# usage: ludic dev tools [--install] [--test]
|
||||
# usage: ludic-dev tools [--install] [--test]
|
||||
function cmd_tools() -> int {
|
||||
ensure_ludicc()
|
||||
print("ludicc: tools/ludic-tools/fmt.ludic -> bin/ludic-fmt (Ludic -> LLVM IR -> binary, no C)")
|
||||
|
|
@ -57,8 +57,8 @@ function cmd_test_tools() -> int {
|
|||
PASS = 0
|
||||
FAIL = 0
|
||||
let fmt = "bin/ludic-fmt"
|
||||
if not is_exec(fmt) { err("bin/ludic-fmt missing — run: ludic dev tools\n"); return 2 }
|
||||
if not is_exec("bin/ludic-lsp") { err("bin/ludic-lsp missing — run: ludic dev tools\n"); return 2 }
|
||||
if not is_exec(fmt) { err("bin/ludic-fmt missing — run: ludic-dev tools\n"); return 2 }
|
||||
if not is_exec("bin/ludic-lsp") { err("bin/ludic-lsp missing — run: ludic-dev tools\n"); return 2 }
|
||||
|
||||
print("formatter")
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ function cmd_test_tools() -> int {
|
|||
# features (go-to-def, find-usages, workspace symbols) can't resolve. Gate the
|
||||
# end-to-end protocol test on Darwin until the directory walk is portable.
|
||||
if is_darwin() {
|
||||
let lrc = sh("bin/ludic dev test-lsp")
|
||||
let lrc = sh("bin/ludic-dev test-lsp")
|
||||
if lrc == 0 { ok("language server protocol") }
|
||||
else if lrc == 2 { skip("language server protocol (bin/ludic-lsp not built)") }
|
||||
else { bad("language server protocol") }
|
||||
|
|
@ -160,10 +160,10 @@ function cmd_test_tools() -> int {
|
|||
# VS Code carries its own copy of the shared grammar; catch an edit to the copy
|
||||
if shq("cmp -s tools/editors/shared/ludic.tmLanguage.json tools/editors/vscode/syntaxes/ludic.tmLanguage.json") and shq("cmp -s tools/editors/shared/ludic.markdown-injection.json tools/editors/vscode/syntaxes/ludic.markdown-injection.json") and shq("cmp -s tools/editors/shared/language-configuration.json tools/editors/vscode/language-configuration.json") {
|
||||
ok("vscode grammar copies match tools/editors/shared")
|
||||
} else { bad2("vscode grammar copies have drifted", "run: ludic dev tools to resync") }
|
||||
} else { bad2("vscode grammar copies have drifted", "run: ludic-dev tools to resync") }
|
||||
|
||||
# the JetBrains plugin is off by default (its first build pulls a gigabyte of
|
||||
# IntelliJ SDK). One env var away: LUDIC_TEST_JETBRAINS=1 ludic dev test-tools
|
||||
# IntelliJ SDK). One env var away: LUDIC_TEST_JETBRAINS=1 ludic-dev test-tools
|
||||
if (getenv_or("LUDIC_TEST_JETBRAINS", "0") == "1") {
|
||||
if shq("test -x tools/editors/jetbrains/gradlew") {
|
||||
if shq("cd tools/editors/jetbrains && ./gradlew buildPlugin verifyPluginStructure --console=plain -q >/dev/null 2>&1 && test -f build/distributions/Ludic-1.0.0.zip") { ok("jetbrains plugin builds and verifies") } else { bad("jetbrains plugin build") }
|
||||
|
|
@ -177,17 +177,17 @@ function cmd_test_tools() -> int {
|
|||
# TextMate grammar, the Kotlin lexer) is the failure mode this layout prevents.
|
||||
# The check is written in Ludic (tools/ludic-cli/checks.ludic) and runs through x — no
|
||||
# Python in the loop.
|
||||
if shq("bin/ludic dev check-vocabulary 2>/dev/null") { ok("vocabulary in sync across grammar/lexer/header") } else { bad("vocabulary drifted") }
|
||||
if shq("bin/ludic-dev check-vocabulary 2>/dev/null") { ok("vocabulary in sync across grammar/lexer/header") } else { bad("vocabulary drifted") }
|
||||
|
||||
# every feature the compiler actually implements — namespace methods, keywords,
|
||||
# types, phases — must have a docs/language page. This reads the implementation
|
||||
# (emit_ns_call + ludic_syntax.h), so shipping a feature without docs fails here.
|
||||
# Ported to Ludic; runs through x.
|
||||
if shq("bin/ludic dev check-impl > /dev/null 2>&1") { ok("docs cover every implemented feature") } else { bad("docs drifted from the implementation") }
|
||||
if shq("bin/ludic-dev check-impl > /dev/null 2>&1") { ok("docs cover every implemented feature") } else { bad("docs drifted from the implementation") }
|
||||
|
||||
# every ```ludic fence in the docs must parse (or be marked skip/expect-error),
|
||||
# so a documented snippet can't drift from what the compiler accepts.
|
||||
if shq("bin/ludic dev check-docs > /dev/null 2>&1") { ok("every doc fence parses") } else { bad2("a doc fence drifted", "run: ludic dev check-docs") }
|
||||
if shq("bin/ludic-dev check-docs > /dev/null 2>&1") { ok("every doc fence parses") } else { bad2("a doc fence drifted", "run: ludic-dev check-docs") }
|
||||
|
||||
return report()
|
||||
}
|
||||
|
|
@ -195,9 +195,9 @@ function cmd_test_tools() -> int {
|
|||
# JSON/XML validity, checked by the Ludic validators in checks.ludic (no Python).
|
||||
function test_json(path: pointer) -> void {
|
||||
let bn = capture_line(`basename {path}`)
|
||||
if shq(`bin/ludic dev lint-asset {path}`) { ok(`valid JSON: {bn}`) } else { bad(`invalid JSON: {path}`) }
|
||||
if shq(`bin/ludic-dev lint-asset {path}`) { ok(`valid JSON: {bn}`) } else { bad(`invalid JSON: {path}`) }
|
||||
}
|
||||
function test_xml(path: pointer) -> void {
|
||||
let bn = capture_line(`basename {path}`)
|
||||
if shq(`bin/ludic dev lint-asset {path}`) { ok(`valid XML: {bn}`) } else { bad(`invalid XML: {path}`) }
|
||||
if shq(`bin/ludic-dev lint-asset {path}`) { ok(`valid XML: {bn}`) } else { bad(`invalid XML: {path}`) }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue