feat(lang): L5 generic records and functions
property Pool<T> { ... }, function first<T>(xs: []T) -> T, map<T, U> over fn
types; a type writes an instance as Pool<Thing>, nested as deep as needed. The
parser names an instance Pool$Thing and remembers its generic and arguments; the
checker takes the generic declarations out, infers a call's type arguments from
its arguments or its result's declared slot, and makes each instance once as an
ordinary record or function, checked like any other. Errors print Pool<Thing>.
An instance keeps its generic's module and export (L3). ludic-fmt keeps type
arguments together while spacing comparisons and shifts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
57b66bdf47
commit
6a24b14f0e
26 changed files with 61796 additions and 51949 deletions
|
|
@ -18,6 +18,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/frontend/lex.ludic")
|
||||
push(f, "selfhost/frontend/parse.ludic")
|
||||
push(f, "selfhost/frontend/parse_game.ludic")
|
||||
push(f, "selfhost/frontend/generics.ludic")
|
||||
push(f, "selfhost/backend/emit_core.ludic")
|
||||
push(f, "selfhost/backend/emit_head.ludic")
|
||||
push(f, "selfhost/backend/emit_addr.ludic")
|
||||
|
|
@ -62,6 +63,8 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/check/check_ops.ludic")
|
||||
push(f, "selfhost/check/check_call.ludic")
|
||||
push(f, "selfhost/check/check_stmt.ludic")
|
||||
push(f, "selfhost/check/check_gen.ludic")
|
||||
push(f, "selfhost/check/check_gen_call.ludic")
|
||||
push(f, "selfhost/backend/emit_stmt.ludic")
|
||||
push(f, "selfhost/backend/game/emit_ecs.ludic")
|
||||
push(f, "selfhost/backend/game/emit_query.ludic")
|
||||
|
|
|
|||
|
|
@ -726,6 +726,11 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/record_as_text", "argument 1 of sign wants a string and this is a Trail", "a record is not text")
|
||||
reject_case("rejected/text_plus_number", "text joins text only", "text plus a number is refused")
|
||||
reject_count("rejected/every_error", 3, "every type error in a program is reported at once, not only the first")
|
||||
feat_case("lang/generics", "", "7 Crater Lake a 2 west 7 0.75 2.5 1", "generics.ludic (L5: generic records and functions, nested instances, fn-typed parameters and fields, inference from the result's slot)")
|
||||
reject_case("rejected/generic_unbound", "cannot tell what T is in this call to pool_new", "a call whose type arguments nothing decides is refused")
|
||||
reject_case("rejected/generic_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types")
|
||||
reject_case("rejected/generic_arity", "Pool takes 1 type argument(s) and Pool<int, string> gives 2", "a generic takes as many type arguments as it has parameters")
|
||||
reject_case("rejected/private_generic", "grow is private to module kit", "an instance of a private generic is private to its module")
|
||||
|
||||
# EV2 the world table: the mod reflection ABI, callable from Ludic by name.
|
||||
net_case("ecs/world_get", "50 1 7")
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ program LudicFmt {
|
|||
var tk_end: []int
|
||||
var tk_line: []int
|
||||
var linestart: []int
|
||||
var tk_gen: []int # 1 on a `<`, `>` or `>>` that brackets type arguments
|
||||
|
||||
function ntok() -> int { return len(tk_kind) }
|
||||
function tok_len(i: int) -> int { return tk_end[i] - tk_start[i] }
|
||||
|
|
@ -197,6 +198,51 @@ program LudicFmt {
|
|||
push_tok(LT_ERR, i, i + ln, line); i += ln
|
||||
}
|
||||
push_tok(LT_EOF, i, i, line)
|
||||
mark_generics()
|
||||
}
|
||||
|
||||
# ---- generics: Pool<Thing>, first<T>(, Map<string, []int> ----
|
||||
function op_is(i: int, t: pointer) -> bool { return tk_kind[i] == LT_OP and (tok_text(i) == t) }
|
||||
# a token that may stand inside a type argument list
|
||||
function type_ish(i: int) -> bool {
|
||||
let k = tk_kind[i]
|
||||
if k == LT_ID or k == LT_TYPE { return true }
|
||||
if k == LT_KW { return (tok_text(i) == "fn") }
|
||||
if k != LT_OP { return false }
|
||||
return op_is(i, ",") or op_is(i, "[") or op_is(i, "]") or op_is(i, "<") or op_is(i, ">") or op_is(i, ">>") or op_is(i, "(") or op_is(i, ")") or op_is(i, "->")
|
||||
}
|
||||
# a `<` written against a name, closed by a matching `>` on the same line with only a type
|
||||
# between, opens type arguments; anything else is a comparison or a shift
|
||||
function mark_generics() -> void {
|
||||
tk_gen = new []int
|
||||
var i = 0
|
||||
while i < ntok() { push(tk_gen, 0); i += 1 }
|
||||
i = 1
|
||||
while i < ntok() {
|
||||
let p = i - 1
|
||||
if op_is(i, "<") and tk_gen[i] == 0 and (tk_kind[p] == LT_ID or tk_kind[p] == LT_TYPE) and tk_end[p] == tk_start[i] {
|
||||
var depth = 1
|
||||
var j = i + 1
|
||||
var ok = true
|
||||
while j < ntok() and depth > 0 and ok {
|
||||
if not type_ish(j) or tk_line[j] != tk_line[i] { ok = false }
|
||||
else {
|
||||
if op_is(j, "<") { depth += 1 }
|
||||
if op_is(j, ">") { depth -= 1 }
|
||||
if op_is(j, ">>") { depth -= 2 }
|
||||
if depth > 0 { j += 1 }
|
||||
}
|
||||
}
|
||||
if ok and depth == 0 {
|
||||
var m = i
|
||||
while m <= j {
|
||||
if op_is(m, "<") or op_is(m, ">") or op_is(m, ">>") { tk_gen[m] = 1 }
|
||||
m += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- token-stream helpers ----
|
||||
|
|
@ -249,6 +295,10 @@ program LudicFmt {
|
|||
let p0 = src[tk_start[prev]]; let c0 = src[tk_start[cur]]
|
||||
let p1 = (plen == 1); let c1 = (clen == 1)
|
||||
if pk == LT_ERR or ck == LT_ERR { return tk_start[cur] - tk_end[prev] } # preserve an error token's spacing
|
||||
# type arguments hug: Pool<Thing>, first<T>(xs), Pool<Pool<int>>
|
||||
if tk_gen[cur] == 1 { return 0 }
|
||||
if tk_gen[prev] == 1 and op_is(prev, "<") { return 0 }
|
||||
if tk_gen[prev] == 1 and c1 and c0 == '(' { return 0 }
|
||||
if c1 and (c0 == ')' or c0 == ']' or c0 == ',' or c0 == ':' or c0 == ';') { return 0 } # ) ] , : ;
|
||||
if c1 and c0 == '.' and ck == LT_OP and dot_tight(prev) { return 0 }
|
||||
if p1 and p0 == '.' and pk == LT_OP and dot_tight(cur) { return 0 }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue