STUDIO HAZE · topaz
Topaz source
Open by house policy. Topaz is our own language.
Topaz 5.20 · 56 files
compiler/crates/topaz_self_frontend/topaz/emitter/emit.tpz
Topaz · 102492 B · Original file
1
import emitter.rust_text { rustStr }2
import emitter.type_test { typeTest, childTypes, subtree, substitute, nominalIdentity }3
import emitter.builtins { BuiltinSpec, staticSpec, staticBuiltin, freeBuiltin, freeSpec, lispexIntrinsic }4
import emitter.ir { typeDeclaration,5
Table,6
opAt,7
capturesOf,8
fieldOf,9
labelName,10
children,11
child,12
optionalChild,13
refusal,14
spanOf,15
declarationSpan,16
rustName,17
decodeEscapes,18
}19
import emitter.repr { reprOf, reprAt, slotRepr, rustType, isCopyRepr, boxed, coerce, bare, sharedInit, sharedRead, sharedWrite, parameterGuard, variadicRepr }20
import stage1_types { Stage1Operation }21
import types { PreviewTypeAtom }23
// A name visible to the code being emitted.24
// local -> a Rust local `rust` of representation `repr`25
// shared -> a typed or boxed cell retained by closures and deferred actions26
// localfn -> a missing-aware local function cell, filled at its declaration27
// global -> a module-level cell `g.<rust>` (typed `Cell` or boxed `TopCell`)28
// fn -> a module-level function; `rust` keys `Ctx.fns`29
// namespace -> an imported module; `rust` is its identity30
export record Bind {31
name: string,32
kind: string,33
rust: string,34
repr: string,35
mutable: bool,36
}38
export record FnInfo {39
id: string,40
name: string,41
body: string,42
adapter: string,43
cell: string,44
paramNames: Array<string>,45
paramReprs: Array<string>,46
paramSpans: Array<string>,47
defaultIds: Array<string>,48
variadic: bool,49
retRepr: string,50
}52
export record ModInfo {53
identity: string,54
cell: string,55
binds: Array<Bind>,56
exports: Array<string>,57
generatedStd: bool,58
}60
export record Ctx {61
table: Table,62
scope: Array<Bind>,63
fns: Map<string, FnInfo>,64
mods: Map<string, ModInfo>,65
cellDecls: Array<string>,66
items: Array<string>,67
fnRet: string,68
inConst: bool,69
atModuleTop: bool,70
loops: Array<LoopFrame>,71
defers: Array<string>,72
}74
export record LoopFrame {75
id: string,76
label: string,77
repr: string,78
cleanup: int,79
}81
export record Code {82
rs: string,83
repr: string,84
}86
// --- scope ---------------------------------------------------------------88
export function has(list: Array<string>, value: string) -> bool {89
for item in list {90
if item == value {91
return true92
}93
}94
false95
}97
export function pushBind(ctx: Ctx, bind: Bind) -> () {98
let mut scope = ctx.scope99
scope.push(bind)100
}102
export function restoreScope(ctx: Ctx, mark: int) -> () {103
let mut scope = ctx.scope104
while scope.length > mark {105
let _ = scope.pop()106
}107
()108
}110
export function lookup(ctx: Ctx, name: string) -> Option<Bind> {111
let mut position = ctx.scope.length - 1112
while position >= 0 {113
let candidate = ctx.scope[position]114
if candidate.name == name {115
return Some(candidate)116
}117
position -= 1118
}119
None120
}122
// A fresh module-level cell `_c<n>` of the given representation.123
export function newCell(ctx: Ctx, repr: string) -> string {124
let name = "_c{ctx.cellDecls.length}"125
let mut decls = ctx.cellDecls126
if isCopyRepr(repr) {127
decls.push("{name}: std::cell::Cell<Option<{rustType(repr)}>>|std::cell::Cell::new(None)")128
} else {129
decls.push("{name}: TopCell|top_cell()")130
}131
name132
}134
function fnInfo(ctx: Ctx, key: string, op: Stage1Operation) -> Result<FnInfo, string> {135
match ctx.fns.get(key) {136
case Some(info) => Ok(info)137
case None => Err(refusal("ir function {key}", op))138
}139
}141
function modInfo(ctx: Ctx, identity: string, op: Stage1Operation) -> Result<ModInfo, string> {142
match ctx.mods.get(identity) {143
case Some(info) => Ok(info)144
case None => Err(refusal("module {identity}", op))145
}146
}148
export function modBind(info: ModInfo, name: string) -> Option<Bind> {149
for bind in info.binds {150
if bind.name == name {151
return Some(bind)152
}153
}154
None155
}157
// --- reads and writes of bindings ----------------------------------------159
export function readBind(ctx: Ctx, bind: Bind, span: string, op: Stage1Operation) -> Result<Code, string> {160
match bind.kind {161
case "localfn" => Ok(Code { rs: "top_cell_get(&{bind.rust}, {rustStr(bind.name)}, {span})?", repr: "value" })162
case "shared" => Ok(Code { rs: sharedRead(bind.rust, bind.repr), repr: bind.repr })163
case "local" => if isCopyRepr(bind.repr) {164
Ok(Code { rs: bind.rust, repr: bind.repr })165
} else {166
Ok(Code { rs: "{bind.rust}.clone()", repr: bind.repr })167
}168
case "global" => if isCopyRepr(bind.repr) {169
Ok(Code {170
rs: "__cell_get(&g.{bind.rust}, {rustStr(bind.name)}, {span})?",171
repr: bind.repr,172
})173
} else {174
Ok(Code {175
rs: "top_cell_get(&g.{bind.rust}, {rustStr(bind.name)}, {span})?",176
repr: "value",177
})178
}179
case "fn" => {180
let info = fnInfo(ctx, bind.rust, op)?181
Ok(Code {182
rs: "top_cell_get(&g.{info.cell}, {rustStr(bind.name)}, {span})?",183
repr: "value",184
})185
}186
case "namespace" => {187
let info = modInfo(ctx, bind.rust, op)?188
Ok(Code {189
rs: "top_cell_get(&g.{info.cell}, {rustStr(bind.name)}, {span})?",190
repr: "value",191
})192
}193
case _ => Err(refusal("binding {bind.kind}", op))194
}195
}197
export function writeBind(bind: Bind, code: string, op: Stage1Operation) -> Result<string, string> {198
match bind.kind {199
case "shared" => Ok(sharedWrite(bind.rust, code, bind.repr))200
case "local" => Ok("{bind.rust} = {bare(code)};")201
case "global" => if isCopyRepr(bind.repr) {202
Ok("g.{bind.rust}.set(Some({bare(code)}));")203
} else {204
Ok("top_cell_set(&g.{bind.rust}, {bare(code)});")205
}206
case _ => Err(refusal("assignment to {bind.kind} binding", op))207
}208
}210
// The code in the operation's own representation when it carries a type.211
function finish(code: Code, op: Stage1Operation) -> Result<Code, string> {212
let target = reprOf(op.semanticTypeAtoms)213
if target == "none" || target == code.repr || code.repr == "never" {214
Ok(code)215
} else {216
Ok(Code { rs: into(code, target, op)?, repr: target })217
}218
}220
// The code converted to `target` (explicit box/unbox boundary).221
export function into(code: Code, target: string, op: Stage1Operation) -> Result<string, string> {222
if code.repr == "never" {223
return Ok(code.rs)224
}225
match coerce(code.rs, code.repr, target, spanOf(op)) {226
case Ok(converted) => Ok(converted)227
case Err(message) => Err("{message} at {op.module}:{op.lo}-{op.hi}")228
}229
}231
function boxCode(code: Code) -> string {232
if code.repr == "never" || code.repr == "none" {233
bare(code.rs)234
} else {235
bare(boxed(code.rs, code.repr))236
}237
}239
// --- expressions ----------------------------------------------------------241
export function emitExpr(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {242
match op.kind {243
case "expression/integer" => match toInt(op.detail) {244
case Some(value) => finish(Code { rs: "{value}i64", repr: "i64" }, op)245
case None => Err(refusal("literal integer {op.detail}", op))246
}247
case "expression/float" => finish(Code { rs: floatLiteral(op)?, repr: "f64" }, op)248
case "expression/boolean" => if op.detail == "true" || op.detail == "false" {249
finish(Code { rs: op.detail, repr: "bool" }, op)250
} else {251
Err(refusal("literal boolean {op.detail}", op))252
}253
case "expression/unit" => finish(Code { rs: "()", repr: "unit" }, op)254
case "expression/null" => finish(Code { rs: "Value::Null", repr: "value" }, op)255
case "expression/string" => finish(emitString(ctx, op)?, op)256
case "expression/identifier" => emitIdentifier(ctx, op)257
case "expression/placeholder" => match lookup(ctx, "_") {258
case Some(bind) => readBind(ctx, bind, spanOf(op), op)259
case None => Err(refusal("unbound pipe placeholder", op))260
}261
case "expression/parenthesized" => finish(emitExpr(ctx, child(ctx.table, op, "operand")?)?, op)262
case "expression/binary" => finish(emitBinary(ctx, op)?, op)263
case "expression/unary" => finish(emitUnary(ctx, op)?, op)264
case "expression/array" => finish(emitArray(ctx, op)?, op)265
case "expression/map" => finish(emitMap(ctx, op)?, op)266
case "expression/set" => {267
let mut values: Array<string> = []268
for element in children(ctx.table, op, "elements")? {269
values.push(boxCode(emitExpr(ctx, element)?))270
}271
finish(Code { rs: "builtin_set_of(vec![{values.join(", ")}], {spanOf(op)})?", repr: "value" }, op)272
}273
case "expression/record-literal" => finish(emitRecord(ctx, op)?, op)274
case "expression/record-update" => finish(emitRecordUpdate(ctx, op)?, op)275
case "expression/member" => finish(emitMember(ctx, op)?, op)276
case "expression/index" => finish(emitIndex(ctx, op)?, op)277
case "expression/optional-member" => finish(emitOptionalMember(ctx, op)?, op)278
case "expression/call" => finish(emitCall(ctx, op)?, op)279
case "expression/pipeline" => finish(emitPipe(ctx, op)?, op)280
case "expression/concurrent" => finish(emitConcurrent(ctx, op)?, op)281
case "expression/lambda" => finish(emitLambda(ctx, op)?, op)282
case "expression/compose" => {283
let lhs = boxCode(emitExpr(ctx, child(ctx.table, op, "left")?)?)284
let rhs = boxCode(emitExpr(ctx, child(ctx.table, op, "right")?)?)285
finish(Code { rs: "Value::Composed(Rc::new(({lhs}, {rhs})))", repr: "value" }, op)286
}287
case "expression/if" => emitIf(ctx, op)288
case "expression/match" => emitMatch(ctx, op)289
case "expression/loop" => emitLoop(ctx, op)290
case "expression/for" => emitFor(ctx, op, true)291
case "expression/range" => emitRange(ctx, op)292
case "expression/comprehension" => emitComprehension(ctx, op)293
case "expression/result-propagation" => finish(emitTry(ctx, op)?, op)294
case "expression/block" => finish(emitBlock(ctx, op)?, op)295
case "expression/block-expression" => finish(emitBlock(ctx, op)?, op)296
case _ => Err(refusal("kind {op.kind}", op))297
}298
}300
function floatLiteral(op: Stage1Operation) -> Result<string, string> {301
let text = op.detail302
if text == "" {303
return Err(refusal("literal float", op))304
}305
for scalar in text.scalars() {306
if !"0123456789._eE+-".contains(scalar) {307
return Err(refusal("literal float {text}", op))308
}309
}310
match Math.parseFloat(text.replace("_", "")) {311
case Ok(value) => if Math.isFinite(value) {312
Ok("{text}_f64")313
} else {314
Ok("f64::INFINITY")315
}316
case Err(_) => Err(refusal("literal float {text}", op))317
}318
}320
function emitIdentifier(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {321
if op.referenceIdentity.startsWith("builtin::") {322
if op.referenceIdentity == "builtin::None" {323
return finish(Code { rs: "Value::None", repr: "value" }, op)324
}325
let tag = freeBuiltin(op.detail)326
if tag != "" { return finish(builtinCode(tag), op) }327
let intrinsic = generatedIntrinsic(ctx, op)328
if intrinsic != "" { return finish(builtinCode(intrinsic), op) }329
return Err(refusal("builtin value {op.detail}", op))330
}331
match lookup(ctx, op.detail) {332
case Some(bind) => finish(readBind(ctx, bind, spanOf(op), op)?, op)333
case None => Err(refusal("unresolved identifier {op.detail}", op))334
}335
}337
function emitString(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {338
let pieces = op.detail.split("|")339
let tag = if pieces.length == 2 {340
pieces[0]341
} else {342
""343
}344
let mut texts: Array<string> = []345
let mut current = ""346
let mut values: Array<string> = []347
let mut steps: Array<string> = []348
let mut position = 0349
for id in op.operands {350
let part = opAt(ctx.table, id)?351
let field = fieldOf(op.operandLabels[position])352
if field == "parts" && part.kind == "expression/string-text" {353
let text = match decodeEscapes(part.detail) {354
case Some(decoded) => decoded355
case None => return Err(refusal("string escape", part))356
}357
current = current + text358
steps.push("__s.push_str({rustStr(text)});")359
} else if field == "value" {360
let value = boxCode(emitExpr(ctx, part)?)361
texts.push(current)362
current = ""363
values.push(value)364
steps.push("__s.push_str(&render(&({value})));")365
} else {366
return Err(refusal("shape string part {field}", op))367
}368
position += 1369
}370
texts.push(current)371
if tag != "" {372
let mut parts: Array<string> = []373
for text in texts {374
parts.push("{rustStr(text)}.to_string()")375
}376
return Ok(Code {377
rs: "make_template({rustStr(tag)}.to_string(), vec![{parts.join(", ")}], vec![{values.join(", ")}])",378
repr: "value",379
})380
}381
if values.length == 0 {382
Ok(Code { rs: "Value::str({rustStr(texts.join(""))})", repr: "value" })383
} else {384
Ok(Code {385
rs: "\{ let mut __s = String::new(); {steps.join(" ")} Value::str(__s) \}",386
repr: "value",387
})388
}389
}391
function binaryName(detail: string) -> string {392
match detail {393
case "add" => "Add"394
case "subtract" => "Sub"395
case "multiply" => "Mul"396
case "divide" => "Div"397
case "remainder" => "Rem"398
case "power" => "Pow"399
case "less-than" => "Lt"400
case "less-or-equal" => "Le"401
case "greater-than" => "Gt"402
case "greater-or-equal" => "Ge"403
case "equal" => "Eq"404
case "not-equal" => "Ne"405
case "in" => "In"406
case "and" => "And"407
case "or" => "Or"408
case "coalesce" => "Coalesce"409
case _ => ""410
}411
}413
function intLeaf(detail: string) -> string {414
match detail {415
case "add" => "int_add"416
case "subtract" => "int_sub"417
case "multiply" => "int_mul"418
case "divide" => "int_div"419
case "remainder" => "int_rem"420
case "power" => "int_pow"421
case _ => ""422
}423
}425
function isOrdering(detail: string) -> bool {426
detail == "less-than" || detail == "less-or-equal" || detail == "greater-than" ||427
detail == "greater-or-equal"428
}430
// A faulting leaf; inside a top-level `const` the fault is reframed as the431
// constant-expression error the interpreter's const pass reports.432
function checked(ctx: Ctx, leaf: string, span: string) -> string {433
if ctx.inConst {434
"__cg({leaf}, {span})?"435
} else {436
"{leaf}?"437
}438
}440
// The arithmetic/comparison core shared by binary expressions and compound441
// assignment. Typed rows of the representation table use the checked scalar442
// leaves of `topaz_value`; every other pairing goes through the boxed leaf.443
function arithmetic(444
ctx: Ctx,445
detail: string,446
left: Code,447
right: Code,448
span: string,449
op: Stage1Operation,450
) -> Result<Code, string> {451
let name = binaryName(detail)452
if name == "" || name == "And" || name == "Or" || name == "Coalesce" {453
return Err(refusal("operator {detail}", op))454
}455
if left.repr == "i64" && right.repr == "i64" {456
let leaf = intLeaf(detail)457
if leaf != "" {458
return Ok(Code { rs: checked(ctx, "{leaf}({left.rs}, {right.rs}, {span})", span), repr: "i64" })459
}460
if isOrdering(detail) {461
return Ok(Code { rs: "int_cmp(BinaryOp::{name}, {left.rs}, {right.rs})", repr: "bool" })462
}463
}464
if left.repr == "f64" && right.repr == "f64" {465
if detail == "add" || detail == "subtract" || detail == "multiply" || detail == "divide" ||466
detail == "power" {467
return Ok(Code { rs: "float_arith(BinaryOp::{name}, {left.rs}, {right.rs})", repr: "f64" })468
}469
if isOrdering(detail) {470
return Ok(Code { rs: "float_cmp(BinaryOp::{name}, {left.rs}, {right.rs})", repr: "bool" })471
}472
}473
if left.repr == right.repr && (left.repr == "i64" || left.repr == "f64" || left.repr == "bool") {474
if detail == "equal" {475
return Ok(Code { rs: "(({left.rs}) == ({right.rs}))", repr: "bool" })476
}477
if detail == "not-equal" {478
return Ok(Code { rs: "(({left.rs}) != ({right.rs}))", repr: "bool" })479
}480
}481
Ok(Code {482
rs: checked(ctx, "binary_value(BinaryOp::{name}, {boxCode(left)}, {boxCode(right)}, {span})", span),483
repr: "value",484
})485
}487
function emitBinary(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {488
let left = emitExpr(ctx, child(ctx.table, op, "left")?)?489
let right = emitExpr(ctx, child(ctx.table, op, "right")?)?490
let span = spanOf(op)491
if op.detail == "and" || op.detail == "or" {492
if left.repr == "bool" && right.repr == "bool" {493
if op.detail == "and" {494
return Ok(Code { rs: "(if {bare(left.rs)} \{ {bare(right.rs)} \} else \{ false \})", repr: "bool" })495
}496
return Ok(Code { rs: "(if {bare(left.rs)} \{ true \} else \{ {bare(right.rs)} \})", repr: "bool" })497
}498
}499
if op.detail == "and" || op.detail == "or" || op.detail == "coalesce" {500
let name = binaryName(op.detail)501
return Ok(Code {502
rs: "(match short_circuit_lhs({boxCode(left)}, BinaryOp::{name}, {span})? \{ Some(__v) => __v, None => {boxCode(right)} \})",503
repr: "value",504
})505
}506
arithmetic(ctx, op.detail, left, right, span, op)507
}509
function emitUnary(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {510
let operand = emitExpr(ctx, child(ctx.table, op, "operand")?)?511
let span = spanOf(op)512
match op.detail {513
case "minus" => if operand.repr == "i64" {514
Ok(Code { rs: checked(ctx, "int_neg({operand.rs}, {span})", span), repr: "i64" })515
} else if operand.repr == "f64" {516
Ok(Code { rs: "(-{operand.rs})", repr: "f64" })517
} else {518
Ok(Code {519
rs: checked(ctx, "unary_value(UnaryOp::Minus, {boxCode(operand)}, {span})", span),520
repr: "value",521
})522
}523
case "not" => if operand.repr == "bool" {524
Ok(Code { rs: "(!{operand.rs})", repr: "bool" })525
} else {526
Ok(Code {527
rs: checked(ctx, "unary_value(UnaryOp::Not, {boxCode(operand)}, {span})", span),528
repr: "value",529
})530
}531
case "plus" => if operand.repr == "i64" || operand.repr == "f64" {532
Ok(operand)533
} else {534
Ok(Code {535
rs: checked(ctx, "unary_value(UnaryOp::Plus, {boxCode(operand)}, {span})", span),536
repr: "value",537
})538
}539
case _ => Err(refusal("operator unary {op.detail}", op))540
}541
}543
function emitArray(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {544
let mut values: Array<string> = []545
let mut steps: Array<string> = []546
let mut spread = false547
let mut position = 0548
for id in op.operands {549
let element = opAt(ctx.table, id)?550
let label = op.operandLabels[position]551
let value = boxCode(emitExpr(ctx, element)?)552
if label.contains("/spread[") {553
spread = true554
steps.push("array_spread_extend(&mut __acc, {value}, {spanOf(op)})?;")555
} else if fieldOf(label) == "elements" {556
values.push(value)557
steps.push("__acc.push({value});")558
} else {559
return Err(refusal("shape array element {label}", op))560
}561
position += 1562
}563
if spread {564
Ok(Code {565
rs: "\{ let mut __acc = Vec::new(); {steps.join(" ")} Value::array(__acc) \}",566
repr: "value",567
})568
} else {569
Ok(Code { rs: "Value::array(vec![{values.join(", ")}])", repr: "value" })570
}571
}573
function emitMap(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {574
let mut pairs: Array<string> = []575
let mut key = ""576
let mut position = 0577
for id in op.operands {578
let part = opAt(ctx.table, id)?579
let field = fieldOf(op.operandLabels[position])580
let value = boxCode(emitExpr(ctx, part)?)581
if field == "key" {582
key = value583
} else if field == "value" {584
pairs.push("({key}, {value})")585
} else {586
return Err(refusal("shape map entry {field}", op))587
}588
position += 1589
}590
Ok(Code { rs: "builtin_map_of(vec![{pairs.join(", ")}], {spanOf(op)})?", repr: "value" })591
}593
function nominalHead(ctx: Ctx, op: Stage1Operation) -> Result<Option<Stage1Operation>, string> {594
if op.kind != "expression/identifier" { return Ok(None) }595
match lookup(ctx, op.detail) {596
case Some(_) => return Ok(None)597
case None => ()598
}599
typeDeclaration(ctx.table, op.id, op.detail)600
}602
function methodIdentity(ctx: Ctx, decl: Stage1Operation) -> string {603
for op in ctx.table.ops {604
if op.module == decl.module && op.kind == "implementation" && op.detail == decl.typeDefinition[0].name {605
return "Some({rustStr(decl.typeDefinition[0].identity)})"606
}607
}608
"None::<&str>"609
}611
function emitEnum(ctx: Ctx, op: Stage1Operation, decl: Stage1Operation, name: string, args: Array<Stage1Operation>) -> Result<Code, string> {612
let root = decl.typeDefinition[0]613
for index in childTypes(decl.typeDefinition, 0) {614
let variant = decl.typeDefinition[index]615
if variant.field != "variants" || variant.name != name { continue }616
if childTypes(decl.typeDefinition, index).length != args.length { return Err(refusal("enum payload arity", op)) }617
let mut values: Array<string> = []618
for arg in args { values.push(boxCode(emitExpr(ctx, arg)?)) }619
let method = methodIdentity(ctx, decl)620
return Ok(Code { rs: "Value::Enum \{ enum_id: Rc::from({rustStr(root.name)}), declaration_identity: Some(Rc::from({rustStr(root.identity)})), method_identity: ({method}).map(Rc::from), variant: Rc::from({rustStr(name)}), variant_index: {variant.index}, payloads: Rc::from(vec![{values.join(", ")}]) \}", repr: "value" })621
}622
Err(refusal("enum variant {name}", op))623
}625
// Defaults belong to their declaration environment, even when the construction626
// occurs in a function that shadows a module binding.627
function defaultContext(ctx: Ctx, decl: Stage1Operation) -> Ctx {628
let mut parent = decl.parentId629
let mut moduleLevel = false630
for op in ctx.table.ops {631
if op.id == parent && op.kind == "export" { parent = op.parentId }632
}633
for op in ctx.table.ops {634
if op.id == parent && op.parentId == "" { moduleLevel = true }635
}636
if !moduleLevel { return ctx }637
match ctx.mods.get(decl.module) {638
case Some(info) => Ctx { ...ctx, scope: info.binds }639
case None => ctx640
}641
}643
function recordDefault(ctx: Ctx, decl: Stage1Operation, ordinal: int) -> Result<Option<Stage1Operation>, string> {644
let mut index = 0645
for label in decl.operandLabels {646
if fieldOf(label) == "default" && label.contains("/fields:{ordinal}/") {647
return Ok(Some(opAt(ctx.table, decl.operands[index])?))648
}649
index += 1650
}651
Ok(None)652
}654
function emitRecordUpdate(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {655
let base = child(ctx.table, op, "base")?656
let declaration = nominalHead(ctx, base)?657
let mut lines: Array<string> = []658
let mut names: Array<string> = []659
let mut fields: Array<string> = []660
let mut spread = false661
match declaration {662
case Some(decl) => if decl.kind == "record" {663
match optionalChild(ctx.table, op, "spread")? {664
case Some(seed) => {665
let mut required: Array<string> = []666
for atom in decl.typeDefinition { if atom.parent == 0 && atom.field == "fields" { required.push(rustStr(atom.edgeName)) } }667
let root = decl.typeDefinition[0]668
lines.push("let __seed = nominal_spread_base_required({boxCode(emitExpr(ctx, seed)?)}, {rustStr(root.name)}, Some({rustStr(root.identity)}), &[{required.join(", ")}], {spanOf(op)})?;")669
spread = true670
}671
case None => ()672
}673
}674
case None => lines.push("let __base = record_update_base({boxCode(emitExpr(ctx, base)?)}, {spanOf(op)})?;")675
}676
let mut index = 0677
for label in op.operandLabels {678
if fieldOf(label) == "value" && label.startsWith("field-initializer[") {679
let name = labelName(label)680
let slot = "__field{names.length}"681
lines.push("let {slot} = {boxCode(emitExpr(ctx, opAt(ctx.table, op.operands[index])?)?)};")682
names.push(name)683
fields.push(slot)684
}685
index += 1686
}687
let mut pairs: Array<string> = []688
match declaration {689
case Some(decl) => {690
let root = decl.typeDefinition[0]691
for atom in decl.typeDefinition {692
if atom.parent != 0 || atom.field != "fields" { continue }693
let mut value = ""694
let mut slot = 0695
for name in names {696
if name == atom.edgeName { value = "{fields[slot]}.clone()" }697
slot += 1698
}699
if value == "" && spread {700
value = "nominal_record_field_required(&__seed, {rustStr(root.name)}, {rustStr(atom.edgeName)}, {spanOf(op)})?"701
}702
if value == "" {703
match recordDefault(ctx, decl, atom.index)? {704
case Some(default) => {705
value = "__default{atom.index}"706
lines.push("let {value} = {boxCode(emitExpr(defaultContext(ctx, decl), default)?) };")707
}708
case None => return Err(refusal("missing record field {root.name}.{atom.edgeName}", op))709
}710
}711
pairs.push("(Rc::from({rustStr(atom.edgeName)}), {value})")712
}713
lines.push("Value::nominal_record_with_identities({rustStr(root.name)}, {rustStr(root.identity)}, {methodIdentity(ctx, decl)}, vec![{pairs.join(", ")}])")714
}715
case None => {716
let mut slot = 0717
for name in names { pairs.push("({rustStr(name)}.to_string(), {fields[slot]})"); slot += 1 }718
lines.push("record_update_merge(__base, vec![{pairs.join(", ")}], {spanOf(op)})?")719
}720
}721
Ok(Code { rs: "\{ {lines.join(" ")} \}", repr: "value" })722
}724
function jsonSchema(ctx: Ctx, origin: Stage1Operation, atoms: Array<PreviewTypeAtom>, index: int, active: Array<string>) -> Result<string, string> {725
let atom = atoms[index]726
let subs = childTypes(atoms, index)727
if atom.kind == "primitive" {728
let name = match atom.name {729
case "int" => "Int"730
case "string" => "Str"731
case "bool" => "Bool"732
case "unit" => "Unit"733
case "null" => "Null"734
case _ => return Err(refusal("JSON schema {atom.name}", origin))735
}736
return Ok("Schema::{name}")737
}738
if atom.kind == "json-value" { return Ok("Schema::Json") }739
if atom.kind == "constructor" && has(["Array", "Option", "Map"], atom.name) {740
let slot = if atom.name == "Map" { 1 } else { 0 }741
if subs.length <= slot { return Err(refusal("JSON schema argument", origin)) }742
return Ok("Schema::{atom.name}(Rc::new({jsonSchema(ctx, origin, atoms, subs[slot], active)?}))")743
}744
if atom.kind == "record" {745
let mut fields: Array<string> = []746
for sub in subs { fields.push("(Rc::from({rustStr(atoms[sub].edgeName)}), {jsonSchema(ctx, origin, atoms, sub, active)?}, None)") }747
return Ok("Schema::StructRecord \{ fields: Rc::from(vec![{fields.join(", ")}]) \}")748
}749
if has(["enum", "newtype", "nominal-record"], atom.kind) {750
let identity = nominalIdentity(atom)751
let schemaKey = JSON.stringify(subtree(atoms, index))?752
if has(active, schemaKey) { return Err(refusal("recursive JSON schema {identity}", origin)) }753
let mut next: Array<string> = []754
for key in active { next.push(key) }755
next.push(schemaKey)756
let declaration = match ctx.table.nominals.get(identity) {757
case Some(found) => found758
case None => return Err(refusal("JSON nominal {identity}", origin))759
}760
let mut params: Map<string, Array<PreviewTypeAtom>> = Map.new()761
let mut ordinal = 0762
for param in declaration {763
if param.parent == 0 && param.field == "typeParameters" {764
if ordinal >= subs.length { return Err(refusal("JSON nominal arity", origin)) }765
params.insert(param.name, subtree(atoms, subs[ordinal]))766
ordinal += 1767
}768
}769
let definition = substitute(declaration, params)770
let mut fields: Array<string> = []771
for sub in childTypes(definition, 0) {772
let member = definition[sub]773
if member.field == "base" {774
return Ok("Schema::Newtype \{ newtype_id: Rc::from({rustStr(declaration[0].name)}), declaration_identity: Some(Rc::from({rustStr(identity)})), base: Rc::new({jsonSchema(ctx, origin, definition, sub, next)?}) \}")775
}776
if member.field == "fields" {777
let mut default = "None"778
for decl in ctx.table.ops {779
if decl.typeDefinition.length > 0 && decl.typeDefinition[0].identity == identity {780
match recordDefault(ctx, decl, member.index)? {781
case Some(value) => if schemaConstant(ctx, value)? { default = "Some({boxCode(emitExpr(defaultContext(ctx, decl), value)?)})" }782
case None => ()783
}784
}785
}786
fields.push("(Rc::from({rustStr(member.edgeName)}), {jsonSchema(ctx, origin, definition, sub, next)?}, {default})")787
}788
if member.field == "variants" {789
let mut payloads: Array<string> = []790
for payload in childTypes(definition, sub) { payloads.push(jsonSchema(ctx, origin, definition, payload, next)?) }791
fields.push("(Rc::from({rustStr(member.name)}), {member.index}u32, Rc::from(vec![{payloads.join(", ")}]))")792
}793
}794
if atom.kind == "enum" {795
return Ok("Schema::Enum \{ enum_id: Rc::from({rustStr(declaration[0].name)}), declaration_identity: Some(Rc::from({rustStr(identity)})), variants: Rc::from(vec![{fields.join(", ")}]) \}")796
}797
return Ok("Schema::Record \{ record_id: Rc::from({rustStr(declaration[0].name)}), declaration_identity: Some(Rc::from({rustStr(identity)})), fields: Rc::from(vec![{fields.join(", ")}]) \}")798
}799
Err(refusal("JSON schema {atom.kind}/{atom.name}", origin))800
}802
function schemaConstant(ctx: Ctx, op: Stage1Operation) -> Result<bool, string> {803
if !has(["expression/integer", "expression/float", "expression/boolean", "expression/unit", "expression/null", "expression/string", "expression/string-text", "expression/unary", "expression/binary", "expression/parenthesized"], op.kind) { return Ok(false) }804
for id in op.operands { if !schemaConstant(ctx, opAt(ctx.table, id)?)? { return Ok(false) } }805
Ok(true)806
}808
function emitRecord(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {809
let mut pairs: Array<string> = []810
let mut position = 0811
for id in op.operands {812
let label = op.operandLabels[position]813
let name = labelName(label)814
if fieldOf(label) != "value" || !label.startsWith("field-initializer[") || name == "" {815
return Err(refusal("shape record field {label}", op))816
}817
let value = boxCode(emitExpr(ctx, opAt(ctx.table, id)?)?)818
pairs.push("({rustStr(name)}.to_string(), {value})")819
position += 1820
}821
Ok(Code { rs: "Value::record([{pairs.join(", ")}])", repr: "value" })822
}824
// The namespace binding an operation names, if it is an identifier bound to825
// an imported module.826
function namespaceOf(ctx: Ctx, op: Stage1Operation) -> Option<Bind> {827
if op.kind != "expression/identifier" {828
return None829
}830
match lookup(ctx, op.detail) {831
case Some(bind) => if bind.kind == "namespace" {832
Some(bind)833
} else {834
None835
}836
case None => None837
}838
}840
// A runtime operation named by a generated std.lispex module, or "" when the841
// reference is not one (the Rust emitter's `lispex_intrinsic_kind`).842
function generatedIntrinsic(ctx: Ctx, op: Stage1Operation) -> string {843
match ctx.mods.get(op.module) {844
case Some(info) => if info.generatedStd { lispexIntrinsic(op.module, op.detail) } else { "" }845
case None => ""846
}847
}849
function builtinCode(tag: string) -> Code {850
Code { rs: "Value::Builtin \{ kind: Builtin::{tag}, recv: None \}", repr: "value" }851
}853
function emitIndex(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {854
let object = boxCode(emitExpr(ctx, child(ctx.table, op, "object")?)?)855
let index = boxCode(emitExpr(ctx, child(ctx.table, op, "index")?)?)856
Ok(Code { rs: "index_value({object}, {index}, {spanOf(op)})?", repr: "value" })857
}859
function memberBody(ctx: Ctx, op: Stage1Operation, object: Stage1Operation, span: string) -> Result<string, string> {860
let field = rustStr(op.detail)861
// Field acquisition precedes method binding. The shared catalog owns which862
// receivers admit a method and whether acquiring it requires a mutable root.863
let mut cursor = object864
while cursor.kind == "expression/member" || cursor.kind == "expression/index" || cursor.kind == "expression/optional-member" {865
cursor = child(ctx.table, cursor, "object")?866
}867
let mut mutable = true868
if cursor.kind == "expression/identifier" {869
match lookup(ctx, cursor.detail) {870
case Some(bind) => { mutable = bind.mutable }871
case None => ()872
}873
}874
let guard = if mutable { "" } else {875
"if receiver_builtin(&__recv, {field}).map(|entry| entry.mutates).unwrap_or(false) \{ return Err(fault(codes::GUARD_IMMUTABLE, {rustStr("`{cursor.detail}` is not `let mut`; in-place collection mutation requires a mutable binding (§9)")}, {span})); \}"876
}877
Ok("match member_value(&__recv, {field}, {span})? \{ Some(__field) => __field, None => \{ {guard} bind_receiver_builtin(__recv, {field}, {span})? \} \}")878
}880
function emitMember(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {881
let object = child(ctx.table, op, "object")?882
match nominalHead(ctx, object)? {883
case Some(decl) => if decl.kind == "enum" { return emitEnum(ctx, op, decl, op.detail, []) }884
case None => ()885
}886
if object.referenceIdentity.startsWith("builtin::") {887
if object.detail == "RoundingMode" {888
for variant in ["Down", "Up", "TowardZero", "AwayFromZero", "HalfUp", "HalfEven"] {889
if op.detail == variant { return Ok(Code { rs: "rounding_mode_value(RoundingMode::{variant})", repr: "value" }) }890
}891
}892
let tag = staticBuiltin(object.detail, op.detail)893
if tag != "" { return Ok(builtinCode(tag)) }894
return Err(refusal("type-directed member {object.detail}.{op.detail}", op))895
}896
let value = boxCode(emitExpr(ctx, object)?)897
match namespaceOf(ctx, object) {898
case Some(namespace) => return Ok(Code { rs: "member_value_required(&({value}), {rustStr(op.detail)}, {spanOf(op)})?", repr: "value" })899
case None => ()900
}901
let body = memberBody(ctx, op, object, spanOf(op))?902
Ok(Code { rs: "\{ let __recv = {value}; {body} \}", repr: "value" })903
}905
function optionalBody(value: string, body: string) -> string {906
"\{ let __object = {value}; match __object \{ Value::None => Value::None, Value::Null => Value::Null, Value::Some(__inner) => \{ let __recv = (*__inner).clone(); wrap_optional({body}) \}, __recv => \{ {body} \} \} \}"907
}909
function emitOptionalMember(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {910
let object = child(ctx.table, op, "object")?911
let value = boxCode(emitExpr(ctx, object)?)912
Ok(Code { rs: optionalBody(value, memberBody(ctx, op, object, spanOf(op))?), repr: "value" })913
}915
// The plan retains argument order. Named and spread AST wrappers are encoded916
// in operand labels, so their leaves are not ordinary `arguments` children.917
function callArguments(ctx: Ctx, op: Stage1Operation) -> Result<Array<Stage1Operation>, string> {918
let mut found: Array<Stage1Operation> = []919
let mut position = 0920
for id in op.operands {921
let label = op.operandLabels[position]922
if label.startsWith("arguments:") || label.contains("/arguments:") {923
found.push(opAt(ctx.table, id)?)924
}925
position += 1926
}927
if found.length != op.callArguments.length { return Err(refusal("shape call arguments", op)) }928
Ok(found)929
}931
function valueCall(ctx: Ctx, op: Stage1Operation, callee: string, args: Array<Stage1Operation>) -> Result<string, string> {932
valueCallPrefix(ctx, op, callee, args, [])933
}935
function valueCallPrefix(ctx: Ctx, op: Stage1Operation, callee: string, args: Array<Stage1Operation>, prefix: Array<string>) -> Result<string, string> {936
let mut steps: Array<string> = ["let __callee = {callee};"]937
let mut positional: Array<string> = prefix938
let mut named: Array<string> = []939
let mut hasSpread = false940
for row in op.callArguments { if row.startsWith("spread|") { hasSpread = true } }941
if hasSpread { steps.push("let mut __tail = Vec::new();") }942
let mut spreadSpan = spanOf(op)943
let mut spread = false944
let mut sawNamed = false945
let mut position = 0946
for arg in args {947
let row = op.callArguments[position].split("|")948
let value = boxCode(emitExpr(ctx, arg)?)949
steps.push("let __a{position} = {value};")950
if row[0] == "named" {951
named.push("({rustStr(row[1])}.to_string(), __a{position})")952
sawNamed = true953
} else if sawNamed && row[0] == "positional" {954
steps.push("return Err(fault(codes::GUARD_ARITY, \"positional arguments must precede named arguments (§5)\", {spanOf(op)}));")955
} else if row[0] == "spread" {956
if !spread { spreadSpan = spanOf(arg) }957
spread = true958
steps.push("call_spread_extend(&mut __tail, __a{position}, {spanOf(arg)})?;")959
} else if spread {960
steps.push("__tail.push(__a{position});")961
} else {962
positional.push("__a{position}")963
}964
position += 1965
}966
let span = spanOf(op)967
let call = if spread {968
"call_value_spread_named(SpreadNamedCall::new(__callee, vec![{positional.join(", ")}], Value::array(__tail), vec![{named.join(", ")}], {span}, {spreadSpan}), cx.clone()).await?"969
} else if named.length > 0 {970
"call_value_named(__callee, vec![{positional.join(", ")}], vec![{named.join(", ")}], cx.clone(), {span}).await?"971
} else {972
"call_value(__callee, vec![{positional.join(", ")}], cx.clone(), {span}).await?"973
}974
Ok("\{ {steps.join(" ")} {call} \}")975
}977
function emitStaticCall(ctx: Ctx, op: Stage1Operation, spec: BuiltinSpec, args: Array<Stage1Operation>) -> Result<Code, string> {978
let mut steps: Array<string> = []979
let mut slots: Array<string> = []980
let mut index = 0981
while index < spec.params.length { slots.push(""); index += 1 }982
let mut next = 0983
let mut named = false984
let mut fallback = false985
let variadic = spec.style.startsWith("variadic")986
let mut values: Array<string> = []987
index = 0988
for arg in args {989
let row = op.callArguments[index].split("|")990
let code = boxCode(emitExpr(ctx, arg)?)991
if variadic {992
if row[0] == "named" { fallback = true }993
if row[0] == "spread" {994
steps.push("call_spread_extend(&mut __values, {code}, {spanOf(arg)})?;")995
} else { steps.push("__values.push({code});") }996
} else {997
steps.push("let __a{index} = {code};")998
let mut slot = next999
if row[0] == "named" {1000
named = trueSHA256 c533f651c743054dde240d7a97c097c486a485789db63ac4c7f54ae65c869f8b