feat: execute source control flow and exact real arithmetic

This commit is contained in:
2026-11-12 10:48:27 +08:00
parent f3e1670c58
commit 5ff1c68f5f
28 changed files with 1045 additions and 117 deletions
+121 -44
View File
@@ -167,6 +167,16 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
StmtKind::Expression(expression) => {
root_scope.expression(expression, &globals)?
}
StmtKind::If(_) | StmtKind::While(_) => {
let statements = std::slice::from_ref(statement);
collect_locals(
statements,
&mut declared,
&mut root_scope.scope.layout.names,
)?;
globals.extend(root_scope.scope.layout.names.iter().cloned());
root_scope.statements(statements, &globals)?;
}
StmtKind::Return(_) => {
return Err(error(statement.span, "return outside a function"));
}
@@ -207,21 +217,38 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
.push(parameter.name.text.clone());
builder.initialized.insert(parameter.name.text.clone());
}
for statement in &function.body.statements {
collect_locals(
&function.body.statements,
&mut locals,
&mut builder.scope.layout.locals,
)?;
builder.statements(&function.body.statements, globals)?;
Ok(builder.scope)
}
fn collect_locals(
statements: &[frontend::ast::Stmt],
locals: &mut BTreeSet<String>,
layout: &mut Vec<String>,
) -> Result<(), Diagnostic> {
for statement in statements {
match &statement.kind {
StmtKind::Binding(binding) => {
declare(&mut locals, &binding.name.text, binding.name.span)?;
builder.scope.layout.locals.push(binding.name.text.clone());
StmtKind::Binding(b) => {
declare(locals, &b.name.text, b.name.span)?;
layout.push(b.name.text.clone());
}
StmtKind::Assignment(assignment) => {
if locals.insert(assignment.name.text.clone()) {
builder
.scope
.layout
.locals
.push(assignment.name.text.clone());
StmtKind::Assignment(a) => {
if locals.insert(a.name.text.clone()) {
layout.push(a.name.text.clone());
}
}
StmtKind::If(b) => {
collect_locals(&b.body.statements, locals, layout)?;
if let Some(other) = &b.otherwise {
collect_locals(&other.statements, locals, layout)?;
}
}
StmtKind::While(b) => collect_locals(&b.body.statements, locals, layout)?,
StmtKind::Declaration(_) => {
return Err(error(
statement.span,
@@ -231,39 +258,7 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
_ => {}
}
}
let mut returned = false;
for statement in &function.body.statements {
if returned {
return Err(error(
statement.span,
"statement after return is unreachable",
));
}
match &statement.kind {
StmtKind::Binding(binding) => {
builder.expression(&binding.value, globals)?;
builder.initialized.insert(binding.name.text.clone());
}
StmtKind::Assignment(assignment) => {
builder.expression(&assignment.value, globals)?;
builder.initialized.insert(assignment.name.text.clone());
}
StmtKind::Return(statement) => {
if let Some(value) = &statement.value {
builder.expression(value, globals)?;
}
returned = true;
}
StmtKind::MemberAssignment(assignment) => {
builder.expression(&assignment.object, globals)?;
builder.expression(&assignment.value, globals)?;
builder.intern(&assignment.name.text);
}
StmtKind::Expression(expression) => builder.expression(expression, globals)?,
StmtKind::Declaration(_) => unreachable!("nested declaration rejected above"),
}
}
Ok(builder.scope)
Ok(())
}
struct Builder {
@@ -300,6 +295,88 @@ impl Builder {
}
}
/// Function-scoped locals; branches merge definite initialization. A loop
/// may execute zero times, so its writes do not initialize later reads.
fn statements(
&mut self,
statements: &[frontend::ast::Stmt],
globals: &BTreeSet<String>,
) -> Result<bool, Diagnostic> {
let mut returned = false;
for statement in statements {
if returned {
return Err(error(
statement.span,
"statement after return is unreachable",
));
}
match &statement.kind {
StmtKind::Binding(b) => {
self.expression(&b.value, globals)?;
self.initialized.insert(b.name.text.clone());
if self.scope.kind != ScopeKind::Function {
self.intern(&b.name.text);
}
}
StmtKind::Assignment(a) => {
self.expression(&a.value, globals)?;
self.initialized.insert(a.name.text.clone());
if self.scope.kind != ScopeKind::Function {
self.intern(&a.name.text);
}
}
StmtKind::MemberAssignment(a) => {
self.expression(&a.object, globals)?;
self.expression(&a.value, globals)?;
self.intern(&a.name.text);
}
StmtKind::Expression(e) => self.expression(e, globals)?,
StmtKind::Return(r) => {
if self.scope.kind != ScopeKind::Function {
return Err(error(statement.span, "return outside a function"));
}
if let Some(e) = &r.value {
self.expression(e, globals)?;
}
returned = true;
}
StmtKind::If(b) => {
self.expression(&b.condition, globals)?;
let before = self.initialized.clone();
let yes_return = self.statements(&b.body.statements, globals)?;
let yes = self.initialized.clone();
self.initialized = before;
let no_return = if let Some(other) = &b.otherwise {
self.statements(&other.statements, globals)?
} else {
false
};
self.initialized = if yes_return {
self.initialized.clone()
} else if no_return {
yes
} else {
yes.intersection(&self.initialized).cloned().collect()
};
returned = yes_return && no_return;
}
StmtKind::While(b) => {
self.expression(&b.condition, globals)?;
let before = self.initialized.clone();
self.statements(&b.body.statements, globals)?;
self.initialized = before;
}
StmtKind::Declaration(_) => {
return Err(error(
statement.span,
"nested declarations are not compiled yet",
));
}
}
}
Ok(returned)
}
fn expression(
&mut self,
expression: &Expr,
+100 -17
View File
@@ -2,7 +2,7 @@ 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::ast::{AstRoot, BinaryOp, Decl, DeclKind, Expr, ExprKind, Stmt, StmtKind, UnaryOp};
use frontend::{Diagnostic, DiagnosticKind, SourceUnit, Span};
use crate::{NameBinding, Scope, ScopeKind, SymbolTable, diagnostic};
@@ -115,6 +115,7 @@ struct Emitter<'a> {
source: &'a SourceUnit,
instructions: Vec<Instruction>,
constants: Vec<Value>,
jumps: Vec<(usize, usize)>,
children: Vec<(usize, CompiledCode)>,
}
@@ -125,6 +126,7 @@ impl<'a> Emitter<'a> {
source,
instructions: Vec::new(),
constants: Vec::new(),
jumps: Vec::new(),
children: Vec::new(),
}
}
@@ -249,6 +251,36 @@ impl<'a> Emitter<'a> {
fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> {
match &statement.kind {
StmtKind::If(branch) => {
self.expression(&branch.condition)?;
let conditional = self.instructions.len();
self.emit(Opcode::PopJumpIfFalse, 0)?;
for statement in &branch.body.statements {
self.statement(statement, symbols)?;
}
let end = self.instructions.len();
self.emit(Opcode::JumpForward, 0)?;
self.jumps.push((conditional, self.instructions.len()));
if let Some(otherwise) = &branch.otherwise {
for statement in &otherwise.statements {
self.statement(statement, symbols)?;
}
}
self.jumps.push((end, self.instructions.len()));
}
StmtKind::While(loop_) => {
let start = self.instructions.len();
self.expression(&loop_.condition)?;
let exit = self.instructions.len();
self.emit(Opcode::PopJumpIfFalse, 0)?;
for statement in &loop_.body.statements {
self.statement(statement, symbols)?;
}
let back = self.instructions.len();
self.emit(Opcode::JumpBackward, 0)?;
self.jumps.push((back, start));
self.jumps.push((exit, self.instructions.len()));
}
StmtKind::Binding(binding) => self.store_binding(&binding.name.text, &binding.value)?,
StmtKind::Assignment(assignment) => {
self.store_binding(&assignment.name.text, &assignment.value)?
@@ -308,22 +340,38 @@ impl<'a> Emitter<'a> {
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::Unary { operand, .. } => {
if let Some(spelling) = signed_literal(expression, false) {
self.number(&spelling, expression.span)?;
} else {
self.expression(operand)?;
self.simple(Opcode::UnaryNegate);
}
}
ExprKind::Binary { .. } => {
return Err(error(
expression.span,
"numeric arithmetic bytecode is not implemented yet",
));
ExprKind::Binary {
operation,
left,
right,
} => {
self.expression(left)?;
self.expression(right)?;
match operation {
BinaryOp::Add => self.emit(Opcode::BinaryArithmetic, 0)?,
BinaryOp::Subtract => self.emit(Opcode::BinaryArithmetic, 1)?,
BinaryOp::Multiply => self.emit(Opcode::BinaryArithmetic, 2)?,
BinaryOp::Divide => self.emit(Opcode::BinaryArithmetic, 3)?,
_ => self
.instructions
.push(Instruction::compare(match operation {
BinaryOp::Equal => endor_core::CompareOp::Eq,
BinaryOp::NotEqual => endor_core::CompareOp::Ne,
BinaryOp::Less => endor_core::CompareOp::Lt,
BinaryOp::LessEqual => endor_core::CompareOp::Le,
BinaryOp::Greater => endor_core::CompareOp::Gt,
BinaryOp::GreaterEqual => endor_core::CompareOp::Ge,
_ => unreachable!(),
})),
}
}
ExprKind::Symbol(_) => {
return Err(error(
@@ -345,7 +393,42 @@ impl<'a> Emitter<'a> {
self.constant(Value::Number(value))
}
fn finish(self) -> Result<CompiledCode, Diagnostic> {
fn finish(mut self) -> Result<CompiledCode, Diagnostic> {
// Labels refer to logical instructions. Resolve relative code-unit
// offsets after EXTENDED_ARG expansion, to a fixed point.
for attempt in 0..64 {
let mut offsets = vec![0_usize];
for i in &self.instructions {
let units = if i.argument <= 0xff {
1
} else if i.argument <= 0xffff {
2
} else if i.argument <= 0xffffff {
3
} else {
4
};
offsets.push(offsets.last().unwrap() + units);
}
let mut changed = false;
for &(index, target) in &self.jumps {
let distance = if self.instructions[index].opcode == Opcode::JumpBackward {
offsets[index + 1] - offsets[target]
} else {
offsets[target] - offsets[index + 1]
};
let operand = u32::try_from(distance)
.map_err(|_| error(self.scope.span, "jump exceeds u32"))?;
changed |= self.instructions[index].argument != operand;
self.instructions[index].argument = operand;
}
if !changed {
break;
}
if attempt == 63 {
return Err(error(self.scope.span, "jump layout did not converge"));
}
}
let line = self
.scope
.span