Phase 7e: polymorphic print(x) + str(x) (retire print_int/print_str)

One `print` instead of two C-style names: `print(x)` writes an int OR a string
followed by a newline, dispatching on the operand type (int -> %d, string ->
%s). `print(int)` emits byte-identically to the old print_int, so every existing
call and every smoke-test output is unchanged.

print_str was only ever the raw IR-to-stdout dump in ir_flush (no newline), which
is not "printing a line" — so it now uses file_write to a new file_stdout()
stream, keeping the emitted IR byte-for-byte identical. That frees `print` to
have consistent always-newline semantics.

Two reseeds: (A) add print + str + file_stdout keeping the intrinsics; (B)
migrate the 61 print_int calls to print, ir_flush to file_write(file_stdout()),
and delete print_int/print_str (+ the now-dead @.fmt_str). str(x) (the
interpolation converter from 7d) is now also a documented standalone builtin.

Vocabulary: print/str/file_stdout in, print_int/print_str out (ludic_syntax.h,
grammar, LudicTokens.kt). LANGUAGE.md updated. Reseeded (22551 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:22:38 +03:00
parent 311042e5ab
commit e9c15cc620
23 changed files with 2947 additions and 2924 deletions

View file

@ -31,7 +31,7 @@ program RPG2D {
}
handler Report phase Render {
for (t, v) in query [Transform, Velocity] { print_int(t.x); print_int(v.speed2) }
for (t, v) in query [Transform, Velocity] { print(t.x); print(v.speed2) }
quit()
}
}

View file

@ -25,8 +25,8 @@ program Hello {
handler Report phase Update {
for (p) in query [Pos] {
print_int(p.x)
print_int(p.y)
print(p.x)
print(p.y)
}
quit()
}

View file

@ -24,11 +24,11 @@ program Arena {
handler Fight phase Update {
for (f) in query [Fighter] {
print_int(damage(f.attack, f.armour, 0))
print_int(damage(f.attack, f.armour, 20))
print_int(hits_to_kill(f.hp, f.attack, f.armour))
print(damage(f.attack, f.armour, 0))
print(damage(f.attack, f.armour, 20))
print(hits_to_kill(f.hp, f.attack, f.armour))
}
print_int(xp_for(3, 4))
print(xp_for(3, 4))
quit()
}
}

View file

@ -10,18 +10,18 @@ program Life {
model Enemy { Health, Sprite }
# ---- program lifecycle -----------------------------------------------------
@OnStart handler Boot { print_int(1) } # once, at boot
@OnQuit handler Bye { print_int(2) } # once, at shutdown
@OnStart handler Boot { print(1) } # once, at boot
@OnQuit handler Bye { print(2) } # once, at shutdown
# ---- property / entity lifecycle -------------------------------------------
@OnAttach(Sprite) handler Load { print_int(Sprite.id + 700) } # when a Sprite is attached
@OnAttach(Sprite) handler Load { print(Sprite.id + 700) } # when a Sprite is attached
@OnSpawn(Enemy) handler Init { Health.hp = Health.max } # constructor
@OnDespawn(Enemy) handler Clean { print_int(Health.hp + 900) } # destructor (dispatched by kind)
@OnDespawn(Enemy) handler Clean { print(Health.hp + 900) } # destructor (dispatched by kind)
handler Seed phase Start { spawn Enemy { Health { max: 50 } } }
handler Run phase Render {
for (h) in query [Health] { print_int(h.hp) } # 50 — Init set hp to max
for (h) in query [Health] { print(h.hp) } # 50 — Init set hp to max
for (e) in query [Health] { despawn self() } # Clean fires: 50 + 900 = 950
quit()
}

View file

@ -24,13 +24,13 @@ program QueryDecl {
# property; `on: Foe` adds the {Foe} kind filter.
@Queries(these: [Battle{hp <= 0 and side == 1}, Pos], on: Foe)
handler CleanBattle phase LateUpdate {
print_int(Pos.x)
print(Pos.x)
despawn self()
}
@Queries(these: [Battle])
handler Census phase Render {
print_int(Battle.hp)
print(Battle.hp)
}
handler Bye phase Render { quit() }

View file

@ -7,32 +7,32 @@
program SceneDemo {
const R_N: int = 0
handler Boot phase Start { set_reg(R_N, 0); print_int(1000) }
handler Boot phase Start { set_reg(R_N, 0); print(1000) }
scene Title start {
on enter { print_int(1) }
on exit { print_int(2) }
on enter { print(1) }
on exit { print(2) }
layer Main {
handler Tick phase Update {
set_reg(R_N, reg(R_N) + 1)
print_int(100 + reg(R_N))
print(100 + reg(R_N))
if reg(R_N) >= 2 { become Play }
}
}
}
scene Play {
on enter { print_int(3); set_reg(R_N, 0) }
on exit { print_int(4) }
on enter { print(3); set_reg(R_N, 0) }
on exit { print(4) }
layer World {
handler Step phase Update {
set_reg(R_N, reg(R_N) + 1)
print_int(200 + reg(R_N))
print(200 + reg(R_N))
if reg(R_N) >= 2 { quit() }
}
}
layer Hud {
handler Draw phase Render { print_int(900) }
handler Draw phase Render { print(900) }
}
}
}

View file

@ -3,24 +3,24 @@
program Strings {
entry {
let greeting = "hello"
if greeting == "hello" { print_int(1) } # content comparison
if greeting != "world" { print_int(2) }
if greeting == "hello" { print(1) } # content comparison
if greeting != "world" { print(2) }
let who = "Ludic"
let msg = greeting + ", " + who + "!" # concatenation chains
if msg == "hello, Ludic!" { print_int(3) }
if msg == "hello, Ludic!" { print(3) }
# build up a string in a loop
var line = ""
var i = 0
while i < 3 { line = line + "ab"; i = i + 1 }
if line == "ababab" { print_int(4) }
if line == "ababab" { print(4) }
if (greeting + who) != greeting { print_int(5) }
if (greeting + who) != greeting { print(5) }
# interpolation: `{expr}` embeds any expression (numbers become text)
let n = 42
if `{greeting}, {who}!` == "hello, Ludic!" { print_int(6) }
if `n={n}, next={n + 1}` == "n=42, next=43" { print_int(7) }
if `{greeting}, {who}!` == "hello, Ludic!" { print(6) }
if `n={n}, next={n + 1}` == "n=42, next=43" { print(7) }
}
}

View file

@ -12,8 +12,8 @@ program Toggles {
model Player { Health, Shield }
# hooks run at the toggle point with the property's data bound by name
@OnDisable(Shield) handler Down { print_int(Shield.amount + 1) } # 5 + 1
@OnEnable(Shield) handler Up { print_int(Shield.amount + 2) } # 5 + 2
@OnDisable(Shield) handler Down { print(Shield.amount + 1) } # 5 + 1
@OnEnable(Shield) handler Up { print(Shield.amount + 2) } # 5 + 2
handler Seed phase Start { spawn Player { Health { max: 50 }, Shield { amount: 5 } } }
@ -21,17 +21,17 @@ program Toggles {
for (e) in query [Player] { disable Shield on self() } # @OnDisable -> 6
var n = 0
for (s) in query [Shield] { n += 1 }
print_int(n) # 0 — no live Shield now
print(n) # 0 — no live Shield now
for (e) in query [Player] { enable Shield on self() } # @OnEnable -> 7, data intact
n = 0
for (s) in query [Shield] { n += 1 }
print_int(n) # 1 — amount is still 5
print(n) # 1 — amount is still 5
disable Player # whole model off
n = 0
for (p) in query [Player] { n += 1 }
print_int(n) # 0 — model disabled
print(n) # 0 — model disabled
quit()
}
}