# 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. # # 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): # # curl -fsSL https://workshopsoft.pages.workshopsoft.io/ludic/install.sh | sh # # Building from a 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 program Ludic { import "prelude.ludic" import "build.ludic" import "split.ludic" import "project.ludic" import "pkg.ludic" import "assets.ludic" import "packignore.ludic" import "pack.ludic" import "bundle.ludic" import "scripts.ludic" import "deps.ludic" import "schema.ludic" import "ui_preview.ludic" import "testpar.ludic" import "migrate.ludic" function usage() -> void { print("ludic — the toolchain for the Ludic language") print("") print("usage: ludic [arguments]") print("") print("start a game:") print(" new create a project that builds and runs as it stands") print(" run [file] [--headless] build the project and run it") print(" build [file] [--headless] [-o out]") print(" compile to build/ (a native binary, nothing to ship beside it)") print(" build [file] --check only check it: types, modules, uses and ports; nothing emitted") print(" build [file] --check --diagnostics=json every error as a JSON array on stdout") print(" test [file|dir...] [-j N] [-v] [--test NAME]") print(" compile and run the project's tests (-v: every test's line)") print(" deps [file] [--graph|--dot|--writes|--uses MOD|--check F|--baseline F]") print(" the module graph as the compiler sees it, and how tangled it is") print(" schema [file] [-o FILE] records, registries and their entries, consts, as JSON (for editors)") print(" migrate state [file] [--prune] [--runtime] module-level vars into states, passed as parameters (0.S); --prune drops states nothing uses, --tighten also mut nothing writes") print(" clean remove build/") print("") print("packages:") print(" add [@version] add a dependency, then resolve + fetch + link") print(" get resolve every package.ludic dependency and write the lock") print(" update [module] bump a dependency (or all) to its latest published version") print(" verify check every locked package against the store by content hash") print(" vendor copy the resolved packages into ./vendor for offline builds") print(" assets [--force] fetch the CC0 materials the renderer needs into assets/polyhaven/") print(" build-lib compile a package's module to a prebuilt dylib in lib//") print(" link-flags print the clang flags to link this project's prebuilt module dylibs") print("") print("shipping:") print(" pack [--out FILE] [dirs...] pack the assets into build/.lpak (honouring .packignore)") print(" pack --no-ignore ... pack everything, ignoring .packignore") print(" pack --list FILE print what is in a pack") print(" pack --verify FILE re-hash every entry against the table") print(" bundle [file] build a macOS .app: icon, metadata, assets, splash") print("") print("the toolchain:") print(" version print the toolchain version") print(" upgrade [version] reinstall from the docs site (the same script that installed it)") print(" doctor check that the install is complete and usable") print(" fmt [--check] [paths...] format Ludic source in place (--check: report, write nothing)") print(" lsp run the language server on stdio (what editors spawn)") print(" ui-preview [--font DIR] preview a game's ludic.ui components for a studio, on stdio") print(" help this message") print("") print("package scripts (package.ludic):") print(" script [args] run a `script \"name\" \"command\"` line (or just: ludic )") print(" scripts list the package's scripts and hooks") print(" `hook before|after \"…\"` wraps build, run, test, bundle, …") print("") print("A file argument runs it: ludic mygame.ludic") } # 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 { let n = slen(s) if n <= 6 { return false } return sslice(s, n - 6, n) == ".ludic" } # 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. function dispatch(cmd: pointer) -> int { if (cmd == "new") { return cmd_new() } if (cmd == "build") { return cmd_build() } if (cmd == "run") { return cmd_run() } if (cmd == "test") { return cmd_test() } if (cmd == "deps") { return cmd_deps() } if (cmd == "schema") { return cmd_schema() } if (cmd == "migrate") { return cmd_migrate() } if (cmd == "clean") { return cmd_clean() } if (cmd == "fmt") { return cmd_fmt() } if (cmd == "lsp") { return cmd_lsp() } if (cmd == "ui-preview") { return cmd_ui_preview() } if (cmd == "doctor") { return cmd_doctor() } if (cmd == "upgrade") { return cmd_upgrade() } if (cmd == "script") { return cmd_script() } if (cmd == "scripts") { return cmd_scripts() } if (cmd == "add") { return cmd_pkg_add() } if (cmd == "get") { return cmd_pkg_get() } if (cmd == "update") { return cmd_pkg_update() } if (cmd == "verify") { return cmd_pkg_verify() } if (cmd == "vendor") { return cmd_pkg_vendor() } if (cmd == "assets") { return cmd_fetch_assets() } if (cmd == "pack") { return cmd_pack() } if (cmd == "bundle") { return cmd_bundle() } if (cmd == "build-lib") { return cmd_pkg_build_lib() } 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 } # 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) } let cmd = arg(1) # `ludic mygame.ludic` — build and run that file if is_ludic_file(cmd) { let rc = cmd_run_file() tmp_cleanup() exit(rc) } # package.ludic hooks wrap the built-in commands: `before` can veto, # `after` runs only when the command succeeded if is_hookable(cmd) { let pre = run_hooks("before", cmd) if pre != 0 { tmp_cleanup(); exit(pre) } } var code = dispatch(cmd) if code == 0 and is_hookable(cmd) { code = run_hooks("after", cmd) } # an unknown command may be one of the package's scripts if code == -1 { code = run_script(cmd, 2) } tmp_cleanup() if code >= 0 { exit(code) } err(`ludic: unknown command '{cmd}'\n`) usage() exit(1) } }