feat: execute source control flow and exact real arithmetic
This commit is contained in:
+121
-44
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user