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
122
selfhost/frontend/stdin_file.ludic
Normal file
122
selfhost/frontend/stdin_file.ludic
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# stdin_file.ludic — `--stdin-file <path>`: an unsaved buffer checked in place of its file. The text
|
||||
# on stdin is read once, whole, and read_file (io.ludic) hands a copy of it back wherever the program
|
||||
# opens <path> - an import, a barrel, a component's .xml / .lss, an .lres - so diagnostics follow
|
||||
# typing without a save. Two paths are the same file when they are after normalise_path: separators
|
||||
# made '/', a relative path put under $PWD, `.` / `..` / `//` folded (and case folded on Windows).
|
||||
var g_sf_path: pointer = null # <path> as given: what a warning about it names
|
||||
var g_sf_norm: pointer = null # ... normalised, what each open is compared against
|
||||
var g_sf_base: pointer = null # ... its last part, the cheap test before normalising an open
|
||||
var g_sf_text: pointer = null # stdin, NUL-terminated
|
||||
var g_sf_len: int = 0
|
||||
var g_sf_served: bool = false # the program opened <path> at least once
|
||||
|
||||
# the flag: remember the path and take stdin now, once (an empty stdin is an empty file)
|
||||
function stdin_file_set(path: pointer) -> void {
|
||||
g_sf_path = path
|
||||
g_sf_norm = normalise_path(path)
|
||||
g_sf_base = base_name(g_sf_norm)
|
||||
var cap = 4096
|
||||
var buf = bytes(cap + 1)
|
||||
var n = 0
|
||||
var c = read_char()
|
||||
while c >= 0 {
|
||||
if n == cap { cap *= 2; buf = resize(buf, cap + 1) }
|
||||
buf[n] = c
|
||||
n += 1
|
||||
c = read_char()
|
||||
}
|
||||
buf[n] = 0
|
||||
g_sf_text = buf
|
||||
g_sf_len = n
|
||||
}
|
||||
|
||||
# read_file's first question: the buffer's text (a copy, as a read of the file would be) when
|
||||
# `path` is <path>, else null
|
||||
function stdin_file_serve(path: pointer) -> pointer {
|
||||
if g_sf_text == null { return null }
|
||||
if not sf_tail_is(path, g_sf_base) { return null }
|
||||
if normalise_path(path) != g_sf_norm { return null }
|
||||
let out = bytes(g_sf_len + 1)
|
||||
var i = 0
|
||||
while i < g_sf_len { out[i] = g_sf_text[i]; i += 1 }
|
||||
out[g_sf_len] = 0
|
||||
g_sf_served = true
|
||||
return out
|
||||
}
|
||||
|
||||
# a buffer for a file the program never opened was checked against nothing: say so rather than
|
||||
# report the saved files clean
|
||||
function stdin_file_unreached() -> void {
|
||||
if g_sf_text == null or g_sf_served { return }
|
||||
pwarn_at(g_sf_path, 1, 0, `--stdin-file {g_sf_path}: the program does not reach this file, so the buffer was not checked`)
|
||||
}
|
||||
|
||||
# does `path`'s last part equal `base`? (no allocation: every read_file asks)
|
||||
function sf_tail_is(path: pointer, base: pointer) -> bool {
|
||||
var n = 0
|
||||
while path[n] != 0 { n += 1 }
|
||||
let m = len(base)
|
||||
if n < m { return false }
|
||||
if n > m and not is_sep(path[n - m - 1]) { return false }
|
||||
var i = 0
|
||||
while i < m {
|
||||
if sf_fold(path[n - m + i]) != sf_fold(base[i]) { return false }
|
||||
i += 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
function sf_fold(c: int) -> int {
|
||||
if c >= 'A' and c <= 'Z' and host_is_windows() { return c + 32 }
|
||||
return c
|
||||
}
|
||||
|
||||
# one spelling per file: '/' separators, absolute (a relative path is under $PWD, the directory the
|
||||
# compiler was started in), no `.`, `..` or empty parts; lower case on Windows
|
||||
function normalise_path(p: pointer) -> pointer {
|
||||
var s = fwd_slashes(p)
|
||||
let abs = (len(s) > 0 and s[0] == '/') or (len(s) > 1 and s[1] == ':')
|
||||
if not abs {
|
||||
let cwd = getenv("PWD")
|
||||
if cwd != null and len(cwd) > 0 { s = fwd_slashes(cwd) + "/" + s }
|
||||
}
|
||||
let parts = new []pointer # the first np are the path so far
|
||||
var np = 0
|
||||
var start = 0
|
||||
var i = 0
|
||||
let n = len(s)
|
||||
while i <= n {
|
||||
if i == n or s[i] == '/' {
|
||||
let part = s[start..i]
|
||||
if part == ".." and np > 0 and parts[np - 1] != ".." { np -= 1 }
|
||||
else if part != "" and part != "." {
|
||||
if np < len(parts) { parts[np] = part } else { push(parts, part) }
|
||||
np += 1
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
var out = ""
|
||||
if len(s) > 0 and s[0] == '/' { out = "/" }
|
||||
i = 0
|
||||
while i < np {
|
||||
if i > 0 { out = out + "/" }
|
||||
out = out + parts[i]
|
||||
i += 1
|
||||
}
|
||||
if host_is_windows() { out = sf_lower(out) }
|
||||
return out
|
||||
}
|
||||
function sf_lower(s: pointer) -> pointer {
|
||||
let n = len(s)
|
||||
let out = bytes(n + 1)
|
||||
var i = 0
|
||||
while i < n {
|
||||
var c = s[i]
|
||||
if c >= 'A' and c <= 'Z' { c += 32 }
|
||||
out[i] = c
|
||||
i += 1
|
||||
}
|
||||
out[n] = 0
|
||||
return out
|
||||
}
|
||||
91105
selfhost/ludicc.seed.ll
91105
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -162,6 +162,7 @@ entry {
|
|||
else if a == "--release" { g_release = true } # what ships: Build.schema_hash() is 0 (emit_schema_hash.ludic)
|
||||
else if a == "--diagnostics=json" { g_diag_json = true } # every error, as JSON on stdout (diag.ludic)
|
||||
else if a == "--diagnostics=text" { g_diag_json = false }
|
||||
else if a == "--stdin-file" { ai += 1; if ai < arg_count() { stdin_file_set(arg(ai)) } } # stdin stands for that file (stdin_file.ludic)
|
||||
else if a == "--emit-schema" { ai += 1; if ai < arg_count() { g_schema_out = arg(ai) } } # emit_schema.ludic
|
||||
else if a == "--globals" { g_allow_globals = true }
|
||||
else if a == "--migrate-state" { # 0.S2: write the plan ludic migrate state applies
|
||||
|
|
@ -262,10 +263,12 @@ entry {
|
|||
if g_check_only {
|
||||
pm_check_maps() # every map's tables against their records (permap_check.ludic)
|
||||
emit_program()
|
||||
stdin_file_unreached() # --stdin-file naming a file nothing opened
|
||||
if g_dg_errors > 0 { diag_exit(1) }
|
||||
diag_exit(0)
|
||||
}
|
||||
emit_program()
|
||||
stdin_file_unreached()
|
||||
if g_dg_errors > 0 { diag_exit(1) } # errors a pass went on from (diag.ludic)
|
||||
deps_flush() # LUDIC_DEPS=<file>: the module graph (ludic deps)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
# The compiler reads one .ludic file whole and emits LLVM IR text to stdout.
|
||||
|
||||
function read_file(path: pointer) -> pointer {
|
||||
let unsaved = stdin_file_serve(path) # --stdin-file: the buffer stands for its file (stdin_file.ludic)
|
||||
if unsaved != null { return unsaved }
|
||||
let f = file_open(path, "rb")
|
||||
if (f == null) { return null }
|
||||
file_seek(f, 0, 2)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue