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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue