ludic/selfhost/backend/stdlib/emit_datetime.ludic
Orkuncakilkaya 647dfec334 feat(compiler): list literals, typed compound assignment, file:line diagnostics
- `[a, b, c]` list literals (E_LIST → emit_list); static_type learns
  slice-element, `new T`, list, string and literal kinds
- `x op= y` lowers through the same path as `x = x op y` (emit_bin_vals):
  fixed `*=`/`/=` use the Q16.16 64-bit paths, string `+=` concatenates,
  int→long widens; unary `-` keeps a fixed operand's type (arith_ty)
- one `unescape()` table for "strings", 'chars' and `interpolation`;
  `'\''`, `'\\'`, `'\"'` no longer read as 0; unterminated char literals
  and unexpected characters are errors instead of silently skipped
- every diagnostic is `file:line: error: msg` (g_parse_file / g_err_file,
  Node.file + Node.line set by node()); tok_desc() in expectation errors;
  duplicate `function` names and unknown `phase` names are reported in
  source terms (phase_id used to default unknown phases to Overlay)
- interpolation holes skip braces inside string literals
- hand-IR preludes move from the user `@fn_` prefix to `@lp_` so a user
  `is_ws` / `str_eq` / `path_join` no longer collides at link time
- `@ClearColor(expr)` accepts any constant expression; `Os.pid()` added
  (docs page + inventory); `str_starts()` in support/str
- main.ludic: `else if` flag ladder, char literals, stale script comments
- examples/lang/operators.ludic covers all of the above; os.ludic covers
  Os.pid; docs pages for Os.pid and the Overlay phase; ten changesets
- reseeded: selfhost/ludicc.seed.ll is the new compiler's own fixpoint

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 01:12:16 +03:00

445 lines
22 KiB
Text

# emit_datetime.ludic — the calendar/clock half of the stdlib: the Duration.*,
# Date.* and DateTime.* namespaces (issue #9). Everything is a plain i32 integer
# epoch, never a float, so it is deterministic and bit-identical on every
# platform:
# Duration — a span in whole seconds (Duration.hours(3) == 10800).
# Date — a civil day, stored as the count of days since 1970-01-01 (UTC),
# so Date arithmetic is ordinary integer add/subtract.
# DateTime — an instant, stored as the count of seconds since the 1970 epoch
# (UTC), matching Time.now().
# The two civil<->epoch conversions are Howard Hinnant's public-domain algorithms
# (chrono-compatible, proleptic Gregorian), emitted once per program as the
# @lp_days_from_civil / @lp_civil_from_days prelude and gated by g_uses_datert.
# v1 is UTC-only with no leap seconds; instants are assumed non-negative (dates
# at or after 1970). Timezones, format/parse and a game-controlled simulated
# clock are tracked follow-ups.
# --- Duration.* — spans in whole seconds -------------------------------------
function is_duration_ns(meth: pointer) -> bool {
if (meth == "seconds") or (meth == "minutes") or (meth == "hours") or (meth == "days") { return true }
if (meth == "as_seconds") or (meth == "as_minutes") or (meth == "as_hours") or (meth == "as_days") { return true }
return false
}
function emit_duration_ns(meth: pointer, e: Node) -> Val {
let n = emit_expr(e.kids[0])
if (meth == "seconds") { return val(n.code, "int") } # already seconds
if (meth == "minutes") { return val(emit_bind(`mul i32 {n.code}, 60`), "int") }
if (meth == "hours") { return val(emit_bind(`mul i32 {n.code}, 3600`), "int") }
if (meth == "days") { return val(emit_bind(`mul i32 {n.code}, 86400`), "int") }
if (meth == "as_seconds") { return val(n.code, "int") }
if (meth == "as_minutes") { return val(emit_bind(`sdiv i32 {n.code}, 60`), "int") }
if (meth == "as_hours") { return val(emit_bind(`sdiv i32 {n.code}, 3600`), "int") }
# as_days: whole days in the span (floor toward zero)
return val(emit_bind(`sdiv i32 {n.code}, 86400`), "int")
}
# --- Date.* — a civil day as days-since-1970 ---------------------------------
# load one component (0=year, 1=month, 2=day) of the civil date for epoch-day
# code `ed` by calling the civil_from_days prelude into three stack slots.
function date_component(ed: pointer, which: int) -> pointer {
g_uses_datert = true
let yp = emit_alloca("i32")
let mp = emit_alloca("i32")
let dp = emit_alloca("i32")
emit(` call void @lp_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
var p = yp
if (which == 1) { p = mp }
if (which == 2) { p = dp }
return emit_bind(`load i32, ptr {p}`)
}
function is_date_ns(meth: pointer) -> bool {
if (meth == "new") or (meth == "year") or (meth == "month") or (meth == "day") { return true }
if (meth == "weekday") or (meth == "is_leap") or (meth == "days_in_month") { return true }
if (meth == "to_epoch") or (meth == "add_days") or (meth == "diff_days") { return true }
return false
}
function emit_date_ns(meth: pointer, e: Node) -> Val {
if (meth == "new") { # new(year, month, day) -> Date (epoch-day)
g_uses_datert = true
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
return val(emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {m.code}, i32 {d.code})`), "int")
}
if (meth == "year") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 0), "int") }
if (meth == "month") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 1), "int") }
if (meth == "day") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 2), "int") }
if (meth == "weekday") { # 0=Sunday .. 6=Saturday (1970-01-01 was Thursday)
let ed = emit_expr(e.kids[0])
let s = emit_bind(`add i32 {ed.code}, 4`) # shift so the epoch's Thursday lands right
let r = emit_bind(`srem i32 {s}, 7`)
let rr = emit_bind(`add i32 {r}, 7`) # normalise a negative remainder into [0,7)
return val(emit_bind(`srem i32 {rr}, 7`), "int")
}
if (meth == "is_leap") { # proleptic Gregorian leap-year test
let y = emit_expr(e.kids[0])
let m4 = emit_bind(`srem i32 {y.code}, 4`); let c4 = emit_bind(`icmp eq i32 {m4}, 0`)
let m100 = emit_bind(`srem i32 {y.code}, 100`); let c100 = emit_bind(`icmp ne i32 {m100}, 0`)
let m400 = emit_bind(`srem i32 {y.code}, 400`); let c400 = emit_bind(`icmp eq i32 {m400}, 0`)
let common = emit_bind(`and i1 {c4}, {c100}`) # divisible by 4 but not by 100
let leap = emit_bind(`or i1 {common}, {c400}`) # ...or divisible by 400
return val(emit_bind(`zext i1 {leap} to i32`), "bool")
}
if (meth == "days_in_month") { # length of (year, month) = next month's day 0
g_uses_datert = true
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
let this = emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {m.code}, i32 1)`)
let dec = emit_bind(`icmp eq i32 {m.code}, 12`) # December rolls over to next January
let ny = emit_bind(`add i32 {y.code}, 1`)
let ny2 = emit_bind(`select i1 {dec}, i32 {ny}, i32 {y.code}`)
let nm = emit_bind(`add i32 {m.code}, 1`)
let nm2 = emit_bind(`select i1 {dec}, i32 1, i32 {nm}`)
let next = emit_bind(`call i32 @lp_days_from_civil(i32 {ny2}, i32 {nm2}, i32 1)`)
return val(emit_bind(`sub i32 {next}, {this}`), "int")
}
if (meth == "to_epoch") { # midnight UTC of the day, as a DateTime instant
let ed = emit_expr(e.kids[0])
return val(emit_bind(`mul i32 {ed.code}, 86400`), "int")
}
if (meth == "add_days") { # the day `n` days after `ed`
let ed = emit_expr(e.kids[0]); let n = emit_expr(e.kids[1])
return val(emit_bind(`add i32 {ed.code}, {n.code}`), "int")
}
# diff_days(a, b) -> whole days from b to a (a - b)
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
return val(emit_bind(`sub i32 {a.code}, {b.code}`), "int")
}
# --- DateTime.* — an instant as seconds-since-1970 ---------------------------
# seconds-of-day for instant `t`, normalised into [0, 86400) even for a negative
# instant, so hour/minute/second stay correct.
function dt_secofday(t: pointer) -> pointer {
let r = emit_bind(`srem i32 {t}, 86400`)
let rr = emit_bind(`add i32 {r}, 86400`)
return emit_bind(`srem i32 {rr}, 86400`)
}
# the epoch-day (floored) an instant falls on: floor(t / 86400).
function dt_epochday(t: pointer) -> pointer {
let sod = dt_secofday(t) # t - sod is an exact multiple of 86400
let base = emit_bind(`sub i32 {t}, {sod}`)
return emit_bind(`sdiv i32 {base}, 86400`)
}
function is_datetime_ns(meth: pointer) -> bool {
if (meth == "from") or (meth == "date") or (meth == "add") { return true }
if (meth == "year") or (meth == "month") or (meth == "day") or (meth == "weekday") { return true }
if (meth == "hour") or (meth == "minute") or (meth == "second") { return true }
if (meth == "format") or (meth == "parse") { return true }
return false
}
# concat two runtime string codes -> a fresh string code
function dt_concat(a: pointer, b: pointer) -> pointer {
return emit_bind(`call ptr @lp_str_concat(ptr {a}, ptr {b})`)
}
# does the pattern have token `tok` (length tlen) starting at index i?
function dt_tok_at(pat: pointer, n: int, i: int, tok: pointer, tlen: int) -> bool {
if (i + tlen) > n { return false }
var k = 0
while k < tlen { if pat[i + k] != tok[k] { return false }; k += 1 }
return true
}
# DateTime.format(t, "pattern") -> string. The pattern MUST be a string literal;
# the tokens YYYY / YY / MM / DD / HH / mm / ss expand to zero-padded fields and
# every other character is copied through verbatim. Expanded at compile time into
# a fold of @lp_str_concat over literal runs and @lp_dt_pad0 field conversions.
function emit_datetime_format(e: Node) -> Val {
g_uses_datert = true
g_uses_str = true
if e.kids[1].kind != E_STR { perr("DateTime.format needs a string-literal pattern") }
let t = emit_expr(e.kids[0])
let pat = e.kids[1].s
let n = len(pat)
# compute all six components once
let ed = dt_epochday(t.code)
let yp = emit_alloca("i32"); let mp = emit_alloca("i32"); let dp = emit_alloca("i32")
emit(` call void @lp_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
let yv = emit_bind(`load i32, ptr {yp}`)
let mv = emit_bind(`load i32, ptr {mp}`)
let dv = emit_bind(`load i32, ptr {dp}`)
let sod = dt_secofday(t.code)
let hh = emit_bind(`sdiv i32 {sod}, 3600`)
let m3 = emit_bind(`srem i32 {sod}, 3600`)
let mi = emit_bind(`sdiv i32 {m3}, 60`)
let ss = emit_bind(`srem i32 {sod}, 60`)
let yy = emit_bind(`srem i32 {yv}, 100`)
var acc = emit_str_const("")
let lit = buf_new()
var i = 0
while i < n {
var field: pointer = null; var width = 0; var tlen = 0
if dt_tok_at(pat, n, i, "YYYY", 4) { field = yv; width = 4; tlen = 4 }
else { if dt_tok_at(pat, n, i, "YY", 2) { field = yy; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "MM", 2) { field = mv; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "DD", 2) { field = dv; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "HH", 2) { field = hh; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "mm", 2) { field = mi; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "ss", 2) { field = ss; width = 2; tlen = 2 } } } } } } }
if (field == null) { buf_putc(lit, pat[i]); i += 1 }
else {
let ls = buf_str(lit)
if len(ls) > 0 { acc = dt_concat(acc, emit_str_const(ls)) }
let piece = emit_bind(`call ptr @lp_dt_pad0(i32 {field}, i32 {width})`)
acc = dt_concat(acc, piece)
lit.len = 0 # start a fresh literal run
i += tlen
}
}
let tail = buf_str(lit)
if len(tail) > 0 { acc = dt_concat(acc, emit_str_const(tail)) }
return val(acc, "string")
}
# DateTime.parse(s, "pattern") -> DateTime. The pattern MUST be a string literal;
# each field is read from a fixed offset (the layout the pattern fixes) and any
# non-digit where a digit is expected fails the parse, returning -1. Missing
# fields default to 1970-01-01 00:00:00.
function emit_datetime_parse(e: Node) -> Val {
g_uses_datert = true
if e.kids[1].kind != E_STR { perr("DateTime.parse needs a string-literal pattern") }
let s = emit_expr(e.kids[0])
let pat = e.kids[1].s
let n = len(pat)
let failp = emit_alloca("i32")
store_at("i32", "0", failp)
# component slots, seeded to the epoch defaults
let yp = emit_alloca("i32"); store_at("i32", "1970", yp)
let mp = emit_alloca("i32"); store_at("i32", "1", mp)
let dp = emit_alloca("i32"); store_at("i32", "1", dp)
let hp = emit_alloca("i32"); store_at("i32", "0", hp)
let np = emit_alloca("i32"); store_at("i32", "0", np)
let sp = emit_alloca("i32"); store_at("i32", "0", sp)
var off = 0
var i = 0
while i < n {
var slot: pointer = null; var width = 0; var tlen = 0
if dt_tok_at(pat, n, i, "YYYY", 4) { slot = yp; width = 4; tlen = 4 }
else { if dt_tok_at(pat, n, i, "MM", 2) { slot = mp; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "DD", 2) { slot = dp; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "HH", 2) { slot = hp; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "mm", 2) { slot = np; width = 2; tlen = 2 }
else { if dt_tok_at(pat, n, i, "ss", 2) { slot = sp; width = 2; tlen = 2 } } } } } }
if (slot == null) { off += 1; i += 1 }
else {
let rd = emit_bind(`call i32 @lp_dt_rd(ptr {s.code}, i32 {itoa(off)}, i32 {width}, ptr {failp})`)
store_at("i32", rd, slot)
off += width
i += tlen
}
}
let y = emit_bind(`load i32, ptr {yp}`)
let mo = emit_bind(`load i32, ptr {mp}`)
let d = emit_bind(`load i32, ptr {dp}`)
let h = emit_bind(`load i32, ptr {hp}`)
let mn = emit_bind(`load i32, ptr {np}`)
let sc = emit_bind(`load i32, ptr {sp}`)
let ed = emit_bind(`call i32 @lp_days_from_civil(i32 {y}, i32 {mo}, i32 {d})`)
let days = emit_bind(`mul i32 {ed}, 86400`)
let hs = emit_bind(`mul i32 {h}, 3600`)
let ns = emit_bind(`mul i32 {mn}, 60`)
let a1 = emit_bind(`add i32 {days}, {hs}`)
let a2 = emit_bind(`add i32 {a1}, {ns}`)
let inst = emit_bind(`add i32 {a2}, {sc}`)
let failed = emit_bind(`load i32, ptr {failp}`)
let bad = emit_bind(`icmp ne i32 {failed}, 0`)
return val(emit_bind(`select i1 {bad}, i32 -1, i32 {inst}`), "int")
}
function emit_datetime_ns(meth: pointer, e: Node) -> Val {
if (meth == "format") { return emit_datetime_format(e) }
if (meth == "parse") { return emit_datetime_parse(e) }
if (meth == "from") { # from(year, month, day, hour, minute, second) -> DateTime
g_uses_datert = true
let y = emit_expr(e.kids[0]); let mo = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
let h = emit_expr(e.kids[3]); let mi = emit_expr(e.kids[4]); let s = emit_expr(e.kids[5])
let ed = emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {mo.code}, i32 {d.code})`)
let days = emit_bind(`mul i32 {ed}, 86400`)
let hh = emit_bind(`mul i32 {h.code}, 3600`)
let mm = emit_bind(`mul i32 {mi.code}, 60`)
let a = emit_bind(`add i32 {days}, {hh}`)
let b = emit_bind(`add i32 {a}, {mm}`)
return val(emit_bind(`add i32 {b}, {s.code}`), "int")
}
if (meth == "date") { # the calendar day this instant is on -> Date
let t = emit_expr(e.kids[0])
return val(dt_epochday(t.code), "int")
}
if (meth == "add") { # the instant `dur` seconds after `t`
let t = emit_expr(e.kids[0]); let dur = emit_expr(e.kids[1])
return val(emit_bind(`add i32 {t.code}, {dur.code}`), "int")
}
if (meth == "year") { let t = emit_expr(e.kids[0]); return val(date_component(dt_epochday(t.code), 0), "int") }
if (meth == "month") { let t = emit_expr(e.kids[0]); return val(date_component(dt_epochday(t.code), 1), "int") }
if (meth == "day") { let t = emit_expr(e.kids[0]); return val(date_component(dt_epochday(t.code), 2), "int") }
if (meth == "weekday") { # 0=Sunday .. 6=Saturday
let t = emit_expr(e.kids[0])
let ed = dt_epochday(t.code)
let s = emit_bind(`add i32 {ed}, 4`)
let r = emit_bind(`srem i32 {s}, 7`)
let rr = emit_bind(`add i32 {r}, 7`)
return val(emit_bind(`srem i32 {rr}, 7`), "int")
}
if (meth == "hour") { # 0..23
let t = emit_expr(e.kids[0])
let sod = dt_secofday(t.code)
return val(emit_bind(`sdiv i32 {sod}, 3600`), "int")
}
if (meth == "minute") { # 0..59
let t = emit_expr(e.kids[0])
let sod = dt_secofday(t.code)
let h = emit_bind(`srem i32 {sod}, 3600`)
return val(emit_bind(`sdiv i32 {h}, 60`), "int")
}
# second: 0..59
let t = emit_expr(e.kids[0])
let sod = dt_secofday(t.code)
return val(emit_bind(`srem i32 {sod}, 60`), "int")
}
# emit_datetime_prelude — the two civil<->epoch conversions, emitted once per
# program that uses Date/DateTime (g_uses_datert). Both are Howard Hinnant's
# public-domain proleptic-Gregorian algorithms, in pure i32 integer IR, so they
# are deterministic and bit-identical everywhere. 719468 is the day count from
# 0000-03-01 to 1970-01-01; 146097 is the days in a 400-year era.
function emit_datetime_prelude() -> void {
# @lp_days_from_civil(y, m, d) -> days since 1970-01-01
emith("define i32 @lp_days_from_civil(i32 %y0, i32 %m, i32 %d) {\n")
emith(" %mle2 = icmp sle i32 %m, 2\n")
emith(" %ysub = select i1 %mle2, i32 1, i32 0\n")
emith(" %y = sub i32 %y0, %ysub\n")
emith(" %yneg = icmp slt i32 %y, 0\n")
emith(" %ym399 = sub i32 %y, 399\n")
emith(" %enum = select i1 %yneg, i32 %ym399, i32 %y\n")
emith(" %era = sdiv i32 %enum, 400\n")
emith(" %era400 = mul i32 %era, 400\n")
emith(" %yoe = sub i32 %y, %era400\n")
emith(" %mgt2 = icmp sgt i32 %m, 2\n")
emith(" %mshift = select i1 %mgt2, i32 -3, i32 9\n")
emith(" %mm = add i32 %m, %mshift\n")
emith(" %t153 = mul i32 153, %mm\n")
emith(" %t153b = add i32 %t153, 2\n")
emith(" %doy0 = sdiv i32 %t153b, 5\n")
emith(" %doy1 = add i32 %doy0, %d\n")
emith(" %doy = sub i32 %doy1, 1\n")
emith(" %yoe365 = mul i32 %yoe, 365\n")
emith(" %yoe4 = sdiv i32 %yoe, 4\n")
emith(" %yoe100 = sdiv i32 %yoe, 100\n")
emith(" %doe0 = add i32 %yoe365, %yoe4\n")
emith(" %doe1 = sub i32 %doe0, %yoe100\n")
emith(" %doe = add i32 %doe1, %doy\n")
emith(" %e146097 = mul i32 %era, 146097\n")
emith(" %r0 = add i32 %e146097, %doe\n")
emith(" %r = sub i32 %r0, 719468\n")
emith(" ret i32 %r\n")
emith("}\n")
# @lp_civil_from_days(z0, yp, mp, dp): write the civil (year, month, day) of
# the epoch-day z0 through the three out-pointers.
emith("define void @lp_civil_from_days(i32 %z0, ptr %yp, ptr %mp, ptr %dp) {\n")
emith(" %z = add i32 %z0, 719468\n")
emith(" %zneg = icmp slt i32 %z, 0\n")
emith(" %zm = sub i32 %z, 146096\n")
emith(" %enum = select i1 %zneg, i32 %zm, i32 %z\n")
emith(" %era = sdiv i32 %enum, 146097\n")
emith(" %era146097 = mul i32 %era, 146097\n")
emith(" %doe = sub i32 %z, %era146097\n")
emith(" %d1460 = sdiv i32 %doe, 1460\n")
emith(" %d36524 = sdiv i32 %doe, 36524\n")
emith(" %d146096 = sdiv i32 %doe, 146096\n")
emith(" %ya = sub i32 %doe, %d1460\n")
emith(" %yb = add i32 %ya, %d36524\n")
emith(" %yc = sub i32 %yb, %d146096\n")
emith(" %yoe = sdiv i32 %yc, 365\n")
emith(" %era400 = mul i32 %era, 400\n")
emith(" %yy = add i32 %yoe, %era400\n")
emith(" %yoe365 = mul i32 %yoe, 365\n")
emith(" %yoe4 = sdiv i32 %yoe, 4\n")
emith(" %yoe100 = sdiv i32 %yoe, 100\n")
emith(" %sub0 = add i32 %yoe365, %yoe4\n")
emith(" %sub1 = sub i32 %sub0, %yoe100\n")
emith(" %doy = sub i32 %doe, %sub1\n")
emith(" %fivedoy = mul i32 5, %doy\n")
emith(" %fivedoy2 = add i32 %fivedoy, 2\n")
emith(" %mpv = sdiv i32 %fivedoy2, 153\n")
emith(" %m153 = mul i32 153, %mpv\n")
emith(" %m153b = add i32 %m153, 2\n")
emith(" %m153c = sdiv i32 %m153b, 5\n")
emith(" %dd0 = sub i32 %doy, %m153c\n")
emith(" %dd = add i32 %dd0, 1\n")
emith(" %mplt10 = icmp slt i32 %mpv, 10\n")
emith(" %mplus3 = add i32 %mpv, 3\n")
emith(" %mminus9 = sub i32 %mpv, 9\n")
emith(" %mfin = select i1 %mplt10, i32 %mplus3, i32 %mminus9\n")
emith(" %mmle2 = icmp sle i32 %mfin, 2\n")
emith(" %yinc = select i1 %mmle2, i32 1, i32 0\n")
emith(" %yfin = add i32 %yy, %yinc\n")
emith(" store i32 %yfin, ptr %yp\n")
emith(" store i32 %mfin, ptr %mp\n")
emith(" store i32 %dd, ptr %dp\n")
emith(" ret void\n")
emith("}\n")
# @lp_dt_pad0(v, w): a fresh, malloc'd, w-digit zero-padded decimal of v
# (v assumed non-negative). Backs DateTime.format's numeric fields.
emith("define ptr @lp_dt_pad0(i32 %v, i32 %w) {\n")
emith(" %we = zext i32 %w to i64\n")
emith(" %sz = add i64 %we, 1\n")
emith(" %buf = call ptr @malloc(i64 %sz)\n")
emith(" %endp = getelementptr i8, ptr %buf, i32 %w\n")
emith(" store i8 0, ptr %endp\n")
emith(" %kp = alloca i32\n %vp = alloca i32\n")
emith(" %km1 = sub i32 %w, 1\n store i32 %km1, ptr %kp\n store i32 %v, ptr %vp\n")
emith(" br label %loop\n")
emith("loop:\n %k = load i32, ptr %kp\n %kok = icmp sge i32 %k, 0\n br i1 %kok, label %body, label %done\n")
emith("body:\n %vv = load i32, ptr %vp\n %d = srem i32 %vv, 10\n %ch = add i32 %d, 48\n %ch8 = trunc i32 %ch to i8\n")
emith(" %cp = getelementptr i8, ptr %buf, i32 %k\n store i8 %ch8, ptr %cp\n")
emith(" %vn = sdiv i32 %vv, 10\n store i32 %vn, ptr %vp\n %kn = sub i32 %k, 1\n store i32 %kn, ptr %kp\n br label %loop\n")
emith("done:\n ret ptr %buf\n}\n")
# @lp_dt_rd(s, off, w, failp): read w decimal digits of s starting at off into an
# int; on any non-digit set *failp = 1. Backs DateTime.parse's fixed-width fields.
emith("define i32 @lp_dt_rd(ptr %s, i32 %off, i32 %w, ptr %failp) {\n")
emith(" %accp = alloca i32\n store i32 0, ptr %accp\n %kp = alloca i32\n store i32 0, ptr %kp\n")
emith(" br label %loop\n")
emith("loop:\n %k = load i32, ptr %kp\n %kok = icmp slt i32 %k, %w\n br i1 %kok, label %body, label %done\n")
emith("body:\n %idx = add i32 %off, %k\n %cp = getelementptr i8, ptr %s, i32 %idx\n %c8 = load i8, ptr %cp\n %c = sext i8 %c8 to i32\n")
emith(" %zero = icmp eq i32 %c, 0\n br i1 %zero, label %stop, label %digit\n") # stop at the terminator, never read past it
emith("stop:\n store i32 1, ptr %failp\n br label %done\n")
emith("digit:\n %lt = icmp slt i32 %c, 48\n %gt = icmp sgt i32 %c, 57\n %isbad = or i1 %lt, %gt\n br i1 %isbad, label %fail, label %ok\n")
emith("fail:\n store i32 1, ptr %failp\n br label %next\n")
emith("ok:\n %acc0 = load i32, ptr %accp\n %m10 = mul i32 %acc0, 10\n %dg = sub i32 %c, 48\n %acc1 = add i32 %m10, %dg\n store i32 %acc1, ptr %accp\n br label %next\n")
emith("next:\n %kn = add i32 %k, 1\n store i32 %kn, ptr %kp\n br label %loop\n")
emith("done:\n %r = load i32, ptr %accp\n ret i32 %r\n}\n")
}
# --- Clock.* — a game-controlled simulated clock -----------------------------
# @L_clock (declared in emit_head, universal) is a plain seconds counter the game
# owns: unlike Time.now / Time.since, it never touches the wall clock, so gameplay
# that reads Clock.now() is deterministic and replay-safe. Advance it however the
# simulation dictates (e.g. by Duration.* each tick), or set it outright.
function is_clock_ns(meth: pointer) -> bool {
if (meth == "now") or (meth == "set") or (meth == "advance") or (meth == "reset") { return true }
return false
}
function emit_clock_ns(meth: pointer, e: Node) -> Val {
if (meth == "now") { # the current simulated instant
return val(emit_bind("load i32, ptr @L_clock"), "int")
}
if (meth == "reset") { # back to the epoch (0)
emit(" store i32 0, ptr @L_clock\n")
return val("0", "void")
}
if (meth == "set") { # set the clock to instant t
let t = emit_expr(e.kids[0])
emit(` store i32 {t.code}, ptr @L_clock\n`)
return val("0", "void")
}
# advance: move the clock forward by a Duration (seconds)
let d = emit_expr(e.kids[0])
let cur = emit_bind("load i32, ptr @L_clock")
let nv = emit_bind(`add i32 {cur}, {d.code}`)
emit(` store i32 {nv}, ptr @L_clock\n`)
return val("0", "void")
}