Phase 7h: string slicing s[a..b] (retire substr)

`s[a..b]` is a fresh substring of the bytes [a, b) — the modern, end-based form
of the C-style `substr(s, start, count)`:

  substr(src, start, i - start)   -> src[start..i]
  substr(t, 2, len(t) - 2)        -> t[2..len(t)]
  substr(src, i, 2)               -> src[i..i + 2]

Mechanics: a new E_SLICE postfix (`base[lo..hi]`, distinct from `base[i]`
indexing) lowers to a @fn_str_slice prelude (malloc + copy + terminate), emitted
once into any program that slices. Two reseeds: add the syntax + prelude, then
migrate the 22 substr calls and delete substr. The migrator recognises the
common `count == end - start` shape and emits the clean `s[start..end]` rather
than `s[start..start + (end - start)]`.

examples/strings.ludic gains slicing (now prints 1..9). Reseeded (22673 lines);
C-free fixpoint holds; goldens identical; 18/18; vocab + doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-28 01:35:24 +03:00
parent 0efa06dca2
commit 1b9789630a
14 changed files with 4049 additions and 3914 deletions

View file

@ -57,7 +57,7 @@ fn args_call(call: Node) -> void {
# ---- string interpolation --------------------------------------------------
# `text {expr} text` desugars to a `+` chain of string literals and `str(expr)`
# holes, so it reuses the string-concat operator and needs no new runtime.
fn interp_lit(buf: ptr, len: int) -> Node { let n = node(E_STR); n.s = substr(buf, 0, len); return n }
fn interp_lit(buf: ptr, len: int) -> Node { let n = node(E_STR); n.s = buf[0..0 + len]; return n }
fn interp_add(acc: Node, part: Node) -> Node {
if acc == null { return part }
return mkbin("+", acc, part)
@ -92,7 +92,7 @@ fn parse_interp(raw: ptr) -> Node {
else { if d == 125 { depth = depth - 1; if depth == 0 { break } } }
i = i + 1
}
acc = interp_add(acc, interp_str(parse_hole(substr(raw, hs, i - hs))))
acc = interp_add(acc, interp_str(parse_hole(raw[hs..i])))
i = i + 1 # skip the closing '}'
} else {
if c == 125 and peek8(raw, i + 1) == 125 { poke8(lit, lj, 125); lj = lj + 1; i = i + 2; continue } # }} -> }
@ -131,7 +131,9 @@ fn p_postfix() -> Node {
var e = p_primary()
while true {
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m }
else { if is_op("[") { pi = pi + 1; let ix = node(E_INDEX); ix.a = e; ix.b = expr(); eat_op("]"); e = ix }
else { if is_op("[") { pi = pi + 1; let lo = expr()
if is_op("..") { pi = pi + 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
else { let ix = node(E_INDEX); ix.a = e; ix.b = lo; eat_op("]"); e = ix } }
else { if is_op("(") { let c = node(E_CALL); c.a = e; args_call(c); e = c } else { break } } }
}
return e
@ -311,7 +313,7 @@ fn dir_of(path: ptr) -> ptr {
var i = 0
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
if last < 0 { return "" }
return substr(path, 0, last + 1)
return path[0..0 + (last + 1)]
}
fn path_join(dir: ptr, rel: ptr) -> ptr {
if peek8(rel, 0) == 47 { return rel } # absolute