Skip to content

Commit 2a738f6

Browse files
committed
release: v0.1.0 "Berlin"
Generics, standard repositories, and query provider-binding. - lang: generic interfaces/classes via monomorphization, with default-method materialization so a class inherits (and can override) interface defaults - std/data: Repository / CrudRepository — implement getProvider() + source(), inherit findAll / findById / save / delete / deleteById and entity<->model conversion as defaults - query: .using(provider) binds a provider to any query so terminals run against it across method boundaries; .first() terminal; QueryProvider.name() enables `where`-based provider selection - codegen: `x as Json` now works on primitive values - examples/data: repository, sqlite-backed, and provider-where demos - editors: tree-sitter grammar handles generic declarations and `as` casts; bumped vscode / zed / tree-sitter extension versions
1 parent ca259af commit 2a738f6

30 files changed

Lines changed: 24499 additions & 16720 deletions

compiler/impl/codegen/codecs.xi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ mapper jsonOfPayload(prog: Program, xtype: String, code: String) -> String {
118118
+ " for (xc_integer_t __pi = 0; __pi < (xc_integer_t)__pv.len; __pi++)"
119119
+ " xstd_json_push(__pa, " + encE + "); __pa; })"
120120
}
121+
// scalar: a primitive (Integer/String/Number/Bool/Json) encodes directly, a
122+
// compound routes through its derived codec — jsonEncodeExpr handles both.
123+
let enc = jsonEncodeExpr(prog, xtype.xnameToCtype(), code)
124+
if string_len(enc) > 0 { return enc }
121125
return "xc_tojson_" + xtype + "(" + code + ")"
122126
}
123127

compiler/impl/codegen/generics.xi

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,40 @@ predicate Program.isGenericIfaceName(name: String) {
226226
return false
227227
}
228228

229+
predicate matHasMethod(ms: MethodSpec[], name: String) {
230+
let i = 0
231+
while i < methodSpecLen(ms) { if methodSpecGet(ms, i).name == name { return true } i = i + 1 }
232+
return false
233+
}
234+
235+
mapper ifaceByName(acc: IfaceSpec[], name: String) -> IfaceSpec {
236+
let i = 0
237+
while i < ifaceSpecLen(acc) {
238+
if ifaceSpecGet(acc, i).name == name { return ifaceSpecGet(acc, i) }
239+
i = i + 1
240+
}
241+
return IfaceSpec { name: "", extendsNames: [], methList: [], typeParams: [], extendsArgs: [] }
242+
}
243+
244+
// A concrete interface method stripped of its default body — so the class that
245+
// implements it supplies the code (materialized, see below) and no shared
246+
// `_default_impl` is emitted for a body that may reach into `this`.
247+
mapper stripBody(ms: MethodSpec) -> MethodSpec {
248+
return MethodSpec {
249+
isAsync: ms.isAsync, kind: ms.kind, name: ms.name, params: ms.params,
250+
retCtype: ms.retCtype, bodyTokens: [], topic: ms.topic,
251+
hasWhere: ms.hasWhere, whereTokens: [], fnDeps: ms.fnDeps
252+
}
253+
}
254+
229255
// The whole pass: rewrite classes' generic implements to concrete names and
230256
// return a Program whose ifaces = (non-generic originals) + (synthesized).
257+
//
258+
// Generic interface defaults are **materialized** into each implementing class:
259+
// an un-overridden default method is copied (already type-substituted) into the
260+
// class's own methList, so its body dispatches to sibling methods on `this`
261+
// (getProvider / source / findAll ...) through normal class-method codegen. The
262+
// synthesized interface then carries the method as abstract.
231263
mapper monomorphize(prog: Program) -> Program {
232264
let synth: IfaceSpec[] = []
233265
let newClasses: ClassSpec[] = []
@@ -236,6 +268,7 @@ mapper monomorphize(prog: Program) -> Program {
236268
while ci < classSpecLen(prog.classes) {
237269
let cs = classSpecGet(prog.classes, ci)
238270
let newImpl: String[] = []
271+
let matMeths: MethodSpec[] = [] // default methods materialized into this class
239272
let ii = 0
240273
while ii < stringArrLen(cs.implNames) {
241274
let ifn = stringArrGet(cs.implNames, ii)
@@ -252,19 +285,39 @@ mapper monomorphize(prog: Program) -> Program {
252285
k = k + 1
253286
}
254287
synth = synthIface(prog, ifn, arg, synth)
288+
// materialize un-overridden defaults from the top concrete interface
289+
// (its methList already flattens every extended interface's methods)
290+
let top = ifaceByName(synth, monoName(ifn, arg))
291+
let tm = 0
292+
while tm < methodSpecLen(top.methList) {
293+
let m = methodSpecGet(top.methList, tm)
294+
if tokenArrLen(m.bodyTokens) > 0 and countMethodName(cs, m.name) == 0
295+
and not matHasMethod(matMeths, m.name) {
296+
matMeths = appendMethodSpec(matMeths, m)
297+
}
298+
tm = tm + 1
299+
}
255300
} else {
256301
newImpl = appendString(newImpl, ifn)
257302
}
258303
ii = ii + 1
259304
}
305+
let finalMeths = cs.methList
306+
let mm = 0
307+
while mm < methodSpecLen(matMeths) {
308+
finalMeths = appendMethodSpec(finalMeths, methodSpecGet(matMeths, mm))
309+
mm = mm + 1
310+
}
260311
newClasses = appendClassSpec(newClasses, ClassSpec {
261-
name: cs.name, implNames: newImpl, depList: cs.depList, methList: cs.methList,
312+
name: cs.name, implNames: newImpl, depList: cs.depList, methList: finalMeths,
262313
stateFields: cs.stateFields, stateInit: cs.stateInit, implArgs: cs.implArgs
263314
})
264315
ci = ci + 1
265316
}
266317

267-
// final ifaces: keep non-generic originals, drop templates, add synthesized
318+
// final ifaces: keep non-generic originals, drop templates, add synthesized.
319+
// Synthesized interface methods are stripped to abstract — every implementing
320+
// class carries the body (its own override or a materialized default).
268321
let newIfaces: IfaceSpec[] = []
269322
let fi = 0
270323
while fi < ifaceSpecLen(prog.ifaces) {
@@ -274,7 +327,17 @@ mapper monomorphize(prog: Program) -> Program {
274327
}
275328
let si = 0
276329
while si < ifaceSpecLen(synth) {
277-
newIfaces = appendIfaceSpec(newIfaces, ifaceSpecGet(synth, si))
330+
let sIf = ifaceSpecGet(synth, si)
331+
let absMeths: MethodSpec[] = []
332+
let am = 0
333+
while am < methodSpecLen(sIf.methList) {
334+
absMeths = appendMethodSpec(absMeths, stripBody(methodSpecGet(sIf.methList, am)))
335+
am = am + 1
336+
}
337+
newIfaces = appendIfaceSpec(newIfaces, IfaceSpec {
338+
name: sIf.name, extendsNames: sIf.extendsNames, methList: absMeths,
339+
typeParams: [], extendsArgs: []
340+
})
278341
si = si + 1
279342
}
280343

compiler/impl/codegen/queryreify.xi

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -508,47 +508,61 @@ mapper genQueryStage(toks: Token[], p: Integer, recv: String, typ: String, ctx:
508508
return ExprRes { code: qStageAppendC(recv, stage, u), pos: q, xtyp: pfx + "qpair:" + elem + ":" + relem, owned: false }
509509
}
510510

511-
if fld == "collect" {
512-
if elem.startsWith2("qpair:") or elem.startsWith2("qgroup:") {
513-
diag_error(line, "xi-query: project joined/grouped rows with .map { ... } before .collect")
514-
}
511+
if fld == "using" {
512+
// bind a provider to the query so the terminals run against it, no DI.
513+
// Store the fat-pointer halves as opaque handles (see QueryPlan).
515514
let ae = genExpr(toks, p + 3, ctx)
516515
let q = ae.pos
517516
if toks.kindAt(q) == 101 { q = q + 1 }
518-
let ct = elem.xnameToCtype()
519-
let dec = jsonDecodeExpr(ctx.prog, ct, "xstd_json_at(__qr" + u + ", __qi" + u + ")")
520-
if string_len(dec) == 0 {
521-
diag_error(line, "xi-query: can't decode rows into element type '" + elem + "'")
522-
}
523-
let code = "({ xc_QueryProvider_t __qv" + u + " = " + ae.code + "; "
524-
+ "xc_Json_t __qr" + u + " = __qv" + u + ".vtable->run(__qv" + u + ".self, " + recv + "); "
525-
+ "xc_List_t __ql" + u + " = xstd_list_new(sizeof(" + ct + ")); "
526-
+ "for (xc_integer_t __qi" + u + " = 0; __qi" + u + " < xstd_json_length(__qr" + u + "); __qi" + u + "++) { "
527-
+ ct + " __qe" + u + " = " + dec + "; "
528-
+ "xstd_list_push(__ql" + u + ", (" + ct + "[]){ __qe" + u + " }); } __ql" + u + "; })"
529-
return ExprRes { code: code, pos: q, xtyp: "List_" + elem, owned: false }
517+
let code = "({ xc_QueryPlan_t __qp" + u + " = " + recv + "; xc_QueryProvider_t __qpp" + u + " = " + ae.code + "; "
518+
+ "__qp" + u + ".providerSelf = __qpp" + u + ".self; __qp" + u + ".providerVtable = (void*)__qpp" + u + ".vtable; __qp" + u + "; })"
519+
return ExprRes { code: code, pos: q, xtyp: typ, owned: false }
530520
}
531521

532-
if fld == "toList" {
533-
// run the query with no explicit provider. A list-rooted query uses an
534-
// ephemeral MemorySource over its inline rows; a source-rooted query uses
535-
// the module's bound QueryProvider (DI) — this is what lets a repository's
536-
// findAll().filter{...}.toList() work.
522+
if fld == "collect" or fld == "toList" or fld == "first" {
523+
// Terminals. `collect(p)` runs against the explicit provider `p`.
524+
// `collect()` / `toList()` / `first()` run against the query's bound
525+
// provider (set by `.using`); with none bound they fall back to the
526+
// module's DI-resolved provider (source-rooted) or an ephemeral
527+
// MemorySource (list-rooted). `first` yields the first row as `T?`.
537528
if elem.startsWith2("qpair:") or elem.startsWith2("qgroup:") {
538-
diag_error(line, "xi-query: project joined/grouped rows with .map { ... } before .toList")
529+
diag_error(line, "xi-query: project joined/grouped rows with .map { ... } before ." + fld)
539530
}
540-
let q = p + 2
541-
if toks.kindAt(q) == 100 { q = q + 1 if toks.kindAt(q) == 101 { q = q + 1 } } // ()
542531
let ct = elem.xnameToCtype()
532+
// an explicit provider arg (collect(p)) — otherwise use the bound/fallback
533+
let hasArg = false
534+
let argCode = ""
535+
let q = p + 2
536+
if toks.kindAt(q) == 100 {
537+
q = q + 1
538+
if toks.kindAt(q) != 101 {
539+
let ae = genExpr(toks, q, ctx)
540+
hasArg = true argCode = ae.code q = ae.pos
541+
}
542+
if toks.kindAt(q) == 101 { q = q + 1 }
543+
}
544+
let fallback = "xc_resolve_QueryProvider()"
545+
if local { fallback = "xc_MemorySource_as_QueryProvider(xc_new_MemorySource())" }
546+
let bound = "(xc_QueryProvider_t){ .self = __qp" + u + ".providerSelf, .vtable = (const xc_QueryProvider_vtable_t*)__qp" + u + ".providerVtable }"
547+
let pick = "(__qp" + u + ".providerVtable ? " + bound + " : (" + fallback + "))"
548+
if hasArg { pick = argCode }
549+
let head = "({ xc_QueryPlan_t __qp" + u + " = " + recv + "; "
550+
+ "xc_QueryProvider_t __qv" + u + " = " + pick + "; "
551+
+ "xc_Json_t __qr" + u + " = __qv" + u + ".vtable->run(__qv" + u + ".self, __qp" + u + "); "
552+
if fld == "first" {
553+
let dec0 = jsonDecodeExpr(ctx.prog, ct, "xstd_json_at(__qr" + u + ", 0)")
554+
if string_len(dec0) == 0 {
555+
diag_error(line, "xi-query: can't decode rows into element type '" + elem + "'")
556+
}
557+
let code = head + "xc_opt_" + elem + "_t __qo" + u + "; __qo" + u + ".has_value = 0; "
558+
+ "if (xstd_json_length(__qr" + u + ") > 0) { __qo" + u + ".has_value = 1; __qo" + u + ".value = " + dec0 + "; } __qo" + u + "; })"
559+
return ExprRes { code: code, pos: q, xtyp: "opt_" + elem, owned: false }
560+
}
543561
let dec = jsonDecodeExpr(ctx.prog, ct, "xstd_json_at(__qr" + u + ", __qi" + u + ")")
544562
if string_len(dec) == 0 {
545563
diag_error(line, "xi-query: can't decode rows into element type '" + elem + "'")
546564
}
547-
let providerC = "xc_resolve_QueryProvider()"
548-
if local { providerC = "xc_MemorySource_as_QueryProvider(xc_new_MemorySource())" }
549-
let code = "({ xc_QueryProvider_t __qv" + u + " = " + providerC + "; "
550-
+ "xc_Json_t __qr" + u + " = __qv" + u + ".vtable->run(__qv" + u + ".self, " + recv + "); "
551-
+ "xc_List_t __ql" + u + " = xstd_list_new(sizeof(" + ct + ")); "
565+
let code = head + "xc_List_t __ql" + u + " = xstd_list_new(sizeof(" + ct + ")); "
552566
+ "for (xc_integer_t __qi" + u + " = 0; __qi" + u + " < xstd_json_length(__qr" + u + "); __qi" + u + "++) { "
553567
+ ct + " __qe" + u + " = " + dec + "; "
554568
+ "xstd_list_push(__ql" + u + ", (" + ct + "[]){ __qe" + u + " }); } __ql" + u + "; })"
@@ -560,6 +574,6 @@ mapper genQueryStage(toks: Token[], p: Integer, recv: String, typ: String, ctx:
560574
return ExprRes { code: recv, pos: p + 2, xtyp: "QueryPlan", owned: false }
561575
}
562576

563-
diag_error(line, "xi-query: unknown stage '." + fld + "' — supported: filter, map, sortedBy, sortedByDescending, take, drop, concat, join, groupBy, collect, toList, plan")
577+
diag_error(line, "xi-query: unknown stage '." + fld + "' — supported: filter, map, sortedBy, sortedByDescending, take, drop, concat, join, groupBy, using, collect, toList, first, plan")
564578
return ExprRes { code: recv, pos: p + 2, xtyp: typ, owned: false }
565579
}

compiler/impl/driver/driver.xi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,10 @@ producer packLibrary(srcPath: String) -> Integer {
532532
}
533533

534534
// The toolchain version (kept in sync with the xi tool); printed by `xc version`.
535-
mapper xcVersion() -> String { return "0.0.99" }
535+
mapper xcVersion() -> String { return "0.1.0" }
536+
537+
// The release codename, shown alongside the version.
538+
mapper xcCodename() -> String { return "Berlin" }
536539

537540
// The value of a `--target <t>` / `--target=<t>` flag anywhere in args, or "".
538541
mapper argTarget(args: String[]) -> String {

compiler/impl/driver/xc_compiler.xi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class XcCompiler implements Compiler {
126126
}
127127
let srcPath = cliSource(args)
128128
if srcPath == "version" or srcPath == "--version" or srcPath == "-v" {
129-
system.stdout.writeln("xc " + xcVersion())
129+
system.stdout.writeln("xc " + xcVersion() + " \"" + xcCodename() + "\"")
130130
return 0
131131
}
132132
if srcPath == "--all" { return buildAll() }

compiler/loadtest.xi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ module LoadTest {
187187
id = "loadtest"
188188
name = "Xi Load Tester"
189189
description = "Load/perf testing for Xi projects: compile-stress, run-bench, and HTTP load."
190-
version = "0.0.99"
190+
version = "0.1.0"
191191
license = "Apache 2.0"
192192
includes = []
193193
excludes = []

compiler/repl/runner.xi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ mapper buildProgram(decls: String, stmts: String) -> String {
8888
}
8989

9090
// The toolchain version. Bump this when cutting a release (matches the tag).
91-
mapper xiVersion() -> String { return "0.0.99" }
91+
mapper xiVersion() -> String { return "0.1.0" }
92+
93+
// The release codename, shown alongside the version.
94+
mapper xiCodename() -> String { return "Berlin" }
9295

9396
// Directory part of a path (everything before the last '/'); "." if none.
9497
mapper dirOf(path: String) -> String {

compiler/repl/xi_repl.xi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class XiRepl implements Repl {
77
if args.len >= 2 {
88
let sub = args.data[1]
99
if sub == "version" or sub == "--version" or sub == "-v" {
10-
system.stdout.writeln("xi " + xiVersion())
10+
system.stdout.writeln("xi " + xiVersion() + " \"" + xiCodename() + "\"")
1111
return 0
1212
}
1313
if sub == "update" {

compiler/test.xi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module Test {
1414
id = "xt"
1515
name = "Xi Test Runner"
1616
description = "Compiles and runs *_test.xi files in test mode, reporting pass/fail."
17-
version = "0.0.99"
17+
version = "0.1.0"
1818
license = "Apache 2.0"
1919
includes = []
2020
excludes = []

compiler/xc.xi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ module Compile {
9292
id = "xc"
9393
name = "Xi Compiler"
9494
description = "The Xi language compiler — Xi source to C99 to native binaries."
95-
version = "0.0.99"
95+
version = "0.1.0"
9696
license = "Apache 2.0"
9797
includes = []
9898
excludes = []

0 commit comments

Comments
 (0)