607 lines
19 KiB
Rust
607 lines
19 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
ops::{Deref, DerefMut},
|
|
rc::Rc,
|
|
};
|
|
|
|
use crate::{
|
|
compile::CompileOptions,
|
|
vm::value::{BytecodeFunction, StringValue},
|
|
};
|
|
use crate::{
|
|
compile::{
|
|
error::CompileError,
|
|
function::FunctionSignature,
|
|
instruction::Emitted,
|
|
syntax::{Expression, FunctionBody},
|
|
},
|
|
vm::{
|
|
Value,
|
|
instruction::{ConstantId, ImmediateInteger, Instruction, LocalId},
|
|
value::{BooleanValue, IdentifierValue, NumberValue},
|
|
},
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub struct FunctionBlock {
|
|
parent: Option<usize>,
|
|
identifier: Option<IdentifierValue>,
|
|
docstring: Option<StringValue>,
|
|
signature: FunctionSignature,
|
|
|
|
// Data
|
|
pub(crate) constants: Vec<Value>,
|
|
locals: Vec<Local>,
|
|
upvalues: Vec<UpvalueDef>,
|
|
scope_depth: usize,
|
|
|
|
// Code
|
|
pub(crate) instructions: Vec<Emitted>,
|
|
labels: Vec<usize>,
|
|
loop_stack: Vec<LoopStackEntry>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Local {
|
|
name: IdentifierValue,
|
|
depth: isize,
|
|
is_captured: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct UpvalueDef {
|
|
pub(crate) index: LocalId,
|
|
pub(crate) is_local: bool,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct LoopStackEntry {
|
|
label_entry: usize,
|
|
label_exit: usize,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum CompileValue {
|
|
Nil,
|
|
Identifier(IdentifierValue),
|
|
Integer(NumberValue),
|
|
Boolean(BooleanValue),
|
|
String(StringValue),
|
|
LocalFunction(usize),
|
|
Quote(Rc<Value>),
|
|
Value(Value),
|
|
Stack,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct CompileContext {
|
|
pub function_blocks: Vec<FunctionBlock>,
|
|
pub(crate) current: usize,
|
|
pub(crate) options: CompileOptions,
|
|
}
|
|
|
|
pub trait Compile {
|
|
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError>;
|
|
}
|
|
|
|
impl CompileContext {
|
|
pub fn new(options: CompileOptions, root_name: Option<IdentifierValue>) -> Self {
|
|
Self {
|
|
function_blocks: vec![FunctionBlock::root(root_name)],
|
|
current: 0,
|
|
options,
|
|
}
|
|
}
|
|
|
|
pub fn compile_value(
|
|
options: CompileOptions,
|
|
chunk_name: Option<IdentifierValue>,
|
|
value: &Value,
|
|
) -> Result<Rc<BytecodeFunction>, CompileError> {
|
|
let mut cx = Self::new(options, chunk_name);
|
|
let expression = Expression::parse(value).map_err(CompileError::Parse)?;
|
|
let value = expression.compile(&mut cx)?;
|
|
cx.compile_return_value(value)?;
|
|
cx.to_bytecode()
|
|
}
|
|
|
|
pub fn compile_function(
|
|
&mut self,
|
|
identifier: Option<IdentifierValue>,
|
|
docstring: Option<StringValue>,
|
|
signature: &FunctionSignature,
|
|
body: &FunctionBody,
|
|
) -> Result<usize, CompileError> {
|
|
// TODO signature
|
|
let index = self.push_lambda_context(identifier, docstring, signature)?;
|
|
for expression in body.head.iter() {
|
|
self.compile_statement(expression)?;
|
|
}
|
|
let value = body.tail.compile(self)?;
|
|
self.compile_return_value(value)?;
|
|
self.pop_context();
|
|
Ok(index)
|
|
}
|
|
|
|
pub fn compile_return_value(&mut self, value: CompileValue) -> Result<(), CompileError> {
|
|
self.push(value)?;
|
|
self.emit(Instruction::Return);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn push_lambda_context(
|
|
&mut self,
|
|
identifier: Option<IdentifierValue>,
|
|
docstring: Option<StringValue>,
|
|
signature: &FunctionSignature,
|
|
) -> Result<usize, CompileError> {
|
|
if self.options.trace_compile {
|
|
eprintln!("COMPILE: push_lambda_context({identifier:?})");
|
|
}
|
|
let block = FunctionBlock::new(Some(self.current), identifier, docstring, signature);
|
|
let index = self.function_blocks.len();
|
|
self.function_blocks.push(block);
|
|
self.current = index;
|
|
Ok(index)
|
|
}
|
|
|
|
pub fn pop_context(&mut self) {
|
|
if self.options.trace_compile {
|
|
let current_name = self.identifier.clone();
|
|
eprintln!("COMPILE: pop_lambda_context({current_name:?})");
|
|
}
|
|
let index = self
|
|
.parent
|
|
.expect("cannot pop out of root function context");
|
|
self.current = index;
|
|
}
|
|
|
|
pub fn compile_statement(&mut self, expression: &Expression) -> Result<(), CompileError> {
|
|
let value = expression.compile(self)?;
|
|
self.discard(value);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn compile_set_global(
|
|
&mut self,
|
|
identifier: IdentifierValue,
|
|
value: CompileValue,
|
|
) -> Result<(), CompileError> {
|
|
self.push(value)?;
|
|
self.get_constant(Value::Identifier(identifier))?;
|
|
self.emit(Instruction::SetGlobal);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn compile_declare_macro(
|
|
&mut self,
|
|
identifier: IdentifierValue,
|
|
value: CompileValue,
|
|
) -> Result<(), CompileError> {
|
|
self.push(value)?;
|
|
self.get_constant(Value::Identifier(identifier))?;
|
|
self.emit(Instruction::DeclareMacro);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn compile_assign(
|
|
&mut self,
|
|
identifier: IdentifierValue,
|
|
value: CompileValue,
|
|
) -> Result<(), CompileError> {
|
|
self.push(value)?;
|
|
|
|
if let Some(local) = self.resolve_local(identifier.as_ref())? {
|
|
self.emit(Instruction::SetLocal);
|
|
self.emit(local);
|
|
} else if let Some(upvalue) = self.resolve_upvalue(identifier.as_ref())? {
|
|
self.emit(Instruction::SetUpvalue);
|
|
self.emit(upvalue);
|
|
} else {
|
|
self.get_constant(Value::Identifier(identifier))?;
|
|
self.emit(Instruction::SetGlobal);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_constant(&mut self, value: Value) -> Result<(), CompileError> {
|
|
let index = self.constant(value)?;
|
|
self.emit(Instruction::PushConstant);
|
|
self.emit(index);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn push(&mut self, value: CompileValue) -> Result<(), CompileError> {
|
|
match value {
|
|
CompileValue::Nil => {
|
|
self.emit(Instruction::PushNil);
|
|
}
|
|
CompileValue::String(value) => {
|
|
self.get_constant(Value::String(value))?;
|
|
}
|
|
CompileValue::Quote(value) => {
|
|
self.get_constant(value.as_ref().clone())?;
|
|
}
|
|
CompileValue::Value(value) => {
|
|
self.get_constant(value)?;
|
|
}
|
|
CompileValue::Integer(value) => {
|
|
if let Ok(immediate) = ImmediateInteger::try_from(value) {
|
|
self.emit(Instruction::PushInteger);
|
|
self.emit(immediate);
|
|
} else {
|
|
self.get_constant(Value::Number(value))?;
|
|
}
|
|
}
|
|
CompileValue::Boolean(BooleanValue(value)) => match value {
|
|
true => self.emit(Instruction::PushTrue),
|
|
false => self.emit(Instruction::PushFalse),
|
|
},
|
|
CompileValue::Identifier(name) => {
|
|
if let Some(local) = self.resolve_local(name.as_ref())? {
|
|
self.emit(Instruction::GetLocal);
|
|
self.emit(local);
|
|
} else if let Some(upvalue) = self.resolve_upvalue(name.as_ref())? {
|
|
self.emit(Instruction::GetUpvalue);
|
|
self.emit(upvalue);
|
|
} else {
|
|
self.get_constant(Value::Identifier(name))?;
|
|
self.emit(Instruction::GetGlobal);
|
|
}
|
|
}
|
|
CompileValue::LocalFunction(index) => {
|
|
let function = self.function_blocks[index].to_bytecode()?;
|
|
self.get_constant(Value::Function(function.clone()))?;
|
|
if !function.upvalues.is_empty() {
|
|
self.emit(Instruction::MakeClosure);
|
|
}
|
|
}
|
|
CompileValue::Stack => (),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn discard(&mut self, value: CompileValue) {
|
|
if matches!(value, CompileValue::Stack) {
|
|
self.emit(Instruction::Drop);
|
|
}
|
|
}
|
|
|
|
pub fn push_scope(&mut self) {
|
|
self.scope_depth += 1;
|
|
if self.options.trace_compile {
|
|
eprintln!("COMPILE: push_scope({})", self.scope_depth);
|
|
}
|
|
}
|
|
|
|
pub fn pop_scope(&mut self, out_value: Option<CompileValue>) -> Result<(), CompileError> {
|
|
if self.options.trace_compile {
|
|
eprintln!("COMPILE: pop_scope({})", self.scope_depth);
|
|
}
|
|
if self.scope_depth == 0 {
|
|
panic!("Cannot pop out of root scope");
|
|
}
|
|
self.scope_depth -= 1;
|
|
let Some(out_value) = out_value else { todo!() };
|
|
self.push(out_value)?;
|
|
self.emit(Instruction::SetTemp);
|
|
for i in (0..self.locals.len()).rev() {
|
|
if self.locals[i].depth <= self.scope_depth as isize {
|
|
break;
|
|
}
|
|
if self.locals[i].is_captured {
|
|
self.emit(Instruction::CloseUpvalue);
|
|
} else {
|
|
self.emit(Instruction::Drop);
|
|
}
|
|
self.locals.pop();
|
|
}
|
|
self.emit(Instruction::GetTemp);
|
|
Ok(())
|
|
}
|
|
|
|
fn resolve_upvalue_inner(
|
|
&mut self,
|
|
at: Option<usize>,
|
|
name: &str,
|
|
) -> Result<Option<LocalId>, CompileError> {
|
|
let Some(at) = at else {
|
|
return Ok(None);
|
|
};
|
|
let block = &mut self.function_blocks[at];
|
|
|
|
if let Some(local) = block.resolve_local(name)? {
|
|
block.locals[usize::from(local)].is_captured = true;
|
|
|
|
return self
|
|
.add_upvalue(UpvalueDef {
|
|
index: local,
|
|
is_local: true,
|
|
})
|
|
.map(Some);
|
|
}
|
|
|
|
let parent = block.parent;
|
|
if let Some(upvalue) = self.resolve_upvalue_inner(parent, name)? {
|
|
return self
|
|
.add_upvalue(UpvalueDef {
|
|
index: upvalue,
|
|
is_local: false,
|
|
})
|
|
.map(Some);
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
fn resolve_upvalue(&mut self, name: &str) -> Result<Option<LocalId>, CompileError> {
|
|
self.resolve_upvalue_inner(self.parent, name)
|
|
}
|
|
}
|
|
|
|
impl Deref for CompileContext {
|
|
type Target = FunctionBlock;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.function_blocks[self.current]
|
|
}
|
|
}
|
|
impl DerefMut for CompileContext {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.function_blocks[self.current]
|
|
}
|
|
}
|
|
|
|
impl FunctionBlock {
|
|
fn new(
|
|
parent: Option<usize>,
|
|
identifier: Option<IdentifierValue>,
|
|
docstring: Option<StringValue>,
|
|
signature: &FunctionSignature,
|
|
) -> Self {
|
|
let mut block = Self {
|
|
parent,
|
|
identifier,
|
|
docstring,
|
|
signature: signature.clone(),
|
|
constants: vec![],
|
|
locals: vec![],
|
|
upvalues: vec![],
|
|
scope_depth: 0,
|
|
instructions: vec![],
|
|
labels: vec![],
|
|
loop_stack: vec![],
|
|
};
|
|
for required in signature.required_arguments.iter() {
|
|
block
|
|
.add_local(required.clone(), Some(-100))
|
|
.expect("couldn't add an argument");
|
|
}
|
|
for optional in signature.optional_arguments.iter() {
|
|
block
|
|
.add_local(optional.clone(), Some(-100))
|
|
.expect("couldn't add an argument");
|
|
}
|
|
block
|
|
}
|
|
|
|
fn root(identifier: Option<IdentifierValue>) -> Self {
|
|
Self::new(
|
|
None,
|
|
identifier,
|
|
None,
|
|
&FunctionSignature {
|
|
required_arguments: vec![],
|
|
optional_arguments: vec![],
|
|
rest_argument: None,
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn enter_loop(&mut self, label_entry: usize, label_exit: usize) {
|
|
self.loop_stack.push(LoopStackEntry {
|
|
label_entry,
|
|
label_exit,
|
|
});
|
|
}
|
|
|
|
pub fn leave_loop(&mut self) {
|
|
let v = self.loop_stack.pop();
|
|
if v.is_none() {
|
|
panic!("Compiler error: leave_loop() called outside any enclosing loop");
|
|
}
|
|
}
|
|
|
|
pub fn emit_break(&mut self) -> Result<(), CompileError> {
|
|
let Some(entry) = self.loop_stack.last() else {
|
|
return Err(CompileError::BreakOutsideOfLoop);
|
|
};
|
|
self.emit(Emitted::Jump(entry.label_exit));
|
|
Ok(())
|
|
}
|
|
|
|
pub fn emit_continue(&mut self) -> Result<(), CompileError> {
|
|
let Some(entry) = self.loop_stack.last() else {
|
|
return Err(CompileError::ContinueOutsideOfLoop);
|
|
};
|
|
self.emit(Emitted::Jump(entry.label_entry));
|
|
Ok(())
|
|
}
|
|
|
|
pub fn to_bytecode(&self) -> Result<Rc<BytecodeFunction>, CompileError> {
|
|
if !self.loop_stack.is_empty() {
|
|
panic!("Compiler error: loop stack not empty on function completion");
|
|
}
|
|
|
|
let mut instructions = vec![];
|
|
let mut resolved_labels = HashMap::new();
|
|
let mut patch_branches = HashMap::new();
|
|
|
|
for &offset in self.labels.iter() {
|
|
resolved_labels.insert(offset, None);
|
|
}
|
|
|
|
// Emit all code, with placeholders for branch targets
|
|
for (offset, emitted) in self.instructions.iter().enumerate() {
|
|
let ip = instructions.len();
|
|
// Resolve real label address
|
|
if let Some(real) = resolved_labels.get_mut(&offset) {
|
|
assert!(real.is_none());
|
|
*real = Some(ip);
|
|
}
|
|
match emitted {
|
|
&Emitted::Jump(label) => {
|
|
instructions.push(Instruction::Jump.into());
|
|
patch_branches.insert(instructions.len(), label);
|
|
instructions.push(0xFF);
|
|
}
|
|
&Emitted::Branch(label) => {
|
|
instructions.push(Instruction::Branch.into());
|
|
patch_branches.insert(instructions.len(), label);
|
|
instructions.push(0xFF);
|
|
}
|
|
Emitted::LocalId(local) => {
|
|
instructions.extend_from_slice(&local.to_bytes());
|
|
}
|
|
Emitted::ConstantId(index) => {
|
|
instructions.extend_from_slice(&index.to_bytes());
|
|
}
|
|
Emitted::ArgumentCount(count) => {
|
|
instructions.extend_from_slice(&count.to_bytes());
|
|
}
|
|
Emitted::ImmediateInteger(value) => {
|
|
instructions.extend_from_slice(&value.to_bytes())
|
|
}
|
|
&Emitted::Bool(value) => instructions.push(value as u8),
|
|
&Emitted::Instruction(instruction) => instructions.push(u8::from(instruction)),
|
|
}
|
|
}
|
|
|
|
// Patch branch targets
|
|
for (position, label) in patch_branches {
|
|
let label_input = self.labels.get(label).copied().unwrap();
|
|
let branch_target = resolved_labels.get(&label_input).copied().unwrap().unwrap();
|
|
let branch_source = position + 1;
|
|
let branch_offset = branch_target
|
|
.checked_signed_diff(branch_source)
|
|
.and_then(|diff| i8::try_from(diff).ok())
|
|
.ok_or_else(|| CompileError::CannotEmitBranch(branch_source, branch_target))?;
|
|
instructions[position] = branch_offset as u8;
|
|
}
|
|
|
|
let min_arity = self.signature.min_arity();
|
|
let max_arity = self.signature.max_arity();
|
|
|
|
Ok(Rc::new(BytecodeFunction {
|
|
identifier: self.identifier.clone(),
|
|
instructions: instructions.into(),
|
|
docstring: self.docstring.clone(),
|
|
constants: self.constants.iter().cloned().collect(),
|
|
upvalues: self.upvalues.iter().copied().collect(),
|
|
min_arity,
|
|
max_arity,
|
|
}))
|
|
}
|
|
|
|
pub fn new_label(&mut self) -> usize {
|
|
let index = self.labels.len();
|
|
self.labels.push(usize::MAX);
|
|
index
|
|
}
|
|
|
|
pub fn adjust_label(&mut self, label: usize) {
|
|
let address = self.instructions.len();
|
|
self.labels[label] = address;
|
|
}
|
|
|
|
pub fn emit<I: Into<Emitted>>(&mut self, insn: I) {
|
|
let emitted = insn.into();
|
|
self.instructions.push(emitted);
|
|
}
|
|
|
|
pub fn constant(&mut self, value: Value) -> Result<ConstantId, CompileError> {
|
|
if let Some(index) = self.constants.iter().position(|v| v == &value) {
|
|
return Ok(index.try_into().unwrap());
|
|
}
|
|
let index = ConstantId::try_from(self.constants.len())
|
|
.map_err(|_| CompileError::TooManyConstants)?;
|
|
self.constants.push(value);
|
|
Ok(index)
|
|
}
|
|
|
|
fn add_local(
|
|
&mut self,
|
|
name: IdentifierValue,
|
|
depth: Option<isize>,
|
|
) -> Result<LocalId, CompileError> {
|
|
let index =
|
|
LocalId::try_from(self.locals.len()).map_err(|_| CompileError::TooManyLocals)?;
|
|
self.locals.push(Local {
|
|
name,
|
|
is_captured: false,
|
|
depth: depth.unwrap_or(-1),
|
|
});
|
|
Ok(index)
|
|
}
|
|
|
|
fn add_upvalue(&mut self, upvalue: UpvalueDef) -> Result<LocalId, CompileError> {
|
|
if let Some(index) = self.upvalues.iter().position(|uv| upvalue == *uv) {
|
|
return Ok(index.try_into().unwrap());
|
|
}
|
|
// Add new
|
|
let index =
|
|
LocalId::try_from(self.upvalues.len()).map_err(|_| CompileError::TooManyLocals)?;
|
|
self.upvalues.push(upvalue);
|
|
Ok(index)
|
|
}
|
|
|
|
// Binding management
|
|
|
|
pub fn init_local(&mut self, index: LocalId) {
|
|
self.locals[usize::from(index)].depth = self.scope_depth as isize;
|
|
}
|
|
|
|
pub fn bind_local(&mut self, name: IdentifierValue) -> Result<LocalId, CompileError> {
|
|
if self.scope_depth == 0 {
|
|
todo!();
|
|
}
|
|
let mut i = self.locals.len();
|
|
while i > 0 {
|
|
i -= 1;
|
|
let local = &self.locals[i];
|
|
if local.depth != -1 && local.depth < self.scope_depth as isize {
|
|
break;
|
|
}
|
|
if local.name == name {
|
|
todo!("Same name, same scope");
|
|
}
|
|
}
|
|
self.add_local(name, None)
|
|
}
|
|
|
|
fn resolve_local(&mut self, name: &str) -> Result<Option<LocalId>, CompileError> {
|
|
let Some(index) = self
|
|
.locals
|
|
.iter()
|
|
.rposition(|local| local.name.as_ref() == name)
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
let local = &self.locals[index];
|
|
if local.depth == -1 {
|
|
return Err(CompileError::UndefinedLocalReference(name.into()));
|
|
}
|
|
|
|
Ok(Some(index.try_into().unwrap()))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn test_compile(
|
|
expression: &Expression,
|
|
) -> Result<(CompileContext, CompileValue), CompileError> {
|
|
let mut cx = CompileContext::new(Default::default(), None);
|
|
let value = expression.compile(&mut cx)?;
|
|
Ok((cx, value))
|
|
}
|