573 lines
13 KiB
Rust
573 lines
13 KiB
Rust
use std::rc::Rc;
|
|
|
|
use crate::ast::{BinaryOp, Expr, LValue, CatchBlock};
|
|
use crate::chunk::{Instruction as I, Chunk, Arg24, Catch};
|
|
use crate::lstr;
|
|
use crate::lstring::LStr;
|
|
use crate::symbol::Symbol;
|
|
use crate::value::function::{FuncAttrs, Function};
|
|
use crate::value::Value;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Local {
|
|
name: Rc<LStr>,
|
|
scope: usize,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum CompilerMode {
|
|
Function, Repl, Module,
|
|
}
|
|
|
|
struct Compiler<'a> {
|
|
mode: CompilerMode,
|
|
parent: Option<&'a Compiler<'a>>,
|
|
chunk: Chunk,
|
|
attrs: FuncAttrs,
|
|
scope: usize,
|
|
locals: Vec<Local>,
|
|
globals: Vec<Local>,
|
|
}
|
|
|
|
pub fn compile(expr: &Expr) -> Function {
|
|
let mut comp = Compiler::new_module(None);
|
|
comp.expr(expr);
|
|
comp.finish()
|
|
}
|
|
|
|
pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec<Local>) {
|
|
let mut comp = Compiler::new_repl(globals);
|
|
comp.expr(expr);
|
|
comp.finish_repl()
|
|
}
|
|
|
|
impl<'a> Default for Compiler<'a> {
|
|
fn default() -> Self {
|
|
let locals = vec![Local {
|
|
name: lstr!("self").into(),
|
|
scope: 0,
|
|
}];
|
|
Self {
|
|
mode: CompilerMode::Function,
|
|
parent: None,
|
|
chunk: Chunk::new(),
|
|
attrs: FuncAttrs { arity: 0, variadic: false },
|
|
scope: 0,
|
|
locals,
|
|
globals: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Compiler<'a> {
|
|
fn new_repl(globals: &[Local]) -> Self {
|
|
Self {
|
|
mode: CompilerMode::Repl,
|
|
globals: globals.to_vec(),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
fn new_module(parent: Option<&'a Self>) -> Self {
|
|
Self {
|
|
mode: CompilerMode::Module,
|
|
parent,
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
fn new_function(&'a self, args: &[&LStr]) -> Self {
|
|
let mut new = Self {
|
|
mode: CompilerMode::Function,
|
|
parent: Some(self),
|
|
..Self::default()
|
|
};
|
|
new.attrs.arity = args.len();
|
|
|
|
for arg in args {
|
|
new.locals.push(Local {
|
|
name: (*arg).into(),
|
|
scope: 0,
|
|
});
|
|
}
|
|
|
|
new
|
|
}
|
|
|
|
pub fn finish(mut self) -> Function {
|
|
self.emit(I::Return);
|
|
Function { chunk: Rc::new(self.chunk), attrs: self.attrs }
|
|
}
|
|
|
|
pub fn finish_repl(mut self) -> (Function, Vec<Local>) {
|
|
self.emit(I::Return);
|
|
(
|
|
Function { chunk: Rc::new(self.chunk), attrs: self.attrs },
|
|
self.globals
|
|
)
|
|
}
|
|
|
|
//
|
|
// Utility
|
|
//
|
|
|
|
fn add_const(&mut self, val: Value) -> usize {
|
|
self.chunk.add_const(val)
|
|
}
|
|
|
|
fn emit(&mut self, instr: I) -> usize {
|
|
self.chunk.add_instr(instr)
|
|
}
|
|
|
|
fn emit_discard(&mut self, mut n: usize) {
|
|
while n > 0 {
|
|
let instrs = &mut self.chunk.instrs;
|
|
|
|
// dup followed by store: remove the dup
|
|
if instrs.len() >= 2
|
|
&& matches!(instrs.get(instrs.len() - 2), Some(I::Dup))
|
|
&& matches!(instrs.last(), Some(
|
|
I::NewLocal | I::StoreLocal(_) | I::StoreGlobal(_)
|
|
))
|
|
{
|
|
// can't panic: checked that instrs.len() >= 2
|
|
let i = self.chunk.instrs.pop().unwrap();
|
|
self.chunk.instrs.pop().unwrap();
|
|
self.chunk.instrs.push(i);
|
|
n -= 1;
|
|
continue;
|
|
}
|
|
|
|
// final side-effectless instruction
|
|
let poppable = matches!(
|
|
instrs.last(),
|
|
Some(
|
|
I::Dup | I::Const(_) | I::Int(_)
|
|
| I::Nil | I::Bool(_) | I::Symbol(_)
|
|
| I::LoadLocal(_)
|
|
)
|
|
);
|
|
if poppable {
|
|
// can't panic: checked that instrs.last() was Some
|
|
instrs.pop().unwrap();
|
|
n -= 1;
|
|
continue;
|
|
}
|
|
|
|
// no more optimizations possible
|
|
break;
|
|
}
|
|
if n > 0 {
|
|
self.emit(I::Drop(Arg24::from_usize(n)));
|
|
}
|
|
}
|
|
|
|
fn ip(&self) -> usize {
|
|
self.chunk.instrs.len()
|
|
}
|
|
|
|
fn update_instr(&mut self, n: usize, new: I) {
|
|
self.chunk.instrs[n] = new;
|
|
}
|
|
|
|
fn begin_scope(&mut self) {
|
|
self.scope += 1;
|
|
}
|
|
|
|
fn end_scope(&mut self) {
|
|
self.scope -= 1;
|
|
|
|
// no need to clean up at bottom scope
|
|
if self.scope == 0 { return; }
|
|
|
|
for i in (0..self.globals.len()).rev() {
|
|
if self.globals[i].scope <= self.scope {
|
|
break;
|
|
}
|
|
self.globals.pop();
|
|
}
|
|
|
|
let mut count = 0;
|
|
for i in (0..self.locals.len()).rev() {
|
|
if self.locals[i].scope <= self.scope {
|
|
break;
|
|
}
|
|
self.locals.pop();
|
|
count += 1;
|
|
}
|
|
|
|
if count > 0 && self.scope > 0 {
|
|
self.emit(I::DropLocal(Arg24::from_usize(count)));
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// variables
|
|
//
|
|
|
|
fn resolve_local(&mut self, name: &LStr) -> Option<usize> {
|
|
self.locals.iter().rev()
|
|
.position(|v| v.name.as_ref() == name)
|
|
.map(|x| self.locals.len() - x - 1)
|
|
}
|
|
|
|
fn resolve_global(&mut self, name: &LStr) -> Option<usize> {
|
|
self.globals.iter().rev()
|
|
.position(|v| v.name.as_ref() == name)
|
|
.map(|x| self.globals.len() - x - 1)
|
|
}
|
|
|
|
|
|
fn load_var(&mut self, name: &LStr) {
|
|
match (self.resolve_local(name), self.resolve_global(name)) {
|
|
(Some(n), None) => {
|
|
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
|
},
|
|
(Some(n), Some(m)) if n >= m => {
|
|
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
|
},
|
|
_ => {
|
|
let sym = Symbol::get(name);
|
|
self.emit(I::LoadGlobal(Arg24::from_symbol(sym)));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn declare_local(&mut self, name: &LStr) -> usize {
|
|
if let Some(i) = self.resolve_local(name) {
|
|
if self.locals[i].scope == self.scope {
|
|
self.emit(I::StoreLocal(Arg24::from_usize(i)));
|
|
return i;
|
|
}
|
|
}
|
|
|
|
self.locals.push(Local {
|
|
name: name.into(),
|
|
scope: self.scope,
|
|
});
|
|
|
|
let i = self.locals.len() - 1;
|
|
self.emit(I::NewLocal);
|
|
i
|
|
}
|
|
|
|
fn store_local(&mut self, i: usize) {
|
|
self.emit(I::StoreLocal(Arg24::from_usize(i)));
|
|
}
|
|
|
|
fn store_global(&mut self, name: &LStr) {
|
|
let sym = Symbol::get(name);
|
|
self.emit(I::StoreGlobal(Arg24::from_symbol(sym)));
|
|
if let Some(i) = self.resolve_global(name) {
|
|
if self.globals[i].scope == self.scope {
|
|
return
|
|
}
|
|
}
|
|
self.globals.push(Local {
|
|
name: name.into(),
|
|
scope: self.scope,
|
|
});
|
|
}
|
|
|
|
fn store_default(&mut self, name: &LStr) {
|
|
match (self.resolve_local(name), self.resolve_global(name)) {
|
|
(Some(n), None) => self.store_local(n),
|
|
(Some(n), Some(m)) if n >= m => self.store_local(n),
|
|
(_, Some(_)) => self.store_global(name),
|
|
(None, None) => {
|
|
if self.mode == CompilerMode::Repl && self.scope == 1 {
|
|
self.store_global(name);
|
|
} else {
|
|
self.declare_local(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Expressions
|
|
//
|
|
|
|
fn expr(&mut self, e: &Expr) {
|
|
match e {
|
|
Expr::Block(xs) if xs.is_empty() => { self.emit(I::Nil); },
|
|
Expr::Block(xs) => {
|
|
self.begin_scope();
|
|
for x in &xs[0..xs.len()-1] {
|
|
self.expr(x);
|
|
self.emit_discard(1);
|
|
}
|
|
self.expr(&xs[xs.len()-1]);
|
|
self.end_scope();
|
|
},
|
|
Expr::Literal(v) => self.expr_literal(v),
|
|
Expr::Ident(ident) => self.load_var(ident),
|
|
Expr::UnaryOp(o, a) => {
|
|
self.expr(a);
|
|
self.emit(I::UnaryOp(*o));
|
|
},
|
|
Expr::BinaryOp(o, a, b) => {
|
|
self.expr(a);
|
|
self.expr(b);
|
|
self.emit(I::BinaryOp(*o));
|
|
},
|
|
Expr::Assign(o, lv, a) => self.expr_assign(*o, lv, a),
|
|
Expr::AssignVar(name, a) => {
|
|
self.expr(a);
|
|
self.emit(I::Dup);
|
|
self.declare_local(name);
|
|
},
|
|
Expr::AssignGlobal(name, a) => {
|
|
self.expr(a);
|
|
self.emit(I::Dup);
|
|
self.store_global(name);
|
|
},
|
|
Expr::List(xs) if xs.is_empty() => {
|
|
self.emit(I::NewList(0));
|
|
},
|
|
Expr::List(xs) => {
|
|
let mut first = true;
|
|
for chunk in xs.chunks(16) {
|
|
for e in chunk {
|
|
self.expr(e);
|
|
}
|
|
if first {
|
|
self.emit(I::NewList(chunk.len() as u8));
|
|
first = false;
|
|
} else {
|
|
self.emit(I::GrowList(chunk.len() as u8));
|
|
}
|
|
}
|
|
},
|
|
Expr::Table(xs) if xs.is_empty() => {
|
|
self.emit(I::NewTable(0));
|
|
},
|
|
Expr::Table(xs) => {
|
|
let mut first = true;
|
|
for chunk in xs.chunks(8) {
|
|
for (k, v) in chunk {
|
|
self.expr(k);
|
|
self.expr(v);
|
|
}
|
|
if first {
|
|
self.emit(I::NewTable(chunk.len() as u8));
|
|
first = false;
|
|
} else {
|
|
self.emit(I::GrowTable(chunk.len() as u8));
|
|
}
|
|
}
|
|
},
|
|
Expr::Index(ct, idx) => {
|
|
self.expr(ct);
|
|
self.expr(idx);
|
|
self.emit(I::Index);
|
|
},
|
|
Expr::FnCall(f, args) => {
|
|
self.expr(f);
|
|
for a in args {
|
|
self.expr(a);
|
|
}
|
|
self.emit(I::Call(args.len() as u8));
|
|
},
|
|
Expr::AssocFnCall(o, f, args) => {
|
|
self.expr(o);
|
|
self.emit(I::Dup);
|
|
self.emit(I::Symbol(Arg24::from_symbol(*f)));
|
|
self.emit(I::Index);
|
|
self.emit(I::Swap);
|
|
for a in args {
|
|
self.expr(a);
|
|
}
|
|
self.emit(I::Call((args.len() + 1) as u8));
|
|
},
|
|
Expr::Return(e) => {
|
|
self.expr(e);
|
|
self.emit(I::Return);
|
|
},
|
|
Expr::Pipe(a, f) => {
|
|
self.expr(a);
|
|
self.expr(f);
|
|
self.emit(I::Swap);
|
|
self.emit(I::Call(1));
|
|
},
|
|
Expr::Lambda(args, body) => self.expr_lambda(args, body),
|
|
Expr::And(a, b) => {
|
|
self.expr(a);
|
|
self.emit(I::Dup);
|
|
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
|
self.emit_discard(1);
|
|
self.expr(b);
|
|
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
|
},
|
|
Expr::Or(a, b) => {
|
|
self.expr(a);
|
|
self.emit(I::Dup);
|
|
let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0)));
|
|
self.emit_discard(1);
|
|
self.expr(b);
|
|
self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip())));
|
|
},
|
|
Expr::If(cond, b1, b2) => {
|
|
self.expr(cond);
|
|
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
|
self.expr(b1);
|
|
let j2 = self.emit(I::Jump(Arg24::from_usize(0)));
|
|
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
|
if let Some(b2) = b2 {
|
|
self.expr(b2);
|
|
} else {
|
|
self.emit(I::Nil);
|
|
}
|
|
self.update_instr(j2,
|
|
I::Jump(Arg24::from_usize(self.ip()))
|
|
);
|
|
},
|
|
Expr::While(cond, body) => {
|
|
let start = self.ip();
|
|
self.expr(cond);
|
|
|
|
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
|
self.expr(body);
|
|
self.emit_discard(1);
|
|
self.emit(I::Jump(Arg24::from_usize(start)));
|
|
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
|
|
|
self.emit(I::Nil);
|
|
},
|
|
Expr::For(name, iter, body) => self.expr_for(name, iter, body),
|
|
Expr::Try(body, catches) => self.expr_try(body, catches),
|
|
}
|
|
}
|
|
|
|
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) {
|
|
let (idx, mut table) = self.chunk.begin_try_table(self.locals.len());
|
|
|
|
self.emit(I::BeginTry(Arg24::from_usize(idx)));
|
|
self.expr(body);
|
|
self.emit(I::EndTry);
|
|
let body_end_addr = self.emit(I::Jump(Arg24::from_usize(0)));
|
|
let mut catch_end_addrs = Vec::new();
|
|
|
|
for catch_block in catch_blocks {
|
|
table.catches.push(Catch {
|
|
addr: self.ip(),
|
|
types: catch_block.types.clone(),
|
|
});
|
|
|
|
self.begin_scope();
|
|
|
|
if let Some(name) = catch_block.name {
|
|
self.declare_local(name);
|
|
} else {
|
|
self.emit_discard(1);
|
|
}
|
|
|
|
self.expr(&catch_block.body);
|
|
self.end_scope();
|
|
|
|
let end_addr = self.emit(I::Jump(Arg24::from_usize(0)));
|
|
catch_end_addrs.push(end_addr);
|
|
}
|
|
|
|
let ip = Arg24::from_usize(self.ip());
|
|
self.update_instr(body_end_addr, I::Jump(ip));
|
|
for addr in catch_end_addrs {
|
|
self.update_instr(addr, I::Jump(ip));
|
|
}
|
|
|
|
self.chunk.finish_catch_table(idx, table);
|
|
}
|
|
|
|
fn expr_for(&mut self, name: &LStr, iter: &Expr, body: &Expr) {
|
|
// load iterable and convert to iterator
|
|
self.expr(iter);
|
|
self.emit(I::IterBegin);
|
|
|
|
// declare loop variable
|
|
self.begin_scope();
|
|
self.emit(I::Nil);
|
|
let local = self.declare_local(name);
|
|
|
|
// begin loop
|
|
let start = self.ip();
|
|
|
|
// call iterator and jump if nil, otherwise store
|
|
self.emit(I::Dup);
|
|
self.emit(I::Call(0));
|
|
let j1 = self.emit(I::IterTest(Arg24::from_usize(0)));
|
|
self.store_local(local);
|
|
|
|
// body
|
|
self.expr(body);
|
|
self.emit_discard(1);
|
|
|
|
// end loop
|
|
self.emit(I::Jump(Arg24::from_usize(start)));
|
|
|
|
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
|
|
self.end_scope();
|
|
self.emit(I::Nil);
|
|
}
|
|
|
|
fn expr_lambda(&mut self, args: &[&LStr], body: &Expr) {
|
|
let mut inner = self.new_function(args);
|
|
inner.parent = Some(self);
|
|
inner.expr(body);
|
|
let func = inner.finish();
|
|
let n = self.add_const(func.into());
|
|
self.emit(I::Const(Arg24::from_usize(n)));
|
|
}
|
|
|
|
fn expr_literal(&mut self, val: &Value) {
|
|
match val {
|
|
Value::Nil
|
|
=> { self.emit(I::Nil); },
|
|
Value::Bool(b)
|
|
=> { self.emit(I::Bool(*b)); },
|
|
Value::Int(i) if (-0x80_0000..=0x7f_ffff).contains(i)
|
|
=> { self.emit(I::Int(Arg24::from_i64(*i))); },
|
|
Value::Symbol(s)
|
|
=> { self.emit(I::Symbol(Arg24::from_symbol(*s))); },
|
|
_ => {
|
|
let n = self.add_const(val.clone());
|
|
self.emit(I::Const(Arg24::from_usize(n)));
|
|
},
|
|
}
|
|
}
|
|
|
|
fn expr_assign(&mut self, o: Option<BinaryOp>, lv: &LValue, a: &Expr) {
|
|
match (lv, o) {
|
|
(LValue::Ident(i), None) => {
|
|
self.expr(a);
|
|
self.emit(I::Dup);
|
|
self.store_default(i);
|
|
},
|
|
(LValue::Ident(i), Some(o)) => {
|
|
self.load_var(i);
|
|
self.expr(a);
|
|
self.emit(I::BinaryOp(o));
|
|
self.emit(I::Dup);
|
|
self.store_default(i);
|
|
},
|
|
(LValue::Index(ct, i), None) => {
|
|
self.expr(ct);
|
|
self.expr(i);
|
|
self.expr(a);
|
|
self.emit(I::StoreIndex);
|
|
},
|
|
(LValue::Index(ct, i), Some(o)) => {
|
|
self.expr(ct);
|
|
self.expr(i);
|
|
self.emit(I::DupTwo);
|
|
self.emit(I::Index);
|
|
self.expr(a);
|
|
self.emit(I::BinaryOp(o));
|
|
self.emit(I::StoreIndex);
|
|
},
|
|
}
|
|
}
|
|
|
|
}
|