Compare commits
1 Commits
main
...
runtime-pr
| Author | SHA1 | Date |
|---|---|---|
|
|
c81c728d54 |
|
|
@ -3,9 +3,4 @@ name = "leaf"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
649
src/compiler.rs
649
src/compiler.rs
|
|
@ -1,649 +0,0 @@
|
|||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
lexer::{Ident, Literal},
|
||||
parser::Expr,
|
||||
};
|
||||
|
||||
use stack::Stack;
|
||||
|
||||
mod stack;
|
||||
|
||||
struct Scope<'a> {
|
||||
values: Vec<(Ident, Rc<RefMeta>)>,
|
||||
parent: Option<&'a Scope<'a>>,
|
||||
}
|
||||
impl<'a> Stack<'a> for Scope<'a> {
|
||||
type Value = (Ident, Rc<RefMeta>);
|
||||
type Input = Ident;
|
||||
type Output = Rc<RefMeta>;
|
||||
|
||||
fn with_parent(parent: Option<&'a Self>) -> Self {
|
||||
Self {
|
||||
values: Vec::new(),
|
||||
parent,
|
||||
}
|
||||
}
|
||||
fn parent(&self) -> Option<&'a Self> {
|
||||
self.parent
|
||||
}
|
||||
fn values(&self) -> &Vec<Self::Value> {
|
||||
&self.values
|
||||
}
|
||||
fn values_mut(&mut self) -> &mut Vec<Self::Value> {
|
||||
&mut self.values
|
||||
}
|
||||
|
||||
fn find_map(_: usize, value: &Self::Value, input: &Self::Input) -> Option<Self::Output> {
|
||||
(value.0 == *input).then_some(value.1.clone())
|
||||
}
|
||||
}
|
||||
impl Scope<'_> {
|
||||
fn assigned(&mut self, id: Ident) -> Rc<RefMeta> {
|
||||
let Some((rm, _)) = self.find(&id) else {
|
||||
let rm: Rc<RefMeta> = Rc::default();
|
||||
self.push((id, rm.clone()));
|
||||
return rm;
|
||||
};
|
||||
rm
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct FuncMeta {
|
||||
pub is_unreturnable: Cell<bool>,
|
||||
}
|
||||
pub type FuncStat = Rc<FuncMeta>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RefStat {
|
||||
pub now: u16,
|
||||
pub meta: Rc<RefMeta>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RefMeta {
|
||||
pub total: Cell<u16>,
|
||||
pub is_shared: Cell<bool>,
|
||||
}
|
||||
|
||||
fn analyze(fs: &FuncStat, scope: &mut Scope, e: &mut Expr, gets_captured: bool) {
|
||||
match e {
|
||||
Expr::Assign(a, b) => {
|
||||
let Expr::Literal(Literal::Ident(id, ref_stat)) = &mut **a else {
|
||||
panic!("invalid assignment");
|
||||
};
|
||||
|
||||
// add to scope
|
||||
let rm = scope.assigned(id.clone());
|
||||
|
||||
// add ref stat
|
||||
*ref_stat = Some(RefStat {
|
||||
now: rm.total.get(),
|
||||
meta: rm,
|
||||
});
|
||||
|
||||
// analyse the value
|
||||
analyze(fs, scope, b, true);
|
||||
}
|
||||
Expr::Literal(Literal::Ident(id, ref_stat)) => {
|
||||
// lookup ident
|
||||
let Some((rm, up_levels)) = scope.find(id) else {
|
||||
panic!("unfound variable")
|
||||
};
|
||||
|
||||
// the var got used, so the compiler will gen code for it
|
||||
// it is okay to count
|
||||
if gets_captured {
|
||||
// increment # of uses
|
||||
rm.total.update(|c| c + 1);
|
||||
|
||||
// if we used something external to this scope, note it
|
||||
if up_levels != 0 {
|
||||
fs.is_unreturnable.set(true);
|
||||
rm.is_shared.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
// set ref meta
|
||||
*ref_stat = Some(RefStat {
|
||||
now: rm.total.get(),
|
||||
meta: rm.clone(),
|
||||
});
|
||||
}
|
||||
// ignore
|
||||
Expr::Literal(_) => {}
|
||||
// for recursion..
|
||||
Expr::Block(a) => {
|
||||
// blocks have their own scope
|
||||
let mut scope = Scope::with_parent(Some(scope));
|
||||
// last is treated differently
|
||||
let last = a.exprs.pop();
|
||||
// analyze the contents in the new scope
|
||||
for e in &mut a.exprs {
|
||||
analyze(fs, &mut scope, e, false);
|
||||
}
|
||||
// analyze last
|
||||
if let Some(mut last) = last {
|
||||
analyze(fs, &mut scope, &mut last, gets_captured);
|
||||
a.exprs.push(last);
|
||||
}
|
||||
}
|
||||
Expr::Func(a, b, func_stat) => {
|
||||
// new function new context
|
||||
let fs = FuncStat::default();
|
||||
*func_stat = Some(fs.clone());
|
||||
// functions have their own scope, because they have args
|
||||
let mut scope = Scope::with_parent(Some(scope));
|
||||
|
||||
// init args
|
||||
for e in a {
|
||||
let Expr::Literal(Literal::Ident(id, _)) = e else {
|
||||
panic!("invalid arg def");
|
||||
};
|
||||
scope.assigned(id.clone());
|
||||
}
|
||||
|
||||
// now analyze the body in the new scope
|
||||
analyze(&fs, &mut scope, b, true);
|
||||
}
|
||||
Expr::If(a, b, c) => {
|
||||
analyze(fs, scope, a, true);
|
||||
analyze(fs, scope, b, gets_captured);
|
||||
if let Some(c) = c {
|
||||
analyze(fs, scope, c, gets_captured);
|
||||
}
|
||||
}
|
||||
Expr::Call(a, b) => {
|
||||
analyze(fs, scope, a, true);
|
||||
for e in b {
|
||||
analyze(fs, scope, e, true);
|
||||
}
|
||||
}
|
||||
Expr::Return(a) | Expr::Negate(a) | Expr::Not(a) => analyze(fs, scope, a, true),
|
||||
Expr::EqualTo(a, b)
|
||||
| Expr::NotEqualTo(a, b)
|
||||
| Expr::And(a, b)
|
||||
| Expr::Or(a, b)
|
||||
| Expr::LessThan(a, b)
|
||||
| Expr::LessThanOrEqualTo(a, b)
|
||||
| Expr::GreaterThan(a, b)
|
||||
| Expr::GreaterThanOrEqualTo(a, b)
|
||||
| Expr::Add(a, b)
|
||||
| Expr::Subtract(a, b)
|
||||
| Expr::Multiply(a, b)
|
||||
| Expr::Divide(a, b)
|
||||
| Expr::Exponent(a, b)
|
||||
| Expr::Modulo(a, b) => {
|
||||
analyze(fs, scope, a, gets_captured);
|
||||
analyze(fs, scope, b, gets_captured);
|
||||
}
|
||||
Expr::AddAssign(a, b)
|
||||
| Expr::SubtractAssign(a, b)
|
||||
| Expr::MultiplyAssign(a, b)
|
||||
| Expr::DivideAssign(a, b) => {
|
||||
analyze(fs, scope, a, true);
|
||||
analyze(fs, scope, b, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- translate pass --- //
|
||||
|
||||
/* 1b is up? */
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Stkval {
|
||||
/* 12b offset */
|
||||
Local(u8),
|
||||
/* 4b up levels ; 8b offset */
|
||||
Shared(u8, u8),
|
||||
}
|
||||
/* 3b type tag */
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Val {
|
||||
/* where? ; pop after use? */
|
||||
Stack(Stkval, bool),
|
||||
/* u16 len, LEN data */
|
||||
String(String),
|
||||
/* 1b returnability, 4b arity, insts */
|
||||
Func(bool, u8, Vec<Inst>),
|
||||
/* 1b value */
|
||||
Bool(bool),
|
||||
/* i64 data */
|
||||
Int64(i64),
|
||||
/* f64 data */
|
||||
Float64(f64),
|
||||
/* ... */
|
||||
Nil,
|
||||
}
|
||||
/* 3b inst type */
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Inst {
|
||||
/* is shared? ; val to copy */
|
||||
Copy(bool, Val),
|
||||
/* where to write, val to write */
|
||||
Move(Stkval, Val),
|
||||
/* how much to increment PC */
|
||||
CJump(usize),
|
||||
/* push result? ; # args provided ; val to call */
|
||||
Call(bool, u8, Val),
|
||||
/* value to return */
|
||||
Return(Val),
|
||||
/* lhs, rhs */
|
||||
Eq(Val, Val),
|
||||
Gt(Val, Val),
|
||||
GtEq(Val, Val),
|
||||
Add(Val, Val),
|
||||
Mul(Val, Val),
|
||||
Div(Val, Val),
|
||||
Mod(Val, Val),
|
||||
Pow(Val, Val),
|
||||
And(Val, Val),
|
||||
Or(Val, Val),
|
||||
/* rhs */
|
||||
Not(Val),
|
||||
}
|
||||
|
||||
/// Value on fake stack.
|
||||
#[derive(Debug)]
|
||||
enum FSValue {
|
||||
Var(Ident),
|
||||
Any,
|
||||
}
|
||||
|
||||
/// A stack that keeps track of values during translation.
|
||||
/// (Local or shared)
|
||||
struct FakeStack<'a> {
|
||||
values: Vec<FSValue>,
|
||||
parent: Option<&'a FakeStack<'a>>,
|
||||
}
|
||||
impl<'a> Stack<'a> for FakeStack<'a> {
|
||||
type Value = FSValue;
|
||||
type Input = Ident;
|
||||
type Output = usize;
|
||||
|
||||
fn with_parent(parent: Option<&'a Self>) -> Self {
|
||||
Self {
|
||||
values: Vec::new(),
|
||||
parent,
|
||||
}
|
||||
}
|
||||
fn parent(&self) -> Option<&'a Self> {
|
||||
self.parent
|
||||
}
|
||||
fn values(&self) -> &Vec<Self::Value> {
|
||||
&self.values
|
||||
}
|
||||
fn values_mut(&mut self) -> &mut Vec<Self::Value> {
|
||||
&mut self.values
|
||||
}
|
||||
|
||||
fn find_map(index: usize, value: &Self::Value, input: &Self::Input) -> Option<Self::Output> {
|
||||
matches!(value, FSValue::Var(x) if x == input).then_some(index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build scope of a function.
|
||||
///
|
||||
/// * Starts with a base block scope that includes any arguments passed
|
||||
/// * Contains the compiled instructions of all blocks inside it
|
||||
/// * Keeps track of its own shared stack (contains vars that higher functions access)
|
||||
/// * Keeps track of its own local stack (vars only used locally)
|
||||
struct FuncBuild<'a> {
|
||||
insts: Vec<Inst>,
|
||||
shared: FakeStack<'a>,
|
||||
local: FakeStack<'a>,
|
||||
}
|
||||
|
||||
impl<'a> FuncBuild<'a> {
|
||||
fn new_root() -> Self {
|
||||
FuncBuild {
|
||||
insts: Vec::new(),
|
||||
shared: FakeStack::with_parent(None),
|
||||
local: FakeStack::with_parent(None),
|
||||
}
|
||||
}
|
||||
fn with_parent(parent: &'a FuncBuild<'a>) -> Self {
|
||||
FuncBuild {
|
||||
insts: Vec::new(),
|
||||
shared: FakeStack::with_parent(Some(&parent.shared)),
|
||||
local: FakeStack::with_parent(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn find(&mut self, id: &Ident) -> Option<Stkval> {
|
||||
self.shared
|
||||
.find(id)
|
||||
.map(|(count, up_levels)| Stkval::Shared(up_levels as u8, count as u8))
|
||||
.or_else(|| self.local.find(id).map(|(c, _)| Stkval::Local(c as u8)))
|
||||
}
|
||||
|
||||
/// Returns stackval for top item of stack.
|
||||
/// (Panics if empty)
|
||||
fn top(&self) -> Stkval {
|
||||
Stkval::Local(self.local.top_index() as u8)
|
||||
}
|
||||
/// Returns stackval for top item of shared stack.
|
||||
/// (Panics if empty)
|
||||
fn top_shared(&self) -> Stkval {
|
||||
Stkval::Shared(0, self.shared.top_index() as u8)
|
||||
}
|
||||
|
||||
/// Pushes a value to stack and returns its stackval.
|
||||
fn push_any(&mut self) -> Stkval {
|
||||
self.local.push(FSValue::Any);
|
||||
self.top()
|
||||
}
|
||||
|
||||
fn check_drop(&mut self, v: &Val) {
|
||||
if let Val::Stack(Stkval::Local(i), true) = v {
|
||||
self.local.pop(*i as usize);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_drops<const N: usize>(&mut self, mut vl: [&mut Val; N]) {
|
||||
use {Stkval::*, Val::*};
|
||||
|
||||
// Sort low->high
|
||||
vl.sort_by_key(|v| match v {
|
||||
Val::Stack(Stkval::Local(o), _) => *o as i16,
|
||||
// It doesn't matter
|
||||
_ => 0,
|
||||
});
|
||||
|
||||
// Fix indices
|
||||
let mut to_pop = Vec::new();
|
||||
for v in vl {
|
||||
if let Stack(Local(o), p) = v {
|
||||
*o -= to_pop.len() as u8;
|
||||
if *p {
|
||||
to_pop.push(*o as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop
|
||||
for o in to_pop {
|
||||
self.local.pop(o);
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_unop(&mut self, r: Expr, f: impl Fn(Val) -> Inst, do_compute: bool) -> Val {
|
||||
let v1 = self.translate(r, do_compute, false);
|
||||
|
||||
// Don't compute anything unnecessarily
|
||||
if !do_compute {
|
||||
return Val::Nil;
|
||||
}
|
||||
|
||||
self.check_drop(&v1);
|
||||
|
||||
self.insts.push(f(v1));
|
||||
Val::Stack(self.push_any(), true)
|
||||
}
|
||||
fn gen_binop(
|
||||
&mut self,
|
||||
l: Expr,
|
||||
r: Expr,
|
||||
f: impl Fn(Val, Val) -> Inst,
|
||||
do_compute: bool,
|
||||
) -> Val {
|
||||
let (mut v1, mut v2) = (
|
||||
self.translate(l, do_compute, false),
|
||||
self.translate(r, do_compute, false),
|
||||
);
|
||||
|
||||
// If this is unused, do not generate code
|
||||
if !do_compute {
|
||||
return Val::Nil;
|
||||
}
|
||||
|
||||
self.check_drops([&mut v1, &mut v2]);
|
||||
|
||||
self.insts.push(f(v1, v2));
|
||||
Val::Stack(self.push_any(), true)
|
||||
}
|
||||
fn gen_copy(&mut self, v1: Val) -> Val {
|
||||
self.check_drop(&v1);
|
||||
self.insts.push(Inst::Copy(false, v1));
|
||||
Val::Stack(self.push_any(), false)
|
||||
}
|
||||
|
||||
fn translate(&mut self, e: Expr, do_compute: bool, do_yield: bool) -> Val {
|
||||
match e {
|
||||
/* organisational */
|
||||
Expr::Block(mut b) => {
|
||||
let last = b.exprs.pop();
|
||||
for e in b.exprs {
|
||||
self.translate(e, false, false);
|
||||
}
|
||||
// compute/yield last expr if requested
|
||||
last.map_or(Val::Nil, |e| self.translate(e, do_compute, do_yield))
|
||||
}
|
||||
Expr::Func(args, expr, fs) => {
|
||||
// neww function!!!!
|
||||
let mut fb = FuncBuild::with_parent(self);
|
||||
// push args to stack
|
||||
let arity = args.len() as u8;
|
||||
for arg in args {
|
||||
let Expr::Literal(Literal::Ident(id, _)) = arg else {
|
||||
unreachable!()
|
||||
};
|
||||
fb.local.push(FSValue::Var(id));
|
||||
}
|
||||
// translate expr
|
||||
fb.translate(*expr, true, false);
|
||||
// pack
|
||||
let returnability = fs.unwrap().is_unreturnable.get();
|
||||
Val::Func(returnability, arity, fb.insts)
|
||||
}
|
||||
|
||||
/* control flow */
|
||||
Expr::Return(r) => {
|
||||
// calculate return value
|
||||
let v1 = self.translate(*r, true, false);
|
||||
// add return inst, eval to nil
|
||||
self.insts.push(Inst::Return(v1));
|
||||
Val::Nil
|
||||
}
|
||||
Expr::Call(func, args) => {
|
||||
// yield all args to stack
|
||||
let n_args = args.len();
|
||||
for arg in args {
|
||||
self.translate(arg, true, true);
|
||||
}
|
||||
// pop all of them
|
||||
self.local.pop_top_n(n_args);
|
||||
|
||||
// get the function
|
||||
let v1 = self.translate(*func, true, false);
|
||||
self.check_drop(&v1);
|
||||
|
||||
// decide if we push result to stack
|
||||
// if we are computing a value or yielding one, then yes
|
||||
let push = do_compute || do_yield;
|
||||
|
||||
// add call
|
||||
self.insts.push(Inst::Call(push, n_args as u8, v1));
|
||||
|
||||
// whatever we output
|
||||
if push {
|
||||
Val::Stack(self.push_any(), true)
|
||||
} else {
|
||||
Val::Nil
|
||||
}
|
||||
}
|
||||
|
||||
/* captured literal */
|
||||
Expr::Literal(lit) if do_yield => {
|
||||
let v1 = self.translate(Expr::Literal(lit), true, false);
|
||||
self.gen_copy(v1)
|
||||
}
|
||||
|
||||
/* 1 to 1 literals */
|
||||
Expr::Literal(Literal::Boolean(b)) => Val::Bool(b),
|
||||
Expr::Literal(Literal::Float(f)) => Val::Float64(f),
|
||||
Expr::Literal(Literal::Integer(i)) => Val::Int64(i),
|
||||
Expr::Literal(Literal::Nil) => Val::Nil,
|
||||
Expr::Literal(Literal::String(s)) => Val::String(s),
|
||||
|
||||
/* vars */
|
||||
Expr::Literal(Literal::Ident(id, Some(rs))) if do_compute => {
|
||||
let is_last_use = rs.now == rs.meta.total.get();
|
||||
let is_shared = rs.meta.is_shared.get();
|
||||
Val::Stack(self.find(&id).unwrap(), is_last_use && !is_shared)
|
||||
}
|
||||
Expr::Literal(Literal::Ident(_, _)) => Val::Nil,
|
||||
Expr::Assign(l, r) => {
|
||||
let Expr::Literal(Literal::Ident(id, Some(ref_stat))) = *l else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// will the var ever get referenced?
|
||||
let gets_referenced = ref_stat.now != ref_stat.meta.total.get();
|
||||
|
||||
// if this isn't getting used for computation OR referenced,
|
||||
// just continue translation without adding to stack
|
||||
if !(do_compute || gets_referenced) {
|
||||
self.translate(*r, false, false)
|
||||
} else {
|
||||
// get val
|
||||
let val = match *r {
|
||||
// the var's value is a literal
|
||||
// if the var gets used as a var, yield to stack
|
||||
// otherwise just return the literal
|
||||
Expr::Literal(lit) => {
|
||||
self.translate(Expr::Literal(lit), true, gets_referenced)
|
||||
}
|
||||
|
||||
// value is an expr
|
||||
// compute it and yield to stack if it gets used
|
||||
e => self.translate(e, true, gets_referenced),
|
||||
};
|
||||
|
||||
// handle value
|
||||
let val = match val {
|
||||
// apply appropriate drop rule
|
||||
Val::Stack(sv, _) => Val::Stack(sv, gets_referenced),
|
||||
// non-literal type
|
||||
val if matches!(val, Val::Func(_, _, _)) => self.gen_copy(val),
|
||||
// okay as-is
|
||||
val => val,
|
||||
};
|
||||
|
||||
// check if var already exists
|
||||
if let Some(sv) = self.find(&id) {
|
||||
// yes, move it there
|
||||
self.insts.push(Inst::Move(sv.clone(), val));
|
||||
self.local.pop_top();
|
||||
// find out if it should be popped
|
||||
let is_shared = matches!(sv, Stkval::Shared(_, _));
|
||||
let should_pop = gets_referenced && !is_shared;
|
||||
// return new stackval
|
||||
return Val::Stack(sv, should_pop);
|
||||
} else if matches!(val, Val::Stack(_, _)) {
|
||||
// no, keep track of new stackval
|
||||
self.local.swap_top(FSValue::Var(id));
|
||||
|
||||
// also move to shared if we're supposed to do that :)
|
||||
if ref_stat.meta.is_shared.get()
|
||||
&& let Val::Stack(sv, _) = val
|
||||
{
|
||||
// move fs value
|
||||
let v = self.local.pop_top();
|
||||
self.shared.push(v);
|
||||
// copy to shared w pop
|
||||
self.insts.push(Inst::Copy(true, Val::Stack(sv, true)));
|
||||
return Val::Stack(self.top_shared(), false);
|
||||
}
|
||||
}
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
/* math */
|
||||
Expr::Add(l, r) => self.gen_binop(*l, *r, Inst::Add, do_compute),
|
||||
Expr::Multiply(l, r) => self.gen_binop(*l, *r, Inst::Mul, do_compute),
|
||||
Expr::Divide(l, r) => self.gen_binop(*l, *r, Inst::Div, do_compute),
|
||||
Expr::Modulo(l, r) => self.gen_binop(*l, *r, Inst::Mod, do_compute),
|
||||
Expr::Exponent(l, r) => self.gen_binop(*l, *r, Inst::Pow, do_compute),
|
||||
|
||||
/* math assignments */
|
||||
Expr::AddAssign(l, r) => self.translate(
|
||||
Expr::Assign(l.clone(), Box::new(Expr::Add(l, r))),
|
||||
do_compute,
|
||||
do_yield,
|
||||
),
|
||||
Expr::SubtractAssign(l, r) => self.translate(
|
||||
Expr::Assign(l.clone(), Box::new(Expr::Subtract(l, r))),
|
||||
do_compute,
|
||||
do_yield,
|
||||
),
|
||||
Expr::MultiplyAssign(l, r) => self.translate(
|
||||
Expr::Assign(l.clone(), Box::new(Expr::Multiply(l, r))),
|
||||
do_compute,
|
||||
do_yield,
|
||||
),
|
||||
Expr::DivideAssign(l, r) => self.translate(
|
||||
Expr::Assign(l.clone(), Box::new(Expr::Divide(l, r))),
|
||||
do_compute,
|
||||
do_yield,
|
||||
),
|
||||
|
||||
/* math substitutions */
|
||||
Expr::Negate(r) if do_compute => {
|
||||
// negate
|
||||
match *r {
|
||||
// statically
|
||||
Expr::Literal(Literal::Integer(i)) => Val::Int64(-i),
|
||||
Expr::Literal(Literal::Float(f)) => Val::Float64(-f),
|
||||
|
||||
// at runtime
|
||||
e => {
|
||||
let e = Box::new(e);
|
||||
let minus_one = Box::new(Expr::Literal(Literal::Integer(-1)));
|
||||
self.translate(Expr::Multiply(e, minus_one), do_compute, do_yield)
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Negate(_) => Val::Nil,
|
||||
Expr::Subtract(l, r) => self.translate(
|
||||
Expr::Add(l, Box::new(Expr::Negate(r))),
|
||||
do_compute,
|
||||
do_yield,
|
||||
),
|
||||
|
||||
/* logic */
|
||||
Expr::And(l, r) => self.gen_binop(*l, *r, Inst::And, do_compute),
|
||||
Expr::Or(l, r) => self.gen_binop(*l, *r, Inst::Or, do_compute),
|
||||
Expr::EqualTo(l, r) => self.gen_binop(*l, *r, Inst::Eq, do_compute),
|
||||
Expr::GreaterThan(l, r) => self.gen_binop(*l, *r, Inst::Gt, do_compute),
|
||||
Expr::GreaterThanOrEqualTo(l, r) => self.gen_binop(*l, *r, Inst::GtEq, do_compute),
|
||||
Expr::Not(r) => self.gen_unop(*r, Inst::Not, do_compute),
|
||||
|
||||
/* logic substitutions */
|
||||
Expr::NotEqualTo(l, r) => {
|
||||
self.translate(Expr::Not(Box::new(Expr::EqualTo(l, r))), do_compute, false)
|
||||
}
|
||||
Expr::LessThan(l, r) => self.translate(Expr::GreaterThan(r, l), do_compute, false),
|
||||
Expr::LessThanOrEqualTo(l, r) => {
|
||||
self.translate(Expr::GreaterThanOrEqualTo(r, l), do_compute, false)
|
||||
}
|
||||
|
||||
e => unimplemented!("{e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analysis_demo(e: &mut Expr) {
|
||||
// analysis pass
|
||||
let fs = FuncStat::default();
|
||||
let mut scope = Scope::with_parent(None);
|
||||
analyze(&fs, &mut scope, e, false);
|
||||
}
|
||||
pub fn translation_demo(e: Expr) -> Vec<Inst> {
|
||||
// translation pass
|
||||
let mut fb = FuncBuild::new_root();
|
||||
fb.translate(e, false, false);
|
||||
fb.insts
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
pub trait Stack<'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
type Value;
|
||||
type Input;
|
||||
type Output;
|
||||
|
||||
fn with_parent(parent: Option<&'a Self>) -> Self;
|
||||
|
||||
fn parent(&self) -> Option<&'a Self>;
|
||||
fn values(&self) -> &Vec<Self::Value>;
|
||||
fn values_mut(&mut self) -> &mut Vec<Self::Value>;
|
||||
|
||||
fn find_map(index: usize, value: &Self::Value, input: &Self::Input) -> Option<Self::Output>;
|
||||
|
||||
fn push(&mut self, value: Self::Value) {
|
||||
self.values_mut().push(value);
|
||||
}
|
||||
fn pop(&mut self, index: usize) {
|
||||
self.values_mut().remove(index);
|
||||
}
|
||||
fn top_index(&self) -> usize {
|
||||
self.values().len() - 1
|
||||
}
|
||||
fn swap_top(&mut self, new: Self::Value) {
|
||||
*self.values_mut().last_mut().unwrap() = new;
|
||||
}
|
||||
fn pop_top(&mut self) -> Self::Value {
|
||||
self.values_mut().pop().unwrap()
|
||||
}
|
||||
fn pop_top_n(&mut self, n: usize) -> Vec<Self::Value> {
|
||||
let start = self.values().len() - n;
|
||||
self.values_mut().split_off(start)
|
||||
}
|
||||
|
||||
fn find(&self, input: &Self::Input) -> Option<(Self::Output, u16)> {
|
||||
let mut cur = Some(self);
|
||||
let mut up_levels = 0;
|
||||
|
||||
while let Some(stack) = cur {
|
||||
let Some(output) = stack
|
||||
.values()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.find_map(|(index, value)| Self::find_map(index, value, input))
|
||||
else {
|
||||
cur = stack.parent();
|
||||
up_levels += 1;
|
||||
continue;
|
||||
};
|
||||
return Some((output, up_levels));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
36
src/kind.rs
36
src/kind.rs
|
|
@ -1,36 +0,0 @@
|
|||
pub trait Kind {
|
||||
type Kinds;
|
||||
|
||||
fn kind(&self) -> Self::Kinds;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kinds {
|
||||
($base:ident, $kind:ident, $( $v:ident $( ( $($vty:ty = $vval:expr),* ) )?),* $(,)?) => {
|
||||
#[derive(Debug)]
|
||||
pub enum $base {
|
||||
$( $v $( ( $($vty),* ) )?, )*
|
||||
}
|
||||
impl $crate::kind::Kind for $base {
|
||||
type Kinds = $kind;
|
||||
|
||||
fn kind(&self) -> $kind {
|
||||
$kind(std::mem::discriminant(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct $kind(std::mem::Discriminant<$base>);
|
||||
|
||||
impl $kind {
|
||||
$(
|
||||
#[allow(non_upper_case_globals, dead_code)]
|
||||
pub const $v: Self = $kind (
|
||||
std::mem::discriminant(
|
||||
&( $base::$v $( ( $($vval),* ) )? )
|
||||
)
|
||||
);
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
195
src/lexer.rs
195
src/lexer.rs
|
|
@ -1,100 +1,86 @@
|
|||
use std::{
|
||||
fmt,
|
||||
iter::Peekable,
|
||||
num::{ParseFloatError, ParseIntError},
|
||||
};
|
||||
use std::{fmt, iter::Peekable, rc::Rc};
|
||||
|
||||
use crate::{compiler::RefStat, kinds};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Ident(String);
|
||||
impl fmt::Display for Ident {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for Ident {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum Literal {
|
||||
String(String),
|
||||
String(Rc<String>),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Boolean(bool),
|
||||
Nil,
|
||||
Ident(Ident, Option<RefStat>),
|
||||
Ident(Ident),
|
||||
}
|
||||
impl fmt::Display for Literal {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Literal::String(s) => write!(f, "\"{s}\""),
|
||||
Literal::Integer(n) => write!(f, "{n}"),
|
||||
Literal::Float(n) => write!(f, "{n}"),
|
||||
Literal::Boolean(b) => write!(f, "{b}"),
|
||||
Literal::Ident(id, ref_stat) => write!(
|
||||
f,
|
||||
"{id}{}",
|
||||
ref_stat
|
||||
.as_ref()
|
||||
.map(|rs| format!(
|
||||
"@{}{}/{}",
|
||||
rs.meta.is_shared.get().then_some("sh+").unwrap_or(""),
|
||||
rs.now,
|
||||
rs.meta.total.get()
|
||||
))
|
||||
.unwrap_or_default()
|
||||
),
|
||||
Literal::Ident(id) => write!(f, "{id}"),
|
||||
Literal::Nil => write!(f, "nil"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kinds!(
|
||||
Token,
|
||||
TokenKind,
|
||||
#[derive(Debug)]
|
||||
pub enum Token {
|
||||
Equals,
|
||||
|
||||
Plus,
|
||||
Minus,
|
||||
Star,
|
||||
Slash,
|
||||
Percent,
|
||||
StarStar,
|
||||
PlusEquals,
|
||||
MinusEquals,
|
||||
StarEquals,
|
||||
SlashEquals,
|
||||
Caret,
|
||||
|
||||
CurlyOpen,
|
||||
CurlyClose,
|
||||
|
||||
ParenOpen,
|
||||
ParenClose,
|
||||
|
||||
Comma,
|
||||
Eol,
|
||||
Func,
|
||||
If,
|
||||
Else,
|
||||
|
||||
Return,
|
||||
|
||||
Not,
|
||||
|
||||
EqualTo,
|
||||
NotEqualTo,
|
||||
|
||||
And,
|
||||
Or,
|
||||
|
||||
LessThan,
|
||||
LessThanOrEqualTo,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqualTo,
|
||||
Literal(Literal = Literal::Nil),
|
||||
);
|
||||
|
||||
Literal(Literal),
|
||||
}
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
pub enum Precedence {
|
||||
Min,
|
||||
Return,
|
||||
Assign,
|
||||
WithAssign,
|
||||
Logical,
|
||||
Equality,
|
||||
Relational,
|
||||
AddSub,
|
||||
MulDivMod,
|
||||
MulDiv,
|
||||
Pow,
|
||||
Prefix,
|
||||
NegateNot,
|
||||
}
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Associativity {
|
||||
|
|
@ -104,9 +90,8 @@ pub enum Associativity {
|
|||
impl Token {
|
||||
pub fn prefix_precedence(&self) -> Option<Precedence> {
|
||||
Some(match self {
|
||||
Token::Return | Token::If | Token::Func | Token::Minus | Token::Not => {
|
||||
Precedence::Prefix
|
||||
}
|
||||
Token::Return => Precedence::Return,
|
||||
Token::Minus | Token::Not => Precedence::NegateNot,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
@ -119,14 +104,9 @@ impl Token {
|
|||
| Token::GreaterThanOrEqualTo => (Precedence::Relational, Associativity::Left),
|
||||
Token::And | Token::Or => (Precedence::Logical, Associativity::Left),
|
||||
Token::Plus | Token::Minus => (Precedence::AddSub, Associativity::Left),
|
||||
Token::Star | Token::Slash | Token::Percent => {
|
||||
(Precedence::MulDivMod, Associativity::Left)
|
||||
}
|
||||
Token::StarStar => (Precedence::Pow, Associativity::Right),
|
||||
Token::Star | Token::Slash => (Precedence::MulDiv, Associativity::Left),
|
||||
Token::Caret => (Precedence::Pow, Associativity::Right),
|
||||
Token::Equals => (Precedence::Assign, Associativity::Right),
|
||||
Token::PlusEquals | Token::MinusEquals | Token::StarEquals | Token::SlashEquals => {
|
||||
(Precedence::WithAssign, Associativity::Right)
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
@ -134,8 +114,6 @@ impl Token {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum LexError {
|
||||
InvalidInteger(ParseIntError),
|
||||
InvalidFloat(ParseFloatError),
|
||||
InvalidEscape(char),
|
||||
UnexpectedCharacter(char),
|
||||
UnexpectedEnd,
|
||||
|
|
@ -143,24 +121,12 @@ pub enum LexError {
|
|||
impl fmt::Display for LexError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::InvalidInteger(err) => write!(f, "invalid integer: {err}"),
|
||||
Self::InvalidFloat(err) => write!(f, "invalid float: {err}"),
|
||||
Self::UnexpectedEnd => write!(f, "unexpected end of source"),
|
||||
Self::UnexpectedCharacter(c) => write!(f, "unexpected char '{c}'"),
|
||||
Self::InvalidEscape(c) => write!(f, "\"\\{c}\" is not a valid string escape"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ParseIntError> for LexError {
|
||||
fn from(err: ParseIntError) -> Self {
|
||||
Self::InvalidInteger(err)
|
||||
}
|
||||
}
|
||||
impl From<ParseFloatError> for LexError {
|
||||
fn from(err: ParseFloatError) -> Self {
|
||||
Self::InvalidFloat(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, LexError>;
|
||||
|
||||
|
|
@ -171,11 +137,6 @@ where
|
|||
chars: Peekable<I>,
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn t(tk: Token) -> Option<Result<Token>> {
|
||||
Some(Ok(tk))
|
||||
}
|
||||
|
||||
impl<I> Lexer<I>
|
||||
where
|
||||
I: Iterator<Item = char>,
|
||||
|
|
@ -209,7 +170,6 @@ where
|
|||
fn eat(&mut self) {
|
||||
self.next();
|
||||
}
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn eat_to(&mut self, tk: Token) -> Option<Result<Token>> {
|
||||
self.eat();
|
||||
Some(Ok(tk))
|
||||
|
|
@ -222,7 +182,7 @@ where
|
|||
fn lex_whitespace(&mut self) -> Option<char> {
|
||||
loop {
|
||||
match self.peek()? {
|
||||
' ' | '\t' | '\n' | '\r' => self.eat(),
|
||||
' ' | '\t' | '\r' => self.eat(),
|
||||
_ => break self.peek(),
|
||||
}
|
||||
}
|
||||
|
|
@ -236,38 +196,29 @@ where
|
|||
}
|
||||
|
||||
match word.as_str() {
|
||||
"func" => Token::Func,
|
||||
"if" => Token::If,
|
||||
"else" => Token::Else,
|
||||
"return" => Token::Return,
|
||||
"true" => Token::Literal(Literal::Boolean(true)),
|
||||
"false" => Token::Literal(Literal::Boolean(false)),
|
||||
"nil" => Token::Literal(Literal::Nil),
|
||||
_ => Token::Literal(Literal::Ident(Ident(word), Option::default())),
|
||||
_ => Token::Literal(Literal::Ident(Ident(word))),
|
||||
}
|
||||
}
|
||||
|
||||
fn lex_number(&mut self) -> Result<Token> {
|
||||
fn lex_integer(&mut self) -> Token {
|
||||
let mut n_str = String::new();
|
||||
|
||||
// we don't lex negatives. the impl for that is
|
||||
// a negation of a positive number at runtime.
|
||||
// maybe that's kind of stupid though, lol
|
||||
let mut is_float = false;
|
||||
while let Some('0'..='9' | '.') = self.peek() {
|
||||
if self.peek() == Some('.') {
|
||||
is_float = true;
|
||||
}
|
||||
while let Some('0'..='9') = self.peek() {
|
||||
n_str.push(self.next_unwrap());
|
||||
}
|
||||
|
||||
let lit = if is_float {
|
||||
Literal::Float(n_str.parse()?)
|
||||
} else {
|
||||
Literal::Integer(n_str.parse()?)
|
||||
};
|
||||
// we can only read digits 0 to 9 so this should not fail
|
||||
// .. unless we overflow
|
||||
let n = n_str.parse().unwrap();
|
||||
|
||||
Ok(Token::Literal(lit))
|
||||
Token::Literal(Literal::Integer(n))
|
||||
}
|
||||
|
||||
fn lex_string(&mut self) -> Result<Token> {
|
||||
|
|
@ -288,7 +239,7 @@ where
|
|||
},
|
||||
c if c == delim => {
|
||||
self.eat();
|
||||
break Ok(Token::Literal(Literal::String(str)));
|
||||
break Ok(Token::Literal(Literal::String(Rc::new(str))));
|
||||
}
|
||||
_ => str.push(self.next_unwrap()),
|
||||
}
|
||||
|
|
@ -307,46 +258,19 @@ where
|
|||
')' => self.eat_to(Token::ParenClose),
|
||||
|
||||
// + add
|
||||
// or += add eq
|
||||
'+' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::PlusEquals),
|
||||
_ => t(Token::Plus),
|
||||
},
|
||||
'+' => self.eat_to(Token::Plus),
|
||||
|
||||
// - subtract
|
||||
// or -= sub eq
|
||||
'-' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::MinusEquals),
|
||||
_ => t(Token::Minus),
|
||||
},
|
||||
'-' => self.eat_to(Token::Minus),
|
||||
|
||||
// * multiply
|
||||
// or *= mult eq
|
||||
// or ** pow
|
||||
'*' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::StarEquals),
|
||||
Some('*') => self.eat_to(Token::StarStar),
|
||||
_ => t(Token::Star),
|
||||
},
|
||||
'*' => self.eat_to(Token::Star),
|
||||
|
||||
// / divide
|
||||
// or /= div eq
|
||||
// or // comment
|
||||
'/' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::SlashEquals),
|
||||
Some('/') => {
|
||||
// skip the rest of the line
|
||||
// this leaves the newline btw
|
||||
while !matches!(self.peek(), Some('\n') | None) {
|
||||
self.eat();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ => t(Token::Slash),
|
||||
},
|
||||
'/' => self.eat_to(Token::Slash),
|
||||
|
||||
// % modulo
|
||||
'%' => self.eat_to(Token::Percent),
|
||||
// ^ pow
|
||||
'^' => self.eat_to(Token::Caret),
|
||||
|
||||
// , comma
|
||||
',' => self.eat_to(Token::Comma),
|
||||
|
|
@ -355,14 +279,14 @@ where
|
|||
// or == equal to
|
||||
'=' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::EqualTo),
|
||||
_ => t(Token::Equals),
|
||||
_ => Some(Ok(Token::Equals)),
|
||||
},
|
||||
|
||||
// ! not
|
||||
// or != not equal to
|
||||
'!' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::NotEqualTo),
|
||||
_ => t(Token::Not),
|
||||
_ => Some(Ok(Token::Not)),
|
||||
},
|
||||
|
||||
// && and
|
||||
|
|
@ -375,25 +299,38 @@ where
|
|||
// or >= greater than/equal to
|
||||
'>' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::GreaterThanOrEqualTo),
|
||||
_ => t(Token::GreaterThan),
|
||||
_ => Some(Ok(Token::GreaterThan)),
|
||||
},
|
||||
|
||||
// < less than
|
||||
// or <= less than/equal to
|
||||
'<' => match self.eat_peek() {
|
||||
Some('=') => self.eat_to(Token::LessThanOrEqualTo),
|
||||
_ => t(Token::LessThan),
|
||||
_ => Some(Ok(Token::LessThan)),
|
||||
},
|
||||
|
||||
// a-zA-Z_ start of word
|
||||
'a'..='z' | 'A'..='Z' | '_' => Some(Ok(self.lex_word())),
|
||||
|
||||
// 0-9 integer
|
||||
'0'..='9' | '.' => Some(self.lex_number()),
|
||||
'0'..='9' => Some(Ok(self.lex_integer())),
|
||||
|
||||
// " strings
|
||||
'"' => Some(self.lex_string()),
|
||||
|
||||
// # comments
|
||||
'#' => {
|
||||
// skip the rest of the line
|
||||
// this leaves the newline btw
|
||||
while !matches!(self.peek(), Some('\n') | None) {
|
||||
self.eat();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ;, \n eol
|
||||
'\n' | ';' => self.eat_to(Token::Eol),
|
||||
|
||||
// unexpected character
|
||||
c => Some(Err(LexError::UnexpectedCharacter(c))),
|
||||
};
|
||||
|
|
|
|||
35
src/main.rs
35
src/main.rs
|
|
@ -1,38 +1,21 @@
|
|||
use std::time::Instant;
|
||||
use std::{rc::Rc, time::Instant};
|
||||
|
||||
use crate::{lexer::Lexer, parser::Parser};
|
||||
use crate::{
|
||||
lexer::Lexer,
|
||||
parser::{Expr, Parser},
|
||||
};
|
||||
|
||||
mod compiler;
|
||||
mod kind;
|
||||
mod lexer;
|
||||
mod parser;
|
||||
mod vm;
|
||||
mod runtime;
|
||||
|
||||
fn main() {
|
||||
let script = std::fs::read_to_string("./start.lf").unwrap();
|
||||
let script = std::fs::read_to_string("./start.leaf").unwrap();
|
||||
let lexer = Lexer::new(script.chars());
|
||||
let mut parser = Parser::new(lexer.map(Result::unwrap));
|
||||
|
||||
let start = Instant::now();
|
||||
let block = parser.parse().unwrap();
|
||||
println!("Parse took {:?}", start.elapsed());
|
||||
let mut e = parser::Expr::Block(block);
|
||||
parser::util::display(&e);
|
||||
|
||||
let start = Instant::now();
|
||||
compiler::analysis_demo(&mut e);
|
||||
println!("Analysis took {:?}", start.elapsed());
|
||||
parser::util::display(&e);
|
||||
|
||||
let start = Instant::now();
|
||||
let insts = compiler::translation_demo(e);
|
||||
println!("Translation took {:?}", start.elapsed());
|
||||
for i in &insts {
|
||||
println!("=> {i:?}");
|
||||
}
|
||||
|
||||
println!("Starting VM!!!!!!!!!!!!!!!!");
|
||||
let start = Instant::now();
|
||||
let out = vm::run(&insts);
|
||||
println!("!! Got result (in {:?}): {out:?}", start.elapsed());
|
||||
// parser::util::display(&Rc::new(Expr::Block(Rc::new(block))));
|
||||
runtime::exec(&block).unwrap();
|
||||
}
|
||||
|
|
|
|||
250
src/parser.rs
250
src/parser.rs
|
|
@ -1,55 +1,43 @@
|
|||
use std::{fmt, iter::Peekable};
|
||||
use std::{fmt, iter::Peekable, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
compiler::FuncStat,
|
||||
kind::Kind,
|
||||
lexer::{Associativity, LexError, Literal, Precedence, Token, TokenKind},
|
||||
};
|
||||
use crate::lexer::{Associativity, LexError, Literal, Precedence, Token};
|
||||
|
||||
pub mod util;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum Expr {
|
||||
// Data and variables
|
||||
Assign(Box<Expr>, Box<Expr>),
|
||||
Assignment(Rc<Expr>, Rc<Expr>),
|
||||
Literal(Literal),
|
||||
// Non-literal datatypes
|
||||
Block(Block),
|
||||
Func(Vec<Expr>, Box<Expr>, Option<FuncStat>),
|
||||
// Runtime datatypes
|
||||
Block(Rc<Block>),
|
||||
// Control flow
|
||||
If(Box<Expr>, Box<Expr>, Option<Box<Expr>>),
|
||||
Return(Box<Expr>),
|
||||
Call(Box<Expr>, Vec<Expr>),
|
||||
Return(Rc<Expr>),
|
||||
Call(Rc<Expr>, Vec<Rc<Expr>>),
|
||||
// Unary operations
|
||||
Negate(Box<Expr>),
|
||||
Not(Box<Expr>),
|
||||
Negate(Rc<Expr>),
|
||||
Not(Rc<Expr>),
|
||||
// Binary operations: logical
|
||||
EqualTo(Box<Expr>, Box<Expr>),
|
||||
NotEqualTo(Box<Expr>, Box<Expr>),
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
EqualTo(Rc<Expr>, Rc<Expr>),
|
||||
NotEqualTo(Rc<Expr>, Rc<Expr>),
|
||||
And(Rc<Expr>, Rc<Expr>),
|
||||
Or(Rc<Expr>, Rc<Expr>),
|
||||
// Binary operations: comparison
|
||||
LessThan(Box<Expr>, Box<Expr>),
|
||||
LessThanOrEqualTo(Box<Expr>, Box<Expr>),
|
||||
GreaterThan(Box<Expr>, Box<Expr>),
|
||||
GreaterThanOrEqualTo(Box<Expr>, Box<Expr>),
|
||||
LessThan(Rc<Expr>, Rc<Expr>),
|
||||
LessThanOrEqualTo(Rc<Expr>, Rc<Expr>),
|
||||
GreaterThan(Rc<Expr>, Rc<Expr>),
|
||||
GreaterThanOrEqualTo(Rc<Expr>, Rc<Expr>),
|
||||
// Binary operations: arithmetic
|
||||
Add(Box<Expr>, Box<Expr>),
|
||||
Subtract(Box<Expr>, Box<Expr>),
|
||||
Multiply(Box<Expr>, Box<Expr>),
|
||||
Divide(Box<Expr>, Box<Expr>),
|
||||
Exponent(Box<Expr>, Box<Expr>),
|
||||
Modulo(Box<Expr>, Box<Expr>),
|
||||
// Binary operations: arithmetic w/ assignment
|
||||
AddAssign(Box<Expr>, Box<Expr>),
|
||||
SubtractAssign(Box<Expr>, Box<Expr>),
|
||||
MultiplyAssign(Box<Expr>, Box<Expr>),
|
||||
DivideAssign(Box<Expr>, Box<Expr>),
|
||||
Add(Rc<Expr>, Rc<Expr>),
|
||||
Subtract(Rc<Expr>, Rc<Expr>),
|
||||
Multiply(Rc<Expr>, Rc<Expr>),
|
||||
Divide(Rc<Expr>, Rc<Expr>),
|
||||
Exponent(Rc<Expr>, Rc<Expr>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Block {
|
||||
pub exprs: Vec<Expr>,
|
||||
pub exprs: Vec<Rc<Expr>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -77,6 +65,7 @@ pub type Result<T> = std::result::Result<T, ParseError>;
|
|||
|
||||
pub struct Parser<I: Iterator<Item = Token>> {
|
||||
tokens: Peekable<I>,
|
||||
is_next_eol: bool,
|
||||
}
|
||||
impl<I> Parser<I>
|
||||
where
|
||||
|
|
@ -84,7 +73,10 @@ where
|
|||
{
|
||||
pub fn new(tokens: I) -> Self {
|
||||
let tokens = tokens.peekable();
|
||||
Self { tokens }
|
||||
Self {
|
||||
tokens,
|
||||
is_next_eol: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn eat(&mut self) {
|
||||
|
|
@ -93,37 +85,33 @@ where
|
|||
fn next_unwrap(&mut self) -> Token {
|
||||
self.try_next().unwrap()
|
||||
}
|
||||
fn skip_eol(&mut self) -> bool {
|
||||
let mut did_skip = false;
|
||||
|
||||
while matches!(self.tokens.peek(), Some(Token::Eol)) {
|
||||
self.tokens.next();
|
||||
did_skip = true;
|
||||
}
|
||||
|
||||
did_skip
|
||||
}
|
||||
fn try_peek(&mut self) -> Result<&Token> {
|
||||
// Peek doesn't advance the token stream, so
|
||||
// don't allow it to unset the EOL flag
|
||||
if self.skip_eol() {
|
||||
self.is_next_eol = true;
|
||||
}
|
||||
self.tokens.peek().ok_or(ParseError::UnexpectedEnd)
|
||||
}
|
||||
fn try_next(&mut self) -> Result<Token> {
|
||||
self.is_next_eol = self.skip_eol();
|
||||
self.tokens.next().ok_or(ParseError::UnexpectedEnd)
|
||||
}
|
||||
|
||||
fn expect_next(&mut self, kind: TokenKind) -> Result<()> {
|
||||
let t = self.try_next()?;
|
||||
|
||||
if t.kind() != kind {
|
||||
return Err(ParseError::UnexpectedToken(t));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn is_next(&mut self, kind: Option<TokenKind>) -> bool {
|
||||
match self.try_peek() {
|
||||
Ok(t) if Some(t.kind()) == kind => true,
|
||||
Ok(_) => false,
|
||||
|
||||
Err(ParseError::UnexpectedEnd) if kind.is_none() => true,
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self, min_prec: Precedence, in_group: bool) -> Result<Box<Expr>> {
|
||||
fn parse_expr(&mut self, min_prec: Precedence, in_group: bool) -> Result<Rc<Expr>> {
|
||||
let mut lhs = match self.try_next()? {
|
||||
// literal
|
||||
Token::Literal(lit) => Box::new(Expr::Literal(lit)),
|
||||
Token::Literal(lit) => Rc::new(Expr::Literal(lit)),
|
||||
|
||||
// start of group
|
||||
Token::ParenOpen => {
|
||||
|
|
@ -135,57 +123,22 @@ where
|
|||
}
|
||||
// start of a block
|
||||
Token::CurlyOpen => {
|
||||
let exprs = self.parse_until(Some(TokenKind::CurlyClose))?;
|
||||
let b = self.parse_block(true)?;
|
||||
// skip curly brace
|
||||
self.eat();
|
||||
Box::new(Expr::Block(Block { exprs }))
|
||||
Rc::new(Expr::Block(Rc::new(b)))
|
||||
}
|
||||
|
||||
// unary ops!! (prefix)
|
||||
t if t.prefix_precedence().is_some() => {
|
||||
let prec = t.prefix_precedence().unwrap();
|
||||
match t {
|
||||
// parse function
|
||||
Token::Func => {
|
||||
// parse args
|
||||
self.expect_next(TokenKind::ParenOpen)?;
|
||||
let args = self
|
||||
.parse_delimited_until(TokenKind::Comma, Some(TokenKind::ParenClose))?;
|
||||
self.eat();
|
||||
// parse body
|
||||
let body = self.parse_expr(prec, in_group)?;
|
||||
// pack
|
||||
Box::new(Expr::Func(args, body, None))
|
||||
}
|
||||
// parse if
|
||||
Token::If => {
|
||||
// parse the condition
|
||||
let cond = self.parse_expr(Precedence::Min, false)?;
|
||||
// parse the true case
|
||||
let true_case = self.parse_expr(Precedence::Min, in_group)?;
|
||||
// and maybe a false case
|
||||
let false_case = matches!(self.try_peek(), Ok(Token::Else))
|
||||
.then(|| {
|
||||
self.eat();
|
||||
self.parse_expr(Precedence::Min, in_group)
|
||||
})
|
||||
.transpose()?;
|
||||
// pack
|
||||
Box::new(Expr::If(cond, true_case, false_case))
|
||||
}
|
||||
|
||||
// another op
|
||||
_ => {
|
||||
let rhs = self.parse_expr(prec, in_group)?;
|
||||
Box::new(match t {
|
||||
Token::Minus => Expr::Negate(rhs),
|
||||
Token::Not => Expr::Not(rhs),
|
||||
Token::Return => Expr::Return(rhs),
|
||||
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
let rhs = self.parse_expr(prec, in_group)?;
|
||||
Rc::new(match t {
|
||||
Token::Minus => Expr::Negate(rhs),
|
||||
Token::Not => Expr::Not(rhs),
|
||||
Token::Return => Expr::Return(rhs),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
// unexpected token
|
||||
|
|
@ -206,16 +159,29 @@ where
|
|||
|
||||
// function call
|
||||
Ok(Token::ParenOpen) => {
|
||||
if self.is_next_eol {
|
||||
break;
|
||||
}
|
||||
|
||||
// eat opening paren
|
||||
self.eat();
|
||||
|
||||
let exprs =
|
||||
self.parse_delimited_until(TokenKind::Comma, Some(TokenKind::ParenClose))?;
|
||||
let mut exprs = Vec::new();
|
||||
while !matches!(self.try_peek()?, Token::ParenClose) {
|
||||
exprs.push(self.parse_expr(Precedence::Min, false)?);
|
||||
|
||||
// Continue if there is a comma,
|
||||
// ignore closing parens
|
||||
match self.try_peek()? {
|
||||
Token::Comma => self.eat(),
|
||||
Token::ParenClose => {}
|
||||
_ => return Err(ParseError::UnexpectedToken(self.next_unwrap())),
|
||||
}
|
||||
}
|
||||
// eat closing paren
|
||||
self.eat();
|
||||
|
||||
lhs = Box::new(Expr::Call(lhs, exprs));
|
||||
lhs = Rc::new(Expr::Call(lhs, exprs));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +192,7 @@ where
|
|||
let (prec, assoc) = op.infix_precedence().unwrap();
|
||||
|
||||
// break if this op is meant for previous recursion
|
||||
// or it's equal and we prefer to build leftward
|
||||
// or it's equal and we would prefer to build leftward..
|
||||
if prec < min_prec || (prec == min_prec && assoc == Associativity::Left) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -237,7 +203,7 @@ where
|
|||
let rhs = self.parse_expr(prec, in_group)?;
|
||||
|
||||
// join to lhs
|
||||
lhs = Box::new(match op {
|
||||
lhs = Rc::new(match op {
|
||||
// equality
|
||||
Token::EqualTo => Expr::EqualTo(lhs, rhs),
|
||||
Token::NotEqualTo => Expr::NotEqualTo(lhs, rhs),
|
||||
|
|
@ -252,18 +218,13 @@ where
|
|||
// add, subtract
|
||||
Token::Plus => Expr::Add(lhs, rhs),
|
||||
Token::Minus => Expr::Subtract(lhs, rhs),
|
||||
Token::PlusEquals => Expr::AddAssign(lhs, rhs),
|
||||
Token::MinusEquals => Expr::SubtractAssign(lhs, rhs),
|
||||
// multiply, divide
|
||||
Token::Star => Expr::Multiply(lhs, rhs),
|
||||
Token::Slash => Expr::Divide(lhs, rhs),
|
||||
Token::StarEquals => Expr::MultiplyAssign(lhs, rhs),
|
||||
Token::SlashEquals => Expr::DivideAssign(lhs, rhs),
|
||||
// exponent, modulo
|
||||
Token::StarStar => Expr::Exponent(lhs, rhs),
|
||||
Token::Percent => Expr::Modulo(lhs, rhs),
|
||||
// exponent
|
||||
Token::Caret => Expr::Exponent(lhs, rhs),
|
||||
// assignment
|
||||
Token::Equals => Expr::Assign(lhs, rhs),
|
||||
Token::Equals => Expr::Assignment(lhs, rhs),
|
||||
// unreachable as all tokens with precedences are covered above
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
|
@ -271,53 +232,26 @@ where
|
|||
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_until(&mut self, until: Option<TokenKind>) -> Result<Vec<Expr>> {
|
||||
fn parse_block(&mut self, in_block: bool) -> Result<Block> {
|
||||
let mut exprs = Vec::new();
|
||||
loop {
|
||||
match self.try_peek() {
|
||||
// end (block)
|
||||
Ok(Token::CurlyClose) if in_block => break,
|
||||
// end (stream) lpwkey idk if this is a good way to check for error
|
||||
// need to add error nodes anyway so whatever
|
||||
Err(ParseError::UnexpectedEnd) if !in_block => break,
|
||||
|
||||
while !self.is_next(until) {
|
||||
// try to parse expr
|
||||
exprs.push(*self.parse_expr(Precedence::Min, false)?);
|
||||
// try to parse expr
|
||||
Ok(_) => exprs.push(self.parse_expr(Precedence::Min, false)?),
|
||||
|
||||
// check for end
|
||||
if self.is_next(until) {
|
||||
break;
|
||||
// invalid
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
fn parse_delimited_until(
|
||||
&mut self,
|
||||
delim: TokenKind,
|
||||
until: Option<TokenKind>,
|
||||
) -> Result<Vec<Expr>> {
|
||||
let mut exprs = Vec::new();
|
||||
|
||||
while !self.is_next(until) {
|
||||
// skip delimiter
|
||||
if self.is_next(Some(delim)) {
|
||||
self.eat();
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to parse expr
|
||||
exprs.push(*self.parse_expr(Precedence::Min, false)?);
|
||||
|
||||
// check for end
|
||||
if self.is_next(until) {
|
||||
break;
|
||||
}
|
||||
|
||||
// check for delim
|
||||
self.expect_next(delim)?;
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Result<Block> {
|
||||
let exprs = self.parse_until(None)?;
|
||||
Ok(Block { exprs })
|
||||
}
|
||||
pub fn parse(&mut self) -> Result<Block> {
|
||||
self.parse_block(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::parser::Expr;
|
||||
|
||||
pub fn display(e: &Expr) {
|
||||
pub fn display(e: &Rc<Expr>) {
|
||||
let result = fmt_expr(e, 0);
|
||||
println!("{result}");
|
||||
}
|
||||
|
||||
fn fmt_binop(left: &Expr, right: &Expr, op: &str, depth: usize) -> String {
|
||||
fn fmt_binop(left: &Rc<Expr>, right: &Rc<Expr>, op: &str, depth: usize) -> String {
|
||||
format!(
|
||||
"({} {} {})",
|
||||
fmt_expr(left, depth),
|
||||
|
|
@ -16,13 +16,9 @@ fn fmt_binop(left: &Expr, right: &Expr, op: &str, depth: usize) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
fn fmt_expr(e: &Expr, depth: usize) -> String {
|
||||
match e {
|
||||
Expr::Assign(l, r) => fmt_binop(l, r, "=", depth),
|
||||
Expr::AddAssign(l, r) => fmt_binop(l, r, "+=", depth),
|
||||
Expr::SubtractAssign(l, r) => fmt_binop(l, r, "-=", depth),
|
||||
Expr::MultiplyAssign(l, r) => fmt_binop(l, r, "*=", depth),
|
||||
Expr::DivideAssign(l, r) => fmt_binop(l, r, "/=", depth),
|
||||
fn fmt_expr(e: &Rc<Expr>, depth: usize) -> String {
|
||||
match &**e {
|
||||
Expr::Assignment(l, r) => fmt_binop(l, r, "=", depth),
|
||||
Expr::Literal(l) => l.to_string(),
|
||||
Expr::Call(l, r) => {
|
||||
let mut result = fmt_expr(l, depth);
|
||||
|
|
@ -37,14 +33,7 @@ fn fmt_expr(e: &Expr, depth: usize) -> String {
|
|||
result.push(')');
|
||||
result
|
||||
}
|
||||
Expr::If(c, t, f) => {
|
||||
let mut result = format!("if ({}) ({})", fmt_expr(c, depth), fmt_expr(t, depth));
|
||||
if let Some(f) = f {
|
||||
let _ = write!(result, " else ({})", fmt_expr(f, depth));
|
||||
}
|
||||
result
|
||||
}
|
||||
Expr::Return(l) => format!("return ({})", fmt_expr(l, depth)),
|
||||
Expr::Return(r) => format!("return {}", fmt_expr(r, depth)),
|
||||
Expr::Block(b) => {
|
||||
let mut result = String::new();
|
||||
let len = b.exprs.len();
|
||||
|
|
@ -60,24 +49,8 @@ fn fmt_expr(e: &Expr, depth: usize) -> String {
|
|||
}
|
||||
result
|
||||
}
|
||||
Expr::Func(a, e, func_stat) => format!(
|
||||
"(func({}){} ({}))",
|
||||
a.iter()
|
||||
.map(|e| fmt_expr(e, depth))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
func_stat
|
||||
.as_ref()
|
||||
.map(|fm| fm
|
||||
.is_unreturnable
|
||||
.get()
|
||||
.then_some("@UNRET")
|
||||
.unwrap_or("@OK"))
|
||||
.unwrap_or_default(),
|
||||
fmt_expr(e, depth)
|
||||
),
|
||||
Expr::Negate(l) => format!("(-{})", fmt_expr(l, depth)),
|
||||
Expr::Not(l) => format!("(!{})", fmt_expr(l, depth)),
|
||||
Expr::Negate(r) => format!("(-{})", fmt_expr(r, depth)),
|
||||
Expr::Not(r) => format!("(!{})", fmt_expr(r, depth)),
|
||||
Expr::EqualTo(l, r) => fmt_binop(l, r, "==", depth),
|
||||
Expr::NotEqualTo(l, r) => fmt_binop(l, r, "!=", depth),
|
||||
Expr::And(l, r) => fmt_binop(l, r, "&&", depth),
|
||||
|
|
@ -90,7 +63,6 @@ fn fmt_expr(e: &Expr, depth: usize) -> String {
|
|||
Expr::Subtract(l, r) => fmt_binop(l, r, "-", depth),
|
||||
Expr::Multiply(l, r) => fmt_binop(l, r, "*", depth),
|
||||
Expr::Divide(l, r) => fmt_binop(l, r, "/", depth),
|
||||
Expr::Exponent(l, r) => fmt_binop(l, r, "**", depth),
|
||||
Expr::Modulo(l, r) => fmt_binop(l, r, "%", depth),
|
||||
Expr::Exponent(l, r) => fmt_binop(l, r, "^", depth),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,303 @@
|
|||
use std::{cell::LazyCell, collections::HashMap, fmt, fmt::Write as _, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
lexer::{Ident, Literal},
|
||||
parser::{Block, Expr},
|
||||
};
|
||||
|
||||
macro_rules! type_error {
|
||||
() => {
|
||||
return Err(RuntimeError::Type.into())
|
||||
};
|
||||
}
|
||||
macro_rules! uncallable {
|
||||
() => {
|
||||
return Err(RuntimeError::Uncallable.into())
|
||||
};
|
||||
}
|
||||
|
||||
use Expr as E;
|
||||
use Literal as L;
|
||||
use Value as V;
|
||||
|
||||
fn builtin_print(args: Args) -> RuntimeResult {
|
||||
let mut result = String::new();
|
||||
for v in args {
|
||||
let _ = write!(result, "{v}\t");
|
||||
}
|
||||
println!("{result}");
|
||||
Ok(V::nil())
|
||||
}
|
||||
fn builtin_if(args: Args) -> RuntimeResult {
|
||||
let Some(Value::Boolean(cond)) = args.get(0).map(|v| &**v) else {
|
||||
type_error!()
|
||||
};
|
||||
let cond = *cond;
|
||||
|
||||
let b_else = args.get(2).map(|v| &**v);
|
||||
let b_if = args.get(1).map(|v| &**v);
|
||||
|
||||
let empty = Vec::new();
|
||||
Ok(match (b_if, b_else) {
|
||||
(Some(V::Block(b_if)), _) if cond => exec_block(&b_if, empty)?,
|
||||
(_, Some(V::Block(b_else))) if !cond => exec_block(&b_else, empty)?,
|
||||
(Some(V::Nil) | None, _) => V::nil(),
|
||||
(_, Some(V::Nil) | None) => V::nil(),
|
||||
(_, _) => uncallable!(),
|
||||
})
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static NIL: LazyCell<Rc<Value>> = const { LazyCell::new(|| Rc::new(V::Nil)) };
|
||||
static TRUE: LazyCell<Rc<Value>> = const { LazyCell::new(|| Rc::new(V::Boolean(true))) };
|
||||
static FALSE: LazyCell<Rc<Value>> = const { LazyCell::new(|| Rc::new(V::Boolean(false))) };
|
||||
}
|
||||
|
||||
pub enum Value {
|
||||
String(Rc<String>),
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
Block(Rc<Block>),
|
||||
BuiltinFn(fn(Args) -> RuntimeResult),
|
||||
Nil,
|
||||
}
|
||||
impl Value {
|
||||
fn bool(b: bool) -> Rc<Self> {
|
||||
if b {
|
||||
Self::bool_true()
|
||||
} else {
|
||||
Self::bool_false()
|
||||
}
|
||||
}
|
||||
fn int(i: i64) -> Rc<Self> {
|
||||
Rc::new(V::Integer(i))
|
||||
}
|
||||
fn str(s: Rc<String>) -> Rc<Self> {
|
||||
Rc::new(V::String(s))
|
||||
}
|
||||
fn block(b: Rc<Block>) -> Rc<Self> {
|
||||
Rc::new(V::Block(b))
|
||||
}
|
||||
fn builtin_fn(f: fn(Args) -> RuntimeResult) -> Rc<Self> {
|
||||
Rc::new(V::BuiltinFn(f))
|
||||
}
|
||||
fn bool_true() -> Rc<Self> {
|
||||
TRUE.with(|v| (**v).clone())
|
||||
}
|
||||
fn bool_false() -> Rc<Self> {
|
||||
FALSE.with(|v| (**v).clone())
|
||||
}
|
||||
fn nil() -> Rc<Self> {
|
||||
NIL.with(|v| (**v).clone())
|
||||
}
|
||||
}
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(V::String(s1), V::String(s2)) => s1 == s2,
|
||||
(V::Integer(i1), V::Integer(i2)) => i1 == i2,
|
||||
(V::Boolean(b1), V::Boolean(b2)) => b1 == b2,
|
||||
(V::Nil, V::Nil) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
V::String(s) => write!(f, "{s}"),
|
||||
V::Integer(i) => write!(f, "{i}"),
|
||||
V::Boolean(b) => write!(f, "{b}"),
|
||||
V::Block(_b) => write!(f, "<block>"),
|
||||
V::BuiltinFn(_f) => write!(f, "<builtin>"),
|
||||
V::Nil => write!(f, "nil"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RuntimeError {
|
||||
Type,
|
||||
Uncallable,
|
||||
}
|
||||
impl fmt::Display for RuntimeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Type => write!(f, "type error"),
|
||||
Self::Uncallable => write!(f, "uncallable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type RuntimeResult = Result<Rc<Value>, RuntimeError>;
|
||||
|
||||
type Args = Vec<Rc<Value>>;
|
||||
|
||||
enum EvalOutcome {
|
||||
EarlyReturn(Rc<Value>),
|
||||
Error(RuntimeError),
|
||||
}
|
||||
impl From<RuntimeError> for EvalOutcome {
|
||||
fn from(err: RuntimeError) -> Self {
|
||||
Self::Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(scope: &mut HashMap<Ident, Rc<Value>>, e: &Rc<Expr>) -> Result<Rc<Value>, EvalOutcome> {
|
||||
Ok(match &**e {
|
||||
E::Assignment(l, r) => {
|
||||
let Expr::Literal(Literal::Ident(id)) = &**l else {
|
||||
type_error!()
|
||||
};
|
||||
let v = eval(scope, r)?;
|
||||
scope.insert(id.clone(), v.clone());
|
||||
v
|
||||
}
|
||||
E::Literal(L::Ident(id)) => match id.as_ref() {
|
||||
"print" => V::builtin_fn(builtin_print),
|
||||
"if" => V::builtin_fn(builtin_if),
|
||||
_ => scope.get(id).cloned().unwrap_or_else(V::nil),
|
||||
},
|
||||
E::Literal(L::String(s)) => V::str(s.clone()),
|
||||
E::Literal(L::Integer(i)) => V::int(*i),
|
||||
E::Literal(L::Boolean(b)) => V::bool(*b),
|
||||
E::Literal(L::Nil) => V::nil(),
|
||||
E::Block(b) => V::block(b.clone()),
|
||||
E::Return(r) => {
|
||||
let r = eval(scope, r)?;
|
||||
return Err(EvalOutcome::EarlyReturn(r));
|
||||
}
|
||||
E::Call(l, r) => {
|
||||
let l = eval(scope, l)?;
|
||||
let args = r.iter().map(|e| eval(scope, e)).collect::<Result<_, _>>()?;
|
||||
match &*l {
|
||||
V::BuiltinFn(f) => f(args)?,
|
||||
V::Block(b) => exec_block(b, args)?,
|
||||
_ => uncallable!(),
|
||||
}
|
||||
}
|
||||
E::Negate(r) => {
|
||||
let l = eval(scope, r)?;
|
||||
let V::Integer(i) = *l else { type_error!() };
|
||||
V::int(-i)
|
||||
}
|
||||
E::Not(r) => {
|
||||
let l = eval(scope, r)?;
|
||||
let V::Boolean(b) = *l else { type_error!() };
|
||||
V::bool(!b)
|
||||
}
|
||||
E::EqualTo(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(*l == *r)
|
||||
}
|
||||
E::NotEqualTo(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(*l != *r)
|
||||
}
|
||||
E::And(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(if let (V::Boolean(b1), V::Boolean(b2)) = (&*l, &*r) {
|
||||
*b1 && *b2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::Or(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(if let (V::Boolean(b1), V::Boolean(b2)) = (&*l, &*r) {
|
||||
*b1 || *b2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::LessThan(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 < i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::LessThanOrEqualTo(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 <= i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::GreaterThan(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 > i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::GreaterThanOrEqualTo(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::bool(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 >= i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::Add(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::int(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 + i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::Subtract(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::int(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 + i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::Multiply(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::int(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 * i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::Divide(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::int(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1 / i2
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
E::Exponent(l, r) => {
|
||||
let (l, r) = (eval(scope, l)?, eval(scope, r)?);
|
||||
V::int(if let (V::Integer(i1), V::Integer(i2)) = (&*l, &*r) {
|
||||
i1.pow(*i2 as u32)
|
||||
} else {
|
||||
type_error!()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn exec_block(b: &Block, _args: Args) -> RuntimeResult {
|
||||
let mut scope = HashMap::new();
|
||||
|
||||
for e in &b.exprs {
|
||||
match eval(&mut scope, e) {
|
||||
Ok(_) => {}
|
||||
Err(EvalOutcome::EarlyReturn(v)) => return Ok(v),
|
||||
Err(EvalOutcome::Error(err)) => return Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(V::nil())
|
||||
}
|
||||
|
||||
pub fn exec(b: &Block) -> RuntimeResult {
|
||||
exec_block(b, Vec::new())
|
||||
}
|
||||
155
src/vm.rs
155
src/vm.rs
|
|
@ -1,155 +0,0 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
use crate::compiler::{Inst, Stkval, Val};
|
||||
|
||||
#[derive(Default)]
|
||||
struct FuncVm<'a> {
|
||||
depth: usize,
|
||||
parent_vm: Option<&'a FuncVm<'a>>,
|
||||
shared: RefCell<Vec<Val>>,
|
||||
locals: Vec<Val>,
|
||||
}
|
||||
impl<'a> FuncVm<'a> {
|
||||
fn with(parent_vm: &'a FuncVm<'a>, locals: Vec<Val>) -> Self {
|
||||
Self {
|
||||
parent_vm: Some(parent_vm),
|
||||
depth: parent_vm.depth + 1,
|
||||
shared: RefCell::default(),
|
||||
locals,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&mut self, v: &Val) -> Val {
|
||||
use {Stkval::*, Val::*};
|
||||
|
||||
match v {
|
||||
Stack(Local(o), true) => self.locals.remove(*o as usize),
|
||||
Stack(Local(o), false) => self.locals[*o as usize].clone(),
|
||||
|
||||
Stack(Shared(l, o), false) => {
|
||||
let mut vm = &*self;
|
||||
for _ in 0..*l {
|
||||
vm = vm.parent_vm.as_ref().unwrap();
|
||||
}
|
||||
vm.shared.borrow_mut()[*o as usize].clone()
|
||||
}
|
||||
Stack(Shared(_, _), true) => panic!("not allowed"),
|
||||
|
||||
v => v.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_all(&mut self, insts: &[Inst]) -> Val {
|
||||
use {Inst::*, Val::*};
|
||||
|
||||
let mut pc = 0;
|
||||
while pc < insts.len() {
|
||||
let inst = &insts[pc];
|
||||
|
||||
// println!("{}> {:?} {inst:?}", "=".repeat(self.depth), self.locals);
|
||||
|
||||
match inst {
|
||||
/* rhs */
|
||||
Not(a) | Return(a) => {
|
||||
let r = match (inst, self.get(a)) {
|
||||
(Not(_), Bool(a)) => Bool(!a),
|
||||
(Return(_), Func(true, _, _)) => panic!("func is unreturnable"),
|
||||
(Return(_), a) => return a,
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
self.locals.push(r);
|
||||
}
|
||||
/* s rhs */
|
||||
Copy(s, a) => {
|
||||
let a = self.get(a);
|
||||
if !*s {
|
||||
self.locals.push(a);
|
||||
} else {
|
||||
self.shared.borrow_mut().push(a);
|
||||
}
|
||||
}
|
||||
/* k rhs */
|
||||
Call(k, n, a) => {
|
||||
// check validity
|
||||
let a = self.get(a);
|
||||
let Func(_, arity, insts) = a else {
|
||||
panic!("called non-function {a:?}")
|
||||
};
|
||||
|
||||
// collect args from stack :)
|
||||
let args_start = self.locals.len() - *n as usize;
|
||||
let args = self.locals.split_off(args_start);
|
||||
|
||||
// make sure its the right amount
|
||||
if *n != arity {
|
||||
panic!("wrong # args")
|
||||
}
|
||||
|
||||
// exec
|
||||
let mut vm = FuncVm::with(self, args);
|
||||
let r = vm.eval_all(&insts);
|
||||
// push value if were supposed to
|
||||
if *k {
|
||||
self.locals.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
/* sv rhs */
|
||||
Move(sv, a) => {
|
||||
let a = self.get(a);
|
||||
match sv {
|
||||
Stkval::Local(o) => {
|
||||
self.locals[*o as usize] = a;
|
||||
}
|
||||
Stkval::Shared(l, o) => {
|
||||
let mut vm = &*self;
|
||||
for _ in 0..*l {
|
||||
vm = vm.parent_vm.unwrap();
|
||||
}
|
||||
vm.shared.borrow_mut()[*o as usize] = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* lhs rhs */
|
||||
Eq(a, b)
|
||||
| Gt(a, b)
|
||||
| GtEq(a, b)
|
||||
| Add(a, b)
|
||||
| Mul(a, b)
|
||||
| Div(a, b)
|
||||
| Mod(a, b)
|
||||
| Pow(a, b)
|
||||
| And(a, b)
|
||||
| Or(a, b) => {
|
||||
let r = match (inst, self.get(a), self.get(b)) {
|
||||
(Add(_, _), Int64(a), Int64(b)) => Int64(a + b),
|
||||
(Mul(_, _), Int64(a), Int64(b)) => Int64(a * b),
|
||||
(Div(_, _), Int64(a), Int64(b)) => Int64(a / b),
|
||||
(Mod(_, _), Int64(a), Int64(b)) => Int64(a % b),
|
||||
(Pow(_, _), Int64(a), Int64(b)) => Int64(a.pow(b.try_into().unwrap())),
|
||||
(And(_, _), Bool(a), Bool(b)) => Bool(a && b),
|
||||
(Or(_, _), Bool(a), Bool(b)) => Bool(a || b),
|
||||
(Eq(_, _), a, b) => Bool(a == b),
|
||||
(Gt(_, _), Int64(a), Int64(b)) => Bool(a > b),
|
||||
(GtEq(_, _), Int64(a), Int64(b)) => Bool(a >= b),
|
||||
|
||||
x => unimplemented!("{x:?}"),
|
||||
};
|
||||
self.locals.push(r);
|
||||
}
|
||||
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
pc += 1;
|
||||
}
|
||||
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(insts: &[Inst]) -> Val {
|
||||
let mut vm = FuncVm::default();
|
||||
vm.eval_all(insts)
|
||||
}
|
||||
Loading…
Reference in New Issue