feat(lang): bind a port member that takes nothing to a variable
bind Purse { money: g_money } writes the getter bind_Purse_money in the
bind's own file, so a one-line wrapper per member is not needed; a
member that takes something is refused a variable. Reseed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7b8c134c21
commit
7ef6c7ec75
8 changed files with 45376 additions and 44711 deletions
|
|
@ -190,6 +190,12 @@ bind Clock { now: fn game_hours }
|
||||||
function game_hours() -> int { return g_hours }
|
function game_hours() -> int { return g_hours }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A member that takes nothing can be bound to a variable instead: `bind Purse { money: g_money }` for a
|
||||||
|
`money: fn() -> int` writes the getter (`bind_Purse_money`, returning `g_money` as it is at each call)
|
||||||
|
in the bind's own file, so the one-line wrapper function is not needed. A member that takes something
|
||||||
|
is refused a variable (`bind Purse: price is a fn(int)->int, and only a member that takes nothing can
|
||||||
|
be bound to a variable`).
|
||||||
|
|
||||||
Calls go through the port by name, `Clock.now()`. `clock` never names `app`, so it needs no
|
Calls go through the port by name, `Clock.now()`. `clock` never names `app`, so it needs no
|
||||||
`uses app`; the binder must be able to see the port - it is `export`ed, and a binder that says
|
`uses app`; the binder must be able to see the port - it is `export`ed, and a binder that says
|
||||||
`uses` names the port's module. What the bind names (`fn game_hours`) is checked from the bind's
|
`uses` names the port's module. What the bind names (`fn game_hours`) is checked from the bind's
|
||||||
|
|
|
||||||
5
changes/bind-variable.md
Normal file
5
changes/bind-variable.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
bump: minor
|
||||||
|
type: feature
|
||||||
|
**`bind Purse { money: g_money }` - a port member bound to a variable.** A member that takes nothing
|
||||||
|
may name a global instead of a function; the compiler writes the getter in the bind's file, so the
|
||||||
|
one-line wrapper is gone. A member that takes something is refused a variable.
|
||||||
22
examples/modules/port_vars.ludic
Normal file
22
examples/modules/port_vars.ludic
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# port_vars.ludic — L3: a port member that takes nothing can be bound to a variable, `money: g_money`
|
||||||
|
# - the compiler writes the getter (bind_Purse_money) in the bind's own file - and a member that takes
|
||||||
|
# something is still bound to a function.
|
||||||
|
#
|
||||||
|
# Running it prints: 40 45 12
|
||||||
|
program PortVars {
|
||||||
|
port Purse {
|
||||||
|
money: fn() -> int
|
||||||
|
price: fn(int) -> int
|
||||||
|
}
|
||||||
|
var g_money: int = 40
|
||||||
|
var g_rate: int = 3
|
||||||
|
function priced(n: int) -> int {
|
||||||
|
return n * g_rate
|
||||||
|
}
|
||||||
|
bind Purse { money: g_money, price: fn priced }
|
||||||
|
entry {
|
||||||
|
let a = Purse.money()
|
||||||
|
g_money += 5
|
||||||
|
print(`{a} {Purse.money()} {Purse.price(4)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
examples/rejected/port_var_args.ludic
Normal file
9
examples/rejected/port_var_args.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# L3: a variable answers a member that takes nothing; price takes an int, so it binds a function
|
||||||
|
program PortVarArgs {
|
||||||
|
port Purse { price: fn(int) -> int }
|
||||||
|
var g_rate: int = 3
|
||||||
|
bind Purse { price: g_rate }
|
||||||
|
entry {
|
||||||
|
print(Purse.price(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# program together, so the module never names (or `uses`) the code that answers it.
|
# program together, so the module never names (or `uses`) the code that answers it.
|
||||||
#
|
#
|
||||||
# port Clock { now: fn() -> float, day: fn() -> int = fn default_day } # in module clock
|
# port Clock { now: fn() -> float, day: fn() -> int = fn default_day } # in module clock
|
||||||
# bind Clock { now: fn game_now } # in the app
|
# bind Clock { now: fn game_now, day: g_day } # in the app; a variable for a member taking nothing
|
||||||
# Clock.now() # anywhere that sees it
|
# Clock.now() # anywhere that sees it
|
||||||
#
|
#
|
||||||
# A port is a record of function values (the record `Clock_port`, members without a default are
|
# A port is a record of function values (the record `Clock_port`, members without a default are
|
||||||
|
|
@ -88,6 +88,13 @@ function port_bind_one(b: Node) -> void {
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
# `member: some_global` binds a member that takes nothing to a variable: a getter is written
|
||||||
|
i = 0
|
||||||
|
while i < len(rec.kids) {
|
||||||
|
let e = rec.kids[i].a
|
||||||
|
if e != null and e.kind == E_ID { port_bind_var(b, c, rec.kids[i]) }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
# what the bind names is seen from the bind's file: the port itself, and every `fn name`
|
# what the bind names is seen from the bind's file: the port itself, and every `fn name`
|
||||||
g_err_file = b.file
|
g_err_file = b.file
|
||||||
g_err_line = b.line
|
g_err_line = b.line
|
||||||
|
|
@ -125,6 +132,40 @@ function port_defaults_only(k: int) -> void {
|
||||||
v.a = nw
|
v.a = nw
|
||||||
g_pt_bind[k] = nw
|
g_pt_bind[k] = nw
|
||||||
}
|
}
|
||||||
|
# `money: gm_money` for a `money: fn() -> int` member: the function `bind_<Port>_<member>` that
|
||||||
|
# returns the variable, written in the bind's own file (so it sees what the bind sees)
|
||||||
|
function port_bind_var(b: Node, c: Node, fi: Node) -> void {
|
||||||
|
let name = fi.a.s
|
||||||
|
let g = find_global(name)
|
||||||
|
if g == null { port_err(b, `bind {b.s}: {fi.s}: {name} is not a variable (a function is bound as fn {name})`) }
|
||||||
|
let fty = field_type(c, fi.s)
|
||||||
|
let n = len(fty)
|
||||||
|
if n < 7 or not (fty[0 .. 6] == "fn()->") {
|
||||||
|
port_err(b, `bind {b.s}: {fi.s} is a {fty}, and only a member that takes nothing can be bound to a variable`)
|
||||||
|
}
|
||||||
|
let ret = fty[6 .. n]
|
||||||
|
let gen = `bind_{b.s}_{fi.s}`
|
||||||
|
let src = `function {gen}() -> {ret} {{\n return {name}\n}}\n`
|
||||||
|
let saved_toks = toks
|
||||||
|
let saved_pi = pi
|
||||||
|
let saved_file = g_parse_file
|
||||||
|
let saved_parsing = g_parsing
|
||||||
|
g_parse_file = b.file
|
||||||
|
g_parsing = true
|
||||||
|
lex_at(src, b.line)
|
||||||
|
pi = 0
|
||||||
|
skipnl()
|
||||||
|
parse_one_decl()
|
||||||
|
toks = saved_toks
|
||||||
|
pi = saved_pi
|
||||||
|
g_parse_file = saved_file
|
||||||
|
g_parsing = saved_parsing
|
||||||
|
let r = node(E_FNREF)
|
||||||
|
r.s = gen
|
||||||
|
r.file = b.file
|
||||||
|
r.line = b.line
|
||||||
|
fi.a = r
|
||||||
|
}
|
||||||
function ports_finish() -> void {
|
function ports_finish() -> void {
|
||||||
let saved = g_parsing
|
let saved = g_parsing
|
||||||
g_parsing = false
|
g_parsing = false
|
||||||
|
|
|
||||||
45000
selfhost/ludicc.seed.ll
45000
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -844,6 +844,8 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/port_unbound", "port Clock is used here but never bound", "a port used and never bound is refused at the use")
|
reject_case("rejected/port_unbound", "port Clock is used here but never bound", "a port used and never bound is refused at the use")
|
||||||
reject_case("rejected/port_missing", "bind Clock leaves out day, which has no default", "a bind gives every required member")
|
reject_case("rejected/port_missing", "bind Clock leaves out day, which has no default", "a bind gives every required member")
|
||||||
reject_case("rejected/port_twice", "port Clock is bound twice (first at", "a port is bound once")
|
reject_case("rejected/port_twice", "port Clock is bound twice (first at", "a port is bound once")
|
||||||
|
feat_case("modules/port_vars", "", "40 45 12", "port_vars.ludic (L3: a port member that takes nothing bound to a variable, money: g_money)")
|
||||||
|
reject_case("rejected/port_var_args", "only a member that takes nothing can be bound to a variable", "a member with parameters binds a function, not a variable")
|
||||||
reject_case("rejected/port_new", "Clock is a port: it is filled once with 'bind Clock", "a port is bound, not made with new")
|
reject_case("rejected/port_new", "Clock is a port: it is filled once with 'bind Clock", "a port is bound, not made with new")
|
||||||
reject_case("rejected/port_private", "Clock is private to module clock", "whoever binds a port has to see it")
|
reject_case("rejected/port_private", "Clock is private to module clock", "whoever binds a port has to see it")
|
||||||
reject_case("rejected/uses_package", "mech uses ludic_inventory.inv_count", "a package's module counts under `uses` like any other")
|
reject_case("rejected/uses_package", "mech uses ludic_inventory.inv_count", "a package's module counts under `uses` like any other")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue