rudus/src/compiler.rs
2025-06-05 13:05:07 -04:00

1298 lines
46 KiB
Rust

use crate::parser::Ast;
use crate::parser::StringPart;
use crate::spans::Spanned;
use crate::value::*;
use chumsky::prelude::SimpleSpan;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum Op {
Noop,
Nil,
True,
False,
Constant,
Jump,
JumpIfFalse,
JumpIfTrue,
Pop,
PopN,
PushBinding,
Store,
StoreAt,
Stash,
Load,
ResetMatch,
Match,
MatchNil,
MatchTrue,
MatchFalse,
PanicIfNoMatch,
MatchConstant,
MatchType,
MatchTuple,
PushTuple,
LoadTuple,
MatchList,
LoadList,
PushList,
AppendList,
ConcatList,
PushDict,
LoadDictValue,
MatchDict,
PushBox,
GetKey,
PanicNoWhen,
JumpIfNoMatch,
JumpIfMatch,
PanicNoMatch,
TypeOf,
JumpBack,
JumpIfZero,
Duplicate,
Decrement,
Truncate,
MatchDepth,
Panic,
EmptyString,
ConcatStrings,
Stringify,
Call,
Return,
Eq,
Add,
Sub,
Mult,
Div,
Unbox,
BoxStore,
Assert,
Get,
At,
Not,
Print,
SetUpvalue,
GetUpvalue,
// Inc,
// Dec,
// Gt,
// Gte,
// Lt,
// Lte,
// Mod,
// Round,
// Ceil,
// Floor,
// Random,
// Sqrt,
// Assoc,
// Concat,
// Conj,
// Count,
// Disj,
// Dissoc,
// Range,
// Rest,
// Slice,
// "atan_2" math/atan2
// "chars" chars
// "cos" math/cos
// "doc" doc
// "downcase" string/ascii-lower
// "pi" math/pi
// "show" show
// "sin" math/sin
// "split" string/split
// "str_slice" string/slice
// "tan" math/tan
// "trim" string/trim
// "triml" string/triml
// "trimr" string/trimr
// "upcase" string/ascii-upper
}
impl std::fmt::Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Op::*;
let rep = match self {
Noop => "noop",
Nil => "nil",
True => "true",
False => "false",
Constant => "constant",
Jump => "jump",
JumpIfFalse => "jump_if_false",
JumpIfTrue => "jump_if_true",
Pop => "pop",
PopN => "pop_n",
PushBinding => "push_binding",
Store => "store",
StoreAt => "store_at",
Stash => "stash",
Load => "load",
Match => "match",
MatchNil => "match_nil",
MatchTrue => "match_true",
MatchFalse => "match_false",
ResetMatch => "reset_match",
PanicIfNoMatch => "panic_if_no_match",
MatchConstant => "match_constant",
MatchType => "match_type",
MatchTuple => "match_tuple",
PushTuple => "push_tuple",
LoadTuple => "load_tuple",
MatchList => "match_list",
LoadList => "load_list",
PushList => "push_list",
AppendList => "append_list",
ConcatList => "concat_list",
PushDict => "push_dict",
LoadDictValue => "load_dict_value",
MatchDict => "match_dict",
PushBox => "push_box",
GetKey => "get_key",
PanicNoWhen => "panic_no_when",
JumpIfNoMatch => "jump_if_no_match",
JumpIfMatch => "jump_if_match",
PanicNoMatch => "panic_no_match",
TypeOf => "type_of",
JumpBack => "jump_back",
JumpIfZero => "jump_if_zero",
Decrement => "decrement",
Truncate => "truncate",
Duplicate => "duplicate",
MatchDepth => "match_depth",
Panic => "panic",
EmptyString => "empty_string",
ConcatStrings => "concat_strings",
Stringify => "stringify",
Print => "print",
Eq => "eq",
Add => "add",
Sub => "sub",
Mult => "mult",
Div => "div",
Unbox => "unbox",
BoxStore => "box_store",
Assert => "assert",
Get => "get",
At => "at",
Not => "not",
Call => "call",
Return => "return",
SetUpvalue => "set_upvalue",
GetUpvalue => "get_upvalue",
};
write!(f, "{rep}")
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Binding {
name: &'static str,
depth: isize,
stack_pos: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Upvalue {
name: &'static str,
stack_pos: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Chunk {
pub constants: Vec<Value>,
pub bytecode: Vec<u8>,
pub strings: Vec<&'static str>,
pub keywords: Vec<&'static str>,
}
impl Chunk {
pub fn dissasemble_instr(&self, i: &mut usize) {
let op = Op::from_u8(self.bytecode[*i]).unwrap();
use Op::*;
match op {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf
| Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq | Add | Sub
| Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString
| ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList
| ConcatList | PushList => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
let next = self.bytecode[*i + 1];
let value = &self.constants[next as usize].show();
println!("{i:04}: {:16} {next:04}: {value}", op.to_string());
*i += 1;
}
PushBinding | MatchTuple | MatchList | MatchDict | LoadDictValue | PushTuple
| PushDict | PushBox | Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch
| JumpIfMatch | JumpBack | JumpIfZero | MatchDepth | PopN | StoreAt | Call
| SetUpvalue | GetUpvalue => {
let next = self.bytecode[*i + 1];
println!("{i:04}: {:16} {next:04}", op.to_string());
*i += 1;
}
}
}
pub fn dissasemble(&self) {
println!("IDX | CODE | INFO");
let mut i = 0;
while i < self.bytecode.len() {
self.dissasemble_instr(&mut i);
i += 1;
}
}
// pub fn kw_from(&self, kw: &str) -> Option<Value> {
// self.kw_index_from(kw).map(Value::Keyword)
// }
// pub fn kw_index_from(&self, kw: &str) -> Option<usize> {
// self.keywords.iter().position(|s| *s == kw)
// }
}
#[derive(Debug, Clone, PartialEq)]
struct LoopInfo {
start: usize,
stack_root: usize,
}
impl LoopInfo {
fn new(start: usize, stack_root: usize) -> LoopInfo {
LoopInfo { start, stack_root }
}
}
fn get_builtin(name: &str, arity: usize) -> Option<Op> {
match (name, arity) {
("type", 1) => Some(Op::TypeOf),
("eq?", 2) => Some(Op::Eq),
("add", 2) => Some(Op::Add),
("sub", 2) => Some(Op::Sub),
("mult", 2) => Some(Op::Mult),
("div", 2) => Some(Op::Div),
("unbox", 1) => Some(Op::Unbox),
("store!", 2) => Some(Op::BoxStore),
("assert!", 1) => Some(Op::Assert),
("get", 2) => Some(Op::Get),
("at", 2) => Some(Op::At),
("not", 1) => Some(Op::Not),
("print!", 1) => Some(Op::Print),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Compiler<'a> {
pub chunk: Chunk,
pub bindings: Vec<Binding>,
pub scope_depth: isize,
pub match_depth: usize,
pub stack_depth: usize,
pub spans: Vec<SimpleSpan>,
pub nodes: Vec<&'static Ast>,
pub ast: &'static Ast,
pub span: SimpleSpan,
pub src: &'static str,
pub name: &'static str,
pub enclosing: Option<&'a Compiler<'a>>,
pub upvalues: Vec<Upvalue>,
loop_info: Vec<LoopInfo>,
}
fn is_binding(expr: &Spanned<Ast>) -> bool {
let (ast, _) = expr;
use Ast::*;
match ast {
Let(..) | LBox(..) => true,
Fn(name, ..) => !name.is_empty(),
_ => false,
}
}
impl<'a> Compiler<'a> {
pub fn new(
ast: &'static Spanned<Ast>,
name: &'static str,
src: &'static str,
enclosing: Option<&'a Compiler>,
) -> Compiler<'a> {
let chunk = Chunk {
constants: vec![],
bytecode: vec![],
strings: vec![],
keywords: vec![
"nil", "bool", "number", "keyword", "string", "tuple", "list", "dict", "box", "fn",
],
};
Compiler {
chunk,
bindings: vec![],
scope_depth: -1,
match_depth: 0,
stack_depth: 0,
spans: vec![],
nodes: vec![],
ast: &ast.0,
span: ast.1,
loop_info: vec![],
enclosing,
upvalues: vec![],
src,
name,
}
}
pub fn visit(&mut self, node: &'static Spanned<Ast>) {
let root_node = self.ast;
let root_span = self.span;
let (ast, span) = node;
self.ast = ast;
self.span = *span;
self.compile();
self.ast = root_node;
self.span = root_span;
}
fn emit_constant(&mut self, val: Value) {
let const_idx = if let Some(idx) = self.chunk.constants.iter().position(|v| *v == val) {
idx
} else {
self.chunk.constants.push(val);
self.chunk.constants.len() - 1
};
if const_idx > u8::MAX as usize {
panic!(
"internal Ludus compiler error: too many constants in chunk:{}:: {}",
self.span, self.ast
)
}
self.emit_op(Op::Constant);
self.emit_byte(const_idx);
self.stack_depth += 1;
}
fn match_constant(&mut self, val: Value) {
let constant_index = match self.chunk.constants.iter().position(|v| *v == val) {
Some(idx) => idx,
None => {
self.chunk.constants.push(val);
self.chunk.constants.len() - 1
}
};
if constant_index > u8::MAX as usize {
panic!(
"internal Ludus compiler error: too many constants in chunk:{}:: {}",
self.span, self.ast
)
}
self.emit_op(Op::MatchConstant);
self.emit_byte(constant_index);
}
fn emit_op(&mut self, op: Op) {
self.chunk.bytecode.push(op as u8);
self.spans.push(self.span);
}
fn emit_byte(&mut self, byte: usize) {
self.chunk.bytecode.push(byte as u8);
self.spans.push(self.span);
}
fn len(&self) -> usize {
self.chunk.bytecode.len()
}
fn bind(&mut self, name: &'static str) {
let binding = Binding {
name,
depth: self.scope_depth,
stack_pos: self.stack_depth - self.match_depth - 1,
};
println!("{:?}", binding);
println!("stack: {}; match: {}", self.stack_depth, self.match_depth);
self.bindings.push(binding);
}
fn resolve_local(&self, name: &'static str) -> Option<usize> {
for binding in self.bindings.iter() {
if binding.name == name {
return Some(binding.stack_pos);
}
}
None
}
fn resolve_upvalue(&self, name: &'static str) -> Option<usize> {
self.upvalues.iter().position(|uv| uv.name == name)
}
fn get_upvalue(&self, name: &'static str) -> Upvalue {
let local = self.bindings.iter().find(|b| b.name == name);
match local {
Some(binding) => Upvalue {
name,
stack_pos: binding.stack_pos,
},
None => self.enclosing.unwrap().get_upvalue(name),
}
}
fn resolve_binding(&self, name: &'static str) -> usize {
if let Some(pos) = self.resolve_local(name) {
return pos;
}
match self.enclosing {
Some(compiler) => compiler.resolve_binding(name),
None => unreachable!(),
}
}
fn pop(&mut self) {
println!("Popping from: {}", self.ast);
self.emit_op(Op::Pop);
self.stack_depth -= 1;
}
fn pop_n(&mut self, n: usize) {
self.emit_op(Op::PopN);
self.emit_byte(n);
self.stack_depth -= n;
}
fn enter_loop(&mut self) {
self.loop_info
.push(LoopInfo::new(self.len(), self.stack_depth));
}
fn leave_loop(&mut self) {
self.loop_info.pop();
}
fn loop_info(&self) -> LoopInfo {
self.loop_info.last().unwrap().clone()
}
fn loop_idx(&self) -> usize {
self.loop_info.last().unwrap().start
}
fn loop_root(&self) -> usize {
self.loop_info.last().unwrap().stack_root
}
pub fn compile(&mut self) {
use Ast::*;
match self.ast {
Error => unreachable!(),
Nil => {
self.emit_op(Op::Nil);
self.stack_depth += 1;
}
Number(n) => self.emit_constant(Value::Number(*n)),
Boolean(b) => {
self.emit_op(if *b { Op::True } else { Op::False });
self.stack_depth += 1;
}
String(s) => {
self.emit_constant(Value::Interned(s));
}
Keyword(s) => self.emit_constant(Value::Keyword(s)),
Block(lines) => {
self.scope_depth += 1;
let stack_depth = self.stack_depth;
for expr in lines.iter().take(lines.len() - 1) {
if is_binding(expr) {
self.visit(expr);
} else {
self.visit(expr);
self.pop();
}
}
let last_expr = lines.last().unwrap();
match last_expr {
(Let(patt, expr), _) => {
self.match_depth = 0;
self.emit_op(Op::ResetMatch);
self.visit(expr);
let expr_pos = self.stack_depth - 1;
self.visit(patt);
self.emit_op(Op::PanicIfNoMatch);
self.emit_op(Op::PushBinding);
self.emit_byte(expr_pos);
self.stack_depth += 1;
}
_ => {
self.visit(last_expr);
}
}
self.stack_depth += 1;
self.emit_op(Op::Store);
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth {
self.bindings.pop();
} else {
break;
}
}
// reset stack
while self.stack_depth > stack_depth + 1 {
self.pop();
}
self.emit_op(Op::Load);
}
If(cond, then, r#else) => {
self.visit(cond);
let jif_idx = self.len();
self.emit_op(Op::JumpIfFalse);
self.emit_byte(0xff);
self.stack_depth -= 1;
self.visit(then);
let jump_idx = self.len();
self.emit_op(Op::Jump);
self.emit_byte(0xff);
self.visit(r#else);
self.stack_depth -= 1;
let end_idx = self.len();
let jif_offset = jump_idx - jif_idx;
let jump_offset = end_idx - jump_idx - 2;
self.chunk.bytecode[jif_idx + 1] = jif_offset as u8;
self.chunk.bytecode[jump_idx + 1] = jump_offset as u8;
}
Let(patt, expr) => {
self.match_depth = 0;
self.emit_op(Op::ResetMatch);
self.visit(expr);
self.visit(patt);
self.emit_op(Op::PanicIfNoMatch);
}
WordPattern(name) => {
self.emit_op(Op::Match);
self.bind(name);
}
Word(name) | Splat(name) => match self.resolve_local(name) {
Some(position) => {
self.emit_op(Op::PushBinding);
self.emit_byte(position);
self.stack_depth += 1;
}
None => match self.resolve_upvalue(name) {
Some(position) => {
println!("resolved upvalue: {name}");
self.emit_op(Op::GetUpvalue);
self.emit_byte(position);
self.stack_depth += 1;
}
None => {
println!("setting upvalue: {name}");
let upvalue = self.get_upvalue(name);
self.emit_op(Op::GetUpvalue);
self.emit_byte(upvalue.stack_pos);
self.upvalues.push(upvalue);
dbg!(&self.upvalues);
self.stack_depth += 1;
}
},
},
PlaceholderPattern => {
self.emit_op(Op::Match);
}
NilPattern => {
self.emit_op(Op::MatchNil);
}
BooleanPattern(b) => {
if *b {
self.emit_op(Op::MatchTrue);
} else {
self.emit_op(Op::MatchFalse);
}
}
NumberPattern(n) => {
self.match_constant(Value::Number(*n));
}
KeywordPattern(s) => {
let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s);
let kw_index = match existing_kw {
Some(index) => index,
None => self.chunk.keywords.len(),
};
if kw_index == self.chunk.keywords.len() {
self.chunk.keywords.push(s);
}
self.match_constant(Value::Keyword(s));
}
AsPattern(word, typ) => {
self.emit_constant(Value::Keyword(typ));
self.emit_op(Op::MatchType);
self.stack_depth -= 1;
self.bind(word);
}
StringPattern(s) => {
self.match_constant(Value::Interned(s));
}
TuplePattern(members) => {
self.emit_op(Op::MatchTuple);
self.emit_byte(members.len());
self.emit_op(Op::JumpIfNoMatch);
let before_load_tup_idx = self.len();
self.emit_byte(0xff);
let mut jump_idxes = vec![];
self.match_depth += members.len();
self.emit_op(Op::LoadTuple);
self.stack_depth += members.len();
for member in members {
self.match_depth -= 1;
self.emit_op(Op::MatchDepth);
self.emit_byte(self.match_depth);
self.visit(member);
self.emit_op(Op::JumpIfNoMatch);
jump_idxes.push(self.len());
self.emit_byte(0xff);
}
self.emit_op(Op::Jump);
let jump_idx = self.len();
self.emit_byte(0xff);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
for _ in 0..members.len() {
self.emit_op(Op::Pop);
}
self.chunk.bytecode[before_load_tup_idx] =
self.len() as u8 - before_load_tup_idx as u8 - 1;
self.chunk.bytecode[jump_idx] = self.len() as u8 - jump_idx as u8 - 1;
}
ListPattern(members) => {
self.emit_op(Op::MatchList);
self.emit_byte(members.len());
self.emit_op(Op::JumpIfNoMatch);
let before_load_list_idx = self.len();
self.emit_byte(0xff);
let mut jump_idxes = vec![];
self.match_depth += members.len();
self.emit_op(Op::LoadList);
self.stack_depth += members.len();
for member in members {
self.match_depth -= 1;
self.emit_op(Op::MatchDepth);
self.emit_byte(self.match_depth);
self.visit(member);
self.emit_op(Op::JumpIfNoMatch);
jump_idxes.push(self.len());
self.emit_byte(0xff);
}
self.emit_op(Op::Jump);
let jump_idx = self.len();
self.emit_byte(0xff);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
for _ in 0..members.len() {
self.emit_op(Op::Pop);
}
self.chunk.bytecode[before_load_list_idx] =
self.len() as u8 - before_load_list_idx as u8 - 1;
self.chunk.bytecode[jump_idx] = self.len() as u8 - jump_idx as u8 - 1;
}
DictPattern(pairs) => {
self.emit_op(Op::MatchDict);
self.emit_byte(pairs.len());
self.emit_op(Op::JumpIfNoMatch);
let before_load_dict_idx = self.len();
self.emit_byte(0xff);
let mut jump_idxes = vec![];
let dict_stack_pos = self.stack_depth - 1;
for pair in pairs {
let (PairPattern(key, pattern), _) = pair else {
unreachable!()
};
self.emit_constant(Value::Keyword(key));
self.emit_op(Op::LoadDictValue);
self.emit_byte(dict_stack_pos);
self.visit(pattern);
self.emit_op(Op::JumpIfNoMatch);
jump_idxes.push(self.len());
self.emit_byte(0xff);
}
self.emit_op(Op::Jump);
let jump_idx = self.len();
self.emit_byte(0xff);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
for _ in 0..pairs.len() {
self.emit_op(Op::Pop);
}
self.chunk.bytecode[before_load_dict_idx] =
self.len() as u8 - before_load_dict_idx as u8 - 1;
self.chunk.bytecode[jump_idx] = self.len() as u8 - jump_idx as u8 - 1;
}
PairPattern(_, _) => unreachable!(),
Tuple(members) => {
for member in members {
self.visit(member);
}
self.emit_op(Op::PushTuple);
self.emit_byte(members.len());
self.stack_depth = self.stack_depth + 1 - members.len();
}
List(members) => {
self.emit_op(Op::PushList);
self.stack_depth += 1;
for member in members {
self.visit(member);
if matches!(member, (Splat(..), _)) {
self.emit_op(Op::ConcatList);
} else {
self.emit_op(Op::AppendList);
}
self.stack_depth -= 1;
}
}
LBox(name, expr) => {
self.visit(expr);
self.emit_op(Op::PushBox);
self.bind(name);
}
Dict(pairs) => {
for pair in pairs {
self.visit(pair);
}
self.emit_op(Op::PushDict);
self.emit_byte(pairs.len());
self.stack_depth = self.stack_depth + 1 - (pairs.len() * 2);
}
Pair(key, value) => {
self.emit_constant(Value::Keyword(key));
self.visit(value);
}
Synthetic(first, second, rest) => {
match (&first.0, &second.0) {
(Word(_), Keyword(_)) => {
self.visit(first);
self.visit(second);
self.emit_op(Op::GetKey);
self.stack_depth -= 1;
}
(Keyword(_), Arguments(args)) => {
self.visit(&args[0]);
self.visit(first);
self.emit_op(Op::GetKey);
self.stack_depth -= 1;
}
(Or, Arguments(args)) => {
let stack_depth = self.stack_depth;
let mut jump_idxes = vec![];
if !args.is_empty() {
for arg in args {
self.visit(arg);
self.emit_op(Op::Stash);
self.emit_op(Op::JumpIfTrue);
jump_idxes.push(self.len());
self.emit_byte(0xff);
}
for idx in jump_idxes {
self.chunk.bytecode[idx] = (self.len() - idx + 2) as u8;
}
self.emit_op(Op::Load);
} else {
self.emit_op(Op::False);
}
self.stack_depth = stack_depth + 1;
}
(And, Arguments(args)) => {
let stack_depth = self.stack_depth;
let mut jump_idxes = vec![];
if !args.is_empty() {
for arg in args {
self.visit(arg);
self.emit_op(Op::Stash);
self.emit_op(Op::JumpIfFalse);
jump_idxes.push(self.len());
self.emit_byte(0xff);
}
for idx in jump_idxes {
self.chunk.bytecode[idx] = (self.len() - idx + 2) as u8;
}
self.emit_op(Op::Load);
} else {
self.emit_op(Op::True);
}
self.stack_depth = stack_depth + 1;
}
(Word(fn_name), Arguments(args)) => match get_builtin(fn_name, args.len()) {
Some(code) => {
for arg in args {
self.visit(arg);
}
self.emit_op(code);
self.stack_depth -= args.len() - 1;
}
None => {
let arity = args.len();
for arg in args {
self.visit(arg);
}
self.emit_op(Op::PushBinding);
self.stack_depth += 1;
// let biter = self.bindings.iter().rev();
for binding in self.bindings.iter() {
if binding.name == *fn_name {
self.emit_byte(binding.stack_pos);
break;
}
}
self.emit_op(Op::Call);
self.emit_byte(arity);
self.stack_depth -= arity;
}
},
_ => unreachable!(),
}
for (term, _) in rest {
match term {
Keyword(str) => {
self.emit_constant(Value::Keyword(str));
self.emit_op(Op::GetKey);
self.stack_depth -= 1;
}
Arguments(args) => {
self.emit_op(Op::Stash);
self.pop();
let arity = args.len();
for arg in args {
self.visit(arg);
}
self.emit_op(Op::Load);
self.emit_op(Op::Call);
self.emit_byte(arity);
self.stack_depth -= arity;
}
_ => unreachable!(),
}
}
}
When(clauses) => {
let mut jump_idxes = vec![];
let mut clauses = clauses.iter();
while let Some((WhenClause(cond, body), _)) = clauses.next() {
self.visit(cond.as_ref());
self.emit_op(Op::JumpIfFalse);
let jif_jump_idx = self.len();
self.emit_byte(0xff);
self.stack_depth -= 1;
self.visit(body);
self.stack_depth -= 1;
self.emit_op(Op::Jump);
jump_idxes.push(self.len());
self.emit_byte(0xff);
self.chunk.bytecode[jif_jump_idx] = self.len() as u8 - jif_jump_idx as u8 - 1;
}
self.emit_op(Op::PanicNoWhen);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
self.stack_depth += 1;
}
WhenClause(..) => unreachable!(),
Match(scrutinee, clauses) => {
self.visit(scrutinee.as_ref());
let stack_depth = self.stack_depth;
let mut jump_idxes = vec![];
let mut clauses = clauses.iter();
while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() {
let mut no_match_jumps = vec![];
self.scope_depth += 1;
self.match_depth = 0;
self.visit(pattern);
self.emit_op(Op::JumpIfNoMatch);
no_match_jumps.push(self.len());
self.emit_byte(0xff);
if guard.is_some() {
let guard_expr: &'static Spanned<Ast> =
Box::leak(Box::new(guard.clone().unwrap()));
self.visit(guard_expr);
self.emit_op(Op::JumpIfFalse);
no_match_jumps.push(self.len());
self.emit_byte(0xff);
self.stack_depth -= 1;
}
self.visit(body);
self.emit_op(Op::Store);
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth {
self.bindings.pop();
} else {
break;
}
}
while self.stack_depth > stack_depth {
self.pop();
}
self.emit_op(Op::Jump);
jump_idxes.push(self.len());
self.emit_byte(0xff);
for idx in no_match_jumps {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
}
self.emit_op(Op::PanicNoMatch);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
while self.stack_depth > stack_depth {
self.pop();
}
self.emit_op(Op::Load);
self.stack_depth += 1;
}
MatchClause(..) => unreachable!(),
Fn(name, body, doc) => {
let name = if name.is_empty() { "anonymous" } else { name };
// first, declare the function
// TODO: or, check if the function has already been declared!
let FnBody(fn_body) = &body.as_ref().0 else {
unreachable!()
};
let mut compilers: HashMap<usize, Compiler> = HashMap::new();
let mut upvalues = vec![];
for clause in fn_body {
let MatchClause(pattern, guard, clause_body) = &clause.0 else {
unreachable!()
};
let TuplePattern(pattern) = &pattern.0 else {
unreachable!()
};
let arity = pattern.len();
let compiler = match compilers.get_mut(&arity) {
Some(compiler) => compiler,
None => {
let mut compiler =
Compiler::new(clause, self.name, self.src, Some(self));
compiler.emit_op(Op::ResetMatch);
compilers.insert(arity, compiler);
compilers.get_mut(&arity).unwrap()
}
};
compiler.stack_depth += arity;
compiler.scope_depth += 1;
compiler.match_depth = arity;
std::mem::swap(&mut upvalues, &mut compiler.upvalues);
let mut tup_jump_idxes = vec![];
for member in pattern {
compiler.match_depth -= 1;
compiler.emit_op(Op::MatchDepth);
compiler.emit_byte(compiler.match_depth);
compiler.visit(member);
compiler.emit_op(Op::JumpIfNoMatch);
tup_jump_idxes.push(compiler.len());
compiler.emit_byte(0xff);
}
if pattern.is_empty() {
compiler.emit_op(Op::Match);
}
compiler.emit_op(Op::Jump);
let jump_idx = compiler.len();
compiler.emit_byte(0xff);
for idx in tup_jump_idxes {
compiler.chunk.bytecode[idx] = compiler.len() as u8 - idx as u8 - 2;
}
for _ in 0..arity {
compiler.emit_op(Op::Pop);
}
compiler.chunk.bytecode[jump_idx] = compiler.len() as u8 - jump_idx as u8 - 1;
let mut no_match_jumps = vec![];
compiler.emit_op(Op::JumpIfNoMatch);
// let jnm_idx = compiler.len();
no_match_jumps.push(compiler.len());
compiler.emit_byte(0xff);
if guard.is_some() {
let guard_expr: &'static Spanned<Ast> =
Box::leak(Box::new(guard.clone().unwrap()));
compiler.visit(guard_expr);
compiler.emit_op(Op::JumpIfFalse);
no_match_jumps.push(self.len());
compiler.emit_byte(0xff);
compiler.stack_depth -= 1;
}
compiler.visit(clause_body);
compiler.emit_op(Op::Store);
compiler.scope_depth -= 1;
while let Some(binding) = compiler.bindings.last() {
if binding.depth > compiler.scope_depth {
compiler.bindings.pop();
} else {
break;
}
}
while compiler.stack_depth > 0 {
compiler.pop();
}
compiler.stack_depth = 0;
compiler.emit_op(Op::Return);
for idx in no_match_jumps {
compiler.chunk.bytecode[idx] = compiler.len() as u8 - idx as u8 - 1;
}
// compiler.chunk.bytecode[jnm_idx] = compiler.len() as u8 - jnm_idx as u8 - 1;
compiler.scope_depth -= 1;
std::mem::swap(&mut compiler.upvalues, &mut upvalues);
}
let mut the_chunks = vec![];
for (arity, mut compiler) in compilers.into_iter() {
compiler.emit_op(Op::PanicNoMatch);
let chunk = compiler.chunk;
if crate::DEBUG_COMPILE {
println!("=== function chuncktion: {name}/{arity} ===");
chunk.dissasemble();
}
the_chunks.push((arity as u8, chunk));
}
let lfn = crate::value::LFn::Defined {
name,
doc: *doc,
chunks: the_chunks,
closed: RefCell::new(vec![]),
};
// TODO: check if the function is already declared, and pull out the relevant OnceCell if need be
let init_val = Value::Fn(Rc::new(lfn));
self.emit_constant(init_val);
self.bind(name);
for upvalue in upvalues {
self.emit_op(Op::SetUpvalue);
self.emit_byte(upvalue.stack_pos);
}
}
FnDeclaration(name) => {
let lfn = Value::Fn(Rc::new(LFn::Declared { name }));
self.emit_constant(lfn);
self.bind(name);
}
FnBody(_) => unreachable!(),
// TODO: add a check to make sure times >= 0
// TODO: fix this so that the algorithm is actually correct
// * truncate
// * check that it's 0 or more, panic if not
// * jump if 0 to end of loop
// * body
// * pop
// * decrement counter (which is now at the top of the stack)
// * jump back to jump if 0 instruction
Repeat(times, body) => {
self.visit(times);
self.emit_op(Op::Truncate);
// skip the decrement the first time
self.emit_op(Op::Jump);
self.emit_byte(1);
// begin repeat
self.emit_op(Op::Decrement);
let repeat_begin = self.len();
self.emit_op(Op::Duplicate);
// self.stack_depth += 1;
self.emit_op(Op::JumpIfZero);
self.emit_byte(0xff);
// compile the body
self.visit(body);
// pop whatever value the body returns
self.pop();
// self.emit_op(Op::Pop);
self.emit_op(Op::JumpBack);
// set jump points
let repeat_end = self.len();
self.emit_byte(repeat_end - repeat_begin);
self.chunk.bytecode[repeat_begin + 2] = (repeat_end - repeat_begin - 2) as u8;
// pop the counter
// self.pop();
// self.emit_op(Op::Pop);
// and emit nil
self.emit_constant(Value::Nil);
}
Loop(value, clauses) => {
//algo:
//first, put the values on the stack
let (Ast::Tuple(members), _) = value.as_ref() else {
unreachable!()
};
for (i, member) in members.iter().enumerate() {
self.visit(member);
self.emit_op(Op::StoreAt);
self.emit_byte(i);
}
let arity = members.len();
let stack_depth = self.stack_depth;
//then, save the beginning of the loop
self.emit_op(Op::Load);
self.enter_loop();
self.stack_depth += arity;
//next, compile each clause:
let mut clauses = clauses.iter();
let mut jump_idxes = vec![];
while let Some((Ast::MatchClause(pattern, _, body), _)) = clauses.next() {
self.emit_op(Op::ResetMatch);
self.scope_depth += 1;
let (Ast::TuplePattern(members), _) = pattern.as_ref() else {
unreachable!()
};
self.match_depth = arity;
let mut tup_jump_idxes = vec![];
for member in members {
self.match_depth -= 1;
self.emit_op(Op::MatchDepth);
self.emit_byte(self.match_depth);
self.visit(member);
self.emit_op(Op::JumpIfNoMatch);
tup_jump_idxes.push(self.len());
self.emit_byte(0xff);
}
self.emit_op(Op::Jump);
let jump_idx = self.len();
self.emit_byte(0xff);
for idx in tup_jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 2;
}
for _ in 0..arity {
self.emit_op(Op::Pop);
}
self.chunk.bytecode[jump_idx] = self.len() as u8 - jump_idx as u8 - 1;
self.emit_op(Op::JumpIfNoMatch);
let jnm_idx = self.len();
self.emit_byte(0xff);
self.visit(body);
self.emit_op(Op::Store);
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth {
self.bindings.pop();
} else {
break;
}
}
while self.stack_depth > stack_depth + arity {
self.pop();
}
self.stack_depth -= arity;
self.emit_op(Op::Jump);
jump_idxes.push(self.len());
self.emit_byte(0xff);
self.chunk.bytecode[jnm_idx] = self.len() as u8 - jnm_idx as u8;
self.scope_depth -= 1;
}
self.emit_op(Op::PanicNoMatch);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
self.emit_op(Op::PopN);
self.emit_byte(arity);
self.stack_depth -= arity;
self.emit_op(Op::Load);
self.stack_depth += 1;
self.leave_loop();
}
Recur(args) => {
for (i, arg) in args.iter().enumerate() {
self.visit(arg);
self.emit_op(Op::StoreAt);
self.emit_byte(i);
}
self.emit_op(Op::PopN);
self.emit_byte(self.loop_root());
self.emit_op(Op::JumpBack);
self.emit_byte(self.len() - self.loop_idx());
}
Panic(msg) => {
self.visit(msg);
self.emit_op(Op::Panic);
}
Interpolated(parts) => {
self.emit_op(Op::EmptyString);
self.stack_depth += 1;
for part in parts {
let str = &part.0;
match str {
StringPart::Inline(_) => unreachable!(),
StringPart::Data(str) => {
let allocated = Value::String(Rc::new(str.clone()));
self.emit_constant(allocated);
self.emit_op(Op::ConcatStrings);
self.stack_depth -= 1;
}
StringPart::Word(word) => {
self.emit_op(Op::PushBinding);
let biter = self.bindings.iter().rev();
for binding in biter {
if binding.name == word.as_str() {
self.emit_byte(binding.stack_pos);
break;
}
}
self.emit_op(Op::Stringify);
self.emit_op(Op::ConcatStrings);
}
}
}
}
Do(terms) => {
let mut terms = terms.iter();
let first = terms.next().unwrap();
// put the first value on the stack
self.visit(first);
for term in terms {
self.visit(term);
self.emit_op(Op::Call);
self.emit_byte(1);
self.stack_depth -= 1;
}
}
Arguments(..) | Placeholder | InterpolatedPattern(..) | Splattern(..) => {
todo!()
}
And | Or => unreachable!(),
}
}
pub fn disassemble(&self) {
println!("=== chunk: {} ===", self.name);
self.chunk.dissasemble();
}
}