use endor_core::{ CodeLayout, DecodedInstruction, EndorCode, EndorNumber, Instruction, ObjectRef, Opcode, Value, Vm, VmError, }; use frontend::ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, Stmt, StmtKind, UnaryOp}; use frontend::{Diagnostic, DiagnosticKind, SourceUnit, Span}; use crate::{NameBinding, Scope, ScopeKind, SymbolTable, diagnostic}; /// Validated, heap-independent executable template. Nested code constants /// remain templates until load(), so no ObjectRef is shared across VM heaps. #[derive(Debug, Clone)] pub struct CompiledCode { code: EndorCode, children: Vec<(usize, CompiledCode)>, } impl CompiledCode { pub fn instructions(&self) -> &[DecodedInstruction] { self.code.instructions() } pub fn layout(&self) -> &CodeLayout { self.code.layout() } pub fn name(&self) -> &str { self.code.name() } pub fn filename(&self) -> &str { self.code.filename() } pub fn first_line(&self) -> usize { self.code.first_line() } pub fn children(&self) -> impl Iterator { self.children.iter().map(|(_, child)| child) } /// Return one owned code-object root; release it after execution. The /// parent retains its nested code constants. Temporary child roots are /// released on both success and failure, including partial recursive loads. pub fn load(&self, vm: &mut Vm) -> Result { let mut roots = Vec::new(); let result = (|| { let mut constants = self.code.constants().to_vec(); for (index, child) in &self.children { let reference = child.load(vm)?; roots.push(reference); constants[*index] = Value::Object(reference); } let code = EndorCode::with_layout( self.code.bytecode().to_vec(), constants, self.code.layout().clone(), )? .with_location( self.code.name(), self.code.filename(), self.code.first_line(), ); vm.new_code(code) })(); for reference in roots { vm.release(reference) .expect("loader owns each temporary code root"); } result } } fn error(span: Span, message: impl Into) -> Diagnostic { diagnostic(DiagnosticKind::Compilation, span, message) } pub(crate) fn generate( root: &AstRoot, source: &SourceUnit, symbols: &SymbolTable, ) -> Result { let mut emitter = Emitter::new(&symbols.root, source); match root { AstRoot::Module(program) => { for declaration in &program.declarations { emitter.declaration(declaration, symbols)?; } emitter.constant(Value::Nil)?; emitter.simple(Opcode::ReturnValue); } AstRoot::Expression(expression) => { emitter.expression(expression)?; emitter.simple(Opcode::ReturnValue); } AstRoot::Interactive(input) => { for statement in &input.statements { if let StmtKind::Expression(expression) = &statement.kind { emitter.expression(expression)?; } else { emitter.statement(statement, symbols)?; emitter.constant(Value::Nil)?; } } emitter.simple(Opcode::ReturnValue); } } emitter.finish() } struct Emitter<'a> { scope: &'a Scope, source: &'a SourceUnit, instructions: Vec, constants: Vec, children: Vec<(usize, CompiledCode)>, } impl<'a> Emitter<'a> { fn new(scope: &'a Scope, source: &'a SourceUnit) -> Self { Self { scope, source, instructions: Vec::new(), constants: Vec::new(), children: Vec::new(), } } fn emit(&mut self, opcode: Opcode, argument: usize) -> Result<(), Diagnostic> { let argument = u32::try_from(argument) .map_err(|_| error(self.scope.span, "bytecode operand exceeds u32"))?; self.instructions.push(Instruction::new(opcode, argument)); Ok(()) } fn simple(&mut self, opcode: Opcode) { self.instructions.push(Instruction::simple(opcode)); } fn constant(&mut self, value: Value) -> Result<(), Diagnostic> { let index = self.constants.len(); self.constants.push(value); self.emit(Opcode::LoadConst, index) } fn name_index(&self, name: &str) -> usize { self.scope .layout .names .iter() .position(|candidate| candidate == name) .expect("analysis interns every emitted name") } fn declaration(&mut self, declaration: &Decl, symbols: &SymbolTable) -> Result<(), Diagnostic> { let DeclKind::Function(function) = &declaration.kind; let scope = symbols .functions .iter() .find(|scope| scope.span == function.span && scope.name == function.name.text) .expect("analysis creates each function scope"); let mut body = Emitter::new(scope, self.source); for statement in &function.body.statements { body.statement(statement, symbols)?; } if !matches!( function .body .statements .last() .map(|statement| &statement.kind), Some(StmtKind::Return(_)) ) { body.constant(Value::Nil)?; body.simple(Opcode::ReturnValue); } let child = body.finish()?; let index = self.constants.len(); self.children.push((index, child)); self.constant(Value::Nil)?; // Replaced by a managed code object during load(). self.instructions.push(Instruction::make_function(0, 0)); self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text)) } fn store_binding(&mut self, name: &str, value: &Expr) -> Result<(), Diagnostic> { self.expression(value)?; if self.scope.kind == ScopeKind::Function { let index = self .scope .layout .locals .iter() .position(|local| local == name) .expect("analysis allocates binding and assignment targets"); self.emit(Opcode::StoreFast, index)?; } else { self.emit(Opcode::StoreGlobal, self.name_index(name))?; } Ok(()) } fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> { match &statement.kind { StmtKind::Binding(binding) => self.store_binding(&binding.name.text, &binding.value)?, StmtKind::Assignment(assignment) => { self.store_binding(&assignment.name.text, &assignment.value)? } StmtKind::Return(statement) => { if let Some(value) = &statement.value { self.expression(value)?; } else { self.constant(Value::Nil)?; } self.simple(Opcode::ReturnValue); } StmtKind::Expression(expression) => { self.expression(expression)?; self.simple(Opcode::PopTop); } StmtKind::Declaration(declaration) => self.declaration(declaration, symbols)?, } Ok(()) } fn expression(&mut self, expression: &Expr) -> Result<(), Diagnostic> { match &expression.kind { ExprKind::String(value) => self.constant(Value::Text(value.clone()))?, ExprKind::Number(spelling) => self.number(spelling, expression.span)?, ExprKind::Name(name) => { let reference = self .scope .references .iter() .find(|reference| reference.span == expression.span && &reference.name == name) .expect("analysis resolves every name expression"); let (opcode, index) = match reference.binding { NameBinding::Local(index) => (Opcode::LoadFast, index), NameBinding::Global(index) => (Opcode::LoadGlobal, index), }; self.emit(opcode, index)?; } ExprKind::Call { callee, arguments } => { self.expression(callee)?; self.simple(Opcode::PushNull); for argument in arguments { self.expression(argument)?; } self.emit(Opcode::Call, arguments.len())?; } ExprKind::Member { object, name } => { self.expression(object)?; self.emit(Opcode::LoadAttr, self.name_index(&name.text))?; } ExprKind::Unary { .. } => { // Literal sign folding preserves arbitrarily large integers // and decimal spelling; this is not general numeric arithmetic. let Some(spelling) = signed_literal(expression, false) else { return Err(error( expression.span, "numeric unary operations are not implemented; only signed number literals are supported", )); }; self.number(&spelling, expression.span)?; } ExprKind::Binary { .. } => { return Err(error( expression.span, "numeric arithmetic bytecode is not implemented yet", )); } ExprKind::Symbol(_) => { return Err(error( expression.span, "symbol runtime values are not implemented yet", )); } } Ok(()) } fn number(&mut self, spelling: &str, span: Span) -> Result<(), Diagnostic> { let value = if spelling.contains('.') { EndorNumber::decimal(spelling) } else { EndorNumber::integer(spelling) } .map_err(|failure| error(span, failure.to_string()))?; self.constant(Value::Number(value)) } fn finish(self) -> Result { let line = self .scope .span .location(self.source.text()) .map_or(1, |location| location.line); let code = EndorCode::from_instructions( &self.instructions, self.constants, self.scope.layout.clone(), ) .map_err(|failure| error(self.scope.span, failure.to_string()))? .with_location(&self.scope.name, self.source.origin().to_string(), line); Ok(CompiledCode { code, children: self.children, }) } } fn signed_literal(expression: &Expr, negative: bool) -> Option { match &expression.kind { ExprKind::Number(spelling) => Some(if negative { format!("-{spelling}") } else { spelling.clone() }), ExprKind::Unary { operation: UnaryOp::Negate, operand, } => signed_literal(operand, !negative), _ => None, } }