ludicc / ludic build: --stdin-file <path> checks an unsaved buffer in place of its file (R10)
stdin is read once, whole, and read_file serves a copy of it wherever the program opens <path> -
the entry, an import through a barrel, a component's .xml / .lss, an .lres. Paths match after
normalising both ('/' separators, relative under $PWD, . / .. / // folded, case on Windows);
diagnostics keep the file's usual name, with the buffer's lines and columns. A <path> nothing
opens is one warning. On ludic build it implies --check. Reseeded; bootstrap-cfree fixpoint holds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4ccbc12b48
commit
c804c4b0ee
12 changed files with 95310 additions and 93180 deletions
|
|
@ -48,6 +48,7 @@ program Ludic {
|
|||
print(" compile to build/<name> (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(" build [file] --check --stdin-file PATH stdin is PATH's unsaved text (an editor's buffer)")
|
||||
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]")
|
||||
|
|
|
|||
|
|
@ -280,6 +280,11 @@ function parse_build_args(start: int) -> pointer {
|
|||
else if a == "--no-maps" { g_no_maps_build = true } # --check leaves the map directories alone
|
||||
else if a == "--maps" { g_maps_build = true } # ... or reads them, whatever program it checks
|
||||
else if a == "--diagnostics=json" { g_check_build = true; g_diag_json_build = true } # every error, as JSON on stdout
|
||||
else if a == "--stdin-file" { # stdin is that file's unsaved text: checked, never built
|
||||
ai += 1
|
||||
if ai < arg_count() { g_stdin_file_build = arg(ai); g_check_build = true }
|
||||
else { err("ludic: --stdin-file needs a path\n"); g_argerr = true }
|
||||
}
|
||||
else if a == "-o" {
|
||||
ai += 1
|
||||
if ai < arg_count() { g_out = arg(ai) }
|
||||
|
|
@ -287,7 +292,7 @@ function parse_build_args(start: int) -> pointer {
|
|||
}
|
||||
else if a[0] == '-' {
|
||||
err(`ludic: unknown option {a}\n`)
|
||||
err(" build/run take: [file] [--headless|--windowed] [-o out] [--save-temps] [--check [--diagnostics=json] [--maps|--no-maps]]\n")
|
||||
err(" build/run take: [file] [--headless|--windowed] [-o out] [--save-temps] [--check [--diagnostics=json] [--stdin-file path] [--maps|--no-maps]]\n")
|
||||
g_argerr = true
|
||||
}
|
||||
else { src = a }
|
||||
|
|
@ -311,6 +316,7 @@ function output_path(entry: pointer) -> pointer {
|
|||
# and nothing emitted or linked; a fraction of a build's time, for iterating on uses lines
|
||||
var g_check_build: bool = false
|
||||
var g_diag_json_build: bool = false # --diagnostics=json: the compiler's JSON array is the whole stdout
|
||||
var g_stdin_file_build: pointer = "" # --stdin-file <path>: the compiler reads stdin where it would read <path>
|
||||
var g_no_maps_build: bool = false
|
||||
var g_maps_build: bool = false
|
||||
# the maps are checked with the package's entry - the game - and left alone for any other program: a
|
||||
|
|
@ -330,13 +336,17 @@ function bld_same_path(a: pointer, b: pointer) -> bool {
|
|||
if len(y) > 2 and y[0] == '.' and y[1] == '/' { y = y[2..len(y)] }
|
||||
return x == y
|
||||
}
|
||||
function stdin_file_flag() -> pointer {
|
||||
if g_stdin_file_build == "" { return "" }
|
||||
return ` --stdin-file {sh_single(g_stdin_file_build)}`
|
||||
}
|
||||
function check_app(src: pointer) -> int {
|
||||
ensure_ludicc()
|
||||
if g_diag_json_build {
|
||||
if not shq(`{ludicc()} --check --diagnostics=json{no_maps_flag(src)}{title_flag()}{unsafe_flag()} {src}`) { return 1 }
|
||||
if not shq(`{ludicc()} --check --diagnostics=json{no_maps_flag(src)}{title_flag()}{unsafe_flag()}{stdin_file_flag()} {src}`) { return 1 }
|
||||
return 0
|
||||
}
|
||||
if not shq(`{ludicc()} --check{no_maps_flag(src)}{title_flag()}{unsafe_flag()} {src}`) { return 1 }
|
||||
if not shq(`{ludicc()} --check{no_maps_flag(src)}{title_flag()}{unsafe_flag()}{stdin_file_flag()} {src}`) { return 1 }
|
||||
print(`checked {src}`)
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/frontend/ast.ludic")
|
||||
push(f, "selfhost/frontend/lex.ludic")
|
||||
push(f, "selfhost/frontend/diag.ludic")
|
||||
push(f, "selfhost/frontend/stdin_file.ludic")
|
||||
push(f, "selfhost/frontend/parse.ludic")
|
||||
push(f, "selfhost/frontend/parse_game.ludic")
|
||||
push(f, "selfhost/frontend/generics.ludic")
|
||||
|
|
|
|||
|
|
@ -99,6 +99,30 @@ function diag_json_case(path: pointer, n: int, want: pointer, label: pointer) ->
|
|||
if s_contains(capture(`cat {js}`), want) { ok(label) } else { bad2(label, `no diagnostic holds [{want}]`) }
|
||||
}
|
||||
|
||||
# --stdin-file <path>: stdin stands for <path> wherever the program opens it. A buffer that breaks an
|
||||
# import reached through its barrel (spelt with `..`) is refused at the buffer's line under the file's
|
||||
# usual name; a buffer that fixes a refused entry checks clean; a path nothing opens is a warning.
|
||||
function stdin_file_cases() -> void {
|
||||
let lbl = "--stdin-file: an unsaved buffer checked in place of its file"
|
||||
let fish = "examples/modules/kinds/fishing/index.ludic"
|
||||
let js = `{tmp_dir()}/sf_break.json`
|
||||
let spelt = "./examples/modules/kinds/fishing/../fishing/index.ludic"
|
||||
if shq(`sed 's/return fishing_st.landed/return "seven"/' {fish} | bin/ludicc examples/modules/private_kinds.ludic --check --diagnostics=json --stdin-file {spelt} > {js} 2>/dev/null`) {
|
||||
bad2(lbl, "a buffer with an error checked clean"); return
|
||||
}
|
||||
let broke = capture(`cat {js}`)
|
||||
if not s_contains(broke, `"file": "{fish}", "line": 21`) { bad2(lbl, `no error at the buffer's line 21 of {fish}: {s_trim(broke)}`); return }
|
||||
let js2 = `{tmp_dir()}/sf_fix.json`
|
||||
let fix = "sed -e 's/print(on + 1)/print(1)/' -e 's/half(1, 2)/half(1)/' -e 's/-> int { return \"seven\" }/-> int { return 7 }/'"
|
||||
let clean = shq(`{fix} examples/rejected/every_error.ludic | bin/ludicc examples/rejected/every_error.ludic --check --diagnostics=json --stdin-file examples/rejected/every_error.ludic > {js2} 2>/dev/null`)
|
||||
let said = s_trim(capture(`cat {js2}`))
|
||||
if not clean or not (said == "[]") { bad2(lbl, `a buffer that fixes the saved file's errors said {said}`); return }
|
||||
let js3 = `{tmp_dir()}/sf_none.json`
|
||||
shq(`printf '' | bin/ludicc examples/modules/private_kinds.ludic --check --diagnostics=json --stdin-file examples/nowhere.ludic > {js3} 2>/dev/null`)
|
||||
if not s_contains(capture(`cat {js3}`), "the program does not reach this file") { bad2(lbl, "a path the program never opens was not reported"); return }
|
||||
ok(lbl)
|
||||
}
|
||||
|
||||
# a feature example that consumes an in-repo controller package (packages/): same
|
||||
# as feat_case, but the compiler is pointed at packages/ via LUDIC_MODULES so a
|
||||
# `import "ludic.foo/bar.ludic"` resolves, and LUDIC_HOME anchors the engine
|
||||
|
|
@ -1227,6 +1251,7 @@ function cmd_dev_test() -> int {
|
|||
diag_json_case("rejected/every_error", 3, "\"line\": 8, \"col\": 15, \"severity\": \"error\", \"message\": \"half takes 1 argument(s) and this call gives 2\"", "--diagnostics=json: every type error as {file, line, col, severity, message}")
|
||||
diag_json_case("rejected/ref_unknown", 1, "@Ref(Shops): this program has no Shops - left unresolved", "--diagnostics=json: an @Ref to a registry the program lacks is a warning beside the error")
|
||||
diag_json_case("rejected/node_bad", 8, "crate.shape is \\\"cube\\\", and field Tool.shape is @OneOf(", "--diagnostics=json: every @Node / @Clip / @Material with no model field, @OneOf of the wrong kind or a row outside its words, and the targets the program lacks as warnings")
|
||||
stdin_file_cases()
|
||||
feat_case("lang/generics", "", "7 Crater Lake a 2 west 7 0.75 2.5 1", "generics.ludic (L5: generic records and functions, nested instances, fn-typed parameters and fields, inference from the result's slot)")
|
||||
reject_case("rejected/generic_unbound", "cannot tell what T is in this call to pool_new", "a call whose type arguments nothing decides is refused")
|
||||
reject_case("rejected/generic_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue