slow.monster

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

1import emitter.rust_text { rustStr }
2import emitter.type_test { typeTest, childTypes, subtree, substitute, nominalIdentity }
3import emitter.builtins { BuiltinSpec, staticSpec, staticBuiltin, freeBuiltin, freeSpec, lispexIntrinsic }
4import 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,
19import emitter.repr { reprOf, reprAt, slotRepr, rustType, isCopyRepr, boxed, coerce, bare, sharedInit, sharedRead, sharedWrite, parameterGuard, variadicRepr }
20import stage1_types { Stage1Operation }
21import 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 actions
26// localfn -> a missing-aware local function cell, filled at its declaration
27// 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 identity
30export record Bind {
31 name: string,
32 kind: string,
33 rust: string,
34 repr: string,
35 mutable: bool,
38export 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,
52export record ModInfo {
53 identity: string,
54 cell: string,
55 binds: Array<Bind>,
56 exports: Array<string>,
57 generatedStd: bool,
60export 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>,
74export record LoopFrame {
75 id: string,
76 label: string,
77 repr: string,
78 cleanup: int,
81export record Code {
82 rs: string,
83 repr: string,
86// --- scope ---------------------------------------------------------------
88export function has(list: Array<string>, value: string) -> bool {
89 for item in list {
90 if item == value {
91 return true
92 }
93 }
94 false
97export function pushBind(ctx: Ctx, bind: Bind) -> () {
98 let mut scope = ctx.scope
99 scope.push(bind)
102export function restoreScope(ctx: Ctx, mark: int) -> () {
103 let mut scope = ctx.scope
104 while scope.length > mark {
105 let _ = scope.pop()
106 }
107 ()
110export function lookup(ctx: Ctx, name: string) -> Option<Bind> {
111 let mut position = ctx.scope.length - 1
112 while position >= 0 {
113 let candidate = ctx.scope[position]
114 if candidate.name == name {
115 return Some(candidate)
116 }
117 position -= 1
118 }
119 None
122// A fresh module-level cell `_c<n>` of the given representation.
123export function newCell(ctx: Ctx, repr: string) -> string {
124 let name = "_c{ctx.cellDecls.length}"
125 let mut decls = ctx.cellDecls
126 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 name
134function 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 }
141function 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 }
148export 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 None
157// --- reads and writes of bindings ----------------------------------------
159export 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 }
197export 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 }
210// The code in the operation's own representation when it carries a type.
211function 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 }
220// The code converted to `target` (explicit box/unbox boundary).
221export 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 }
231function 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 }
239// --- expressions ----------------------------------------------------------
241export 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 }
300function floatLiteral(op: Stage1Operation) -> Result<string, string> {
301 let text = op.detail
302 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 }
320function 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 }
337function 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 = 0
349 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) => decoded
355 case None => return Err(refusal("string escape", part))
356 }
357 current = current + text
358 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 += 1
369 }
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 }
391function 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 }
413function 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 }
425function isOrdering(detail: string) -> bool {
426 detail == "less-than" || detail == "less-or-equal" || detail == "greater-than" ||
427 detail == "greater-or-equal"
430// A faulting leaf; inside a top-level `const` the fault is reframed as the
431// constant-expression error the interpreter's const pass reports.
432function checked(ctx: Ctx, leaf: string, span: string) -> string {
433 if ctx.inConst {
434 "__cg({leaf}, {span})?"
435 } else {
436 "{leaf}?"
437 }
440// The arithmetic/comparison core shared by binary expressions and compound
441// assignment. Typed rows of the representation table use the checked scalar
442// leaves of `topaz_value`; every other pairing goes through the boxed leaf.
443function 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 })
487function 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)
509function 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 }
543function emitArray(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {
544 let mut values: Array<string> = []
545 let mut steps: Array<string> = []
546 let mut spread = false
547 let mut position = 0
548 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 = true
554 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 += 1
562 }
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 }
573function emitMap(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {
574 let mut pairs: Array<string> = []
575 let mut key = ""
576 let mut position = 0
577 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 = value
583 } else if field == "value" {
584 pairs.push("({key}, {value})")
585 } else {
586 return Err(refusal("shape map entry {field}", op))
587 }
588 position += 1
589 }
590 Ok(Code { rs: "builtin_map_of(vec![{pairs.join(", ")}], {spanOf(op)})?", repr: "value" })
593function 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)
602function 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>"
611function 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))
625// Defaults belong to their declaration environment, even when the construction
626// occurs in a function that shadows a module binding.
627function defaultContext(ctx: Ctx, decl: Stage1Operation) -> Ctx {
628 let mut parent = decl.parentId
629 let mut moduleLevel = false
630 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 => ctx
640 }
643function recordDefault(ctx: Ctx, decl: Stage1Operation, ordinal: int) -> Result<Option<Stage1Operation>, string> {
644 let mut index = 0
645 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 += 1
650 }
651 Ok(None)
654function 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 = false
661 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 = true
670 }
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 = 0
677 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 += 1
686 }
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 = 0
695 for name in names {
696 if name == atom.edgeName { value = "{fields[slot]}.clone()" }
697 slot += 1
698 }
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 = 0
717 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" })
724function 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) => found
758 case None => return Err(refusal("JSON nominal {identity}", origin))
759 }
760 let mut params: Map<string, Array<PreviewTypeAtom>> = Map.new()
761 let mut ordinal = 0
762 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 += 1
767 }
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))
802function 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)
808function emitRecord(ctx: Ctx, op: Stage1Operation) -> Result<Code, string> {
809 let mut pairs: Array<string> = []
810 let mut position = 0
811 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 += 1
820 }
821 Ok(Code { rs: "Value::record([{pairs.join(", ")}])", repr: "value" })
824// The namespace binding an operation names, if it is an identifier bound to
825// an imported module.
826function namespaceOf(ctx: Ctx, op: Stage1Operation) -> Option<Bind> {
827 if op.kind != "expression/identifier" {
828 return None
829 }
830 match lookup(ctx, op.detail) {
831 case Some(bind) => if bind.kind == "namespace" {
832 Some(bind)
833 } else {
834 None
835 }
836 case None => None
837 }
840// A runtime operation named by a generated std.lispex module, or "" when the
841// reference is not one (the Rust emitter's `lispex_intrinsic_kind`).
842function 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 }
849function builtinCode(tag: string) -> Code {
850 Code { rs: "Value::Builtin \{ kind: Builtin::{tag}, recv: None \}", repr: "value" }
853function 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" })
859function 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 which
862 // receivers admit a method and whether acquiring it requires a mutable root.
863 let mut cursor = object
864 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 = true
868 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})? \} \}")
880function 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" })
905function 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} \} \} \}"
909function 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" })
915// The plan retains argument order. Named and spread AST wrappers are encoded
916// in operand labels, so their leaves are not ordinary `arguments` children.
917function callArguments(ctx: Ctx, op: Stage1Operation) -> Result<Array<Stage1Operation>, string> {
918 let mut found: Array<Stage1Operation> = []
919 let mut position = 0
920 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 += 1
926 }
927 if found.length != op.callArguments.length { return Err(refusal("shape call arguments", op)) }
928 Ok(found)
931function valueCall(ctx: Ctx, op: Stage1Operation, callee: string, args: Array<Stage1Operation>) -> Result<string, string> {
932 valueCallPrefix(ctx, op, callee, args, [])
935function 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> = prefix
938 let mut named: Array<string> = []
939 let mut hasSpread = false
940 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 = false
944 let mut sawNamed = false
945 let mut position = 0
946 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 = true
953 } 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 = true
958 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 += 1
965 }
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} \}")
977function 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 = 0
981 while index < spec.params.length { slots.push(""); index += 1 }
982 let mut next = 0
983 let mut named = false
984 let mut fallback = false
985 let variadic = spec.style.startsWith("variadic")
986 let mut values: Array<string> = []
987 index = 0
988 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 = next
999 if row[0] == "named" {
1000 named = true

SHA256 c533f651c743054dde240d7a97c097c486a485789db63ac4c7f54ae65c869f8b