Compare commits

..

1 Commits

Author SHA1 Message Date
minish c81c728d54
runtime prototype 2025-07-01 23:49:05 -04:00
9 changed files with 478 additions and 926 deletions

View File

@ -3,9 +3,4 @@ name = "leaf"
version = "0.1.0"
edition = "2024"
[profile.release]
strip = true
lto = true
codegen-units = 1
[dependencies]

View File

@ -1,491 +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) {
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);
}
Expr::Literal(Literal::Ident(id, ref_stat)) => {
// lookup literal
let Some((rm, up_levels)) = scope.find(id) else {
panic!("unfound variable")
};
// increment # of uses
rm.total.update(|c| c + 1);
// set ref meta
*ref_stat = Some(RefStat {
now: rm.total.get(),
meta: rm.clone(),
});
// if we used something external to this scope, note it
if up_levels != 0 {
fs.is_unreturnable.set(true);
rm.is_shared.set(true);
}
}
// ignore
Expr::Literal(_) => {}
// for recursion..
Expr::Block(a) => {
// blocks have their own scope
let mut scope = Scope::with_parent(Some(scope));
// analyze the contents in the new scope
for e in &mut a.exprs {
analyze(fs, &mut scope, e);
}
}
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);
}
Expr::If(a, b, c) => {
analyze(fs, scope, a);
analyze(fs, scope, b);
if let Some(c) = c {
analyze(fs, scope, c);
}
}
Expr::Call(a, b) => {
analyze(fs, scope, a);
for e in b {
analyze(fs, scope, e);
}
}
Expr::Return(a) | Expr::Negate(a) | Expr::Not(a) => analyze(fs, scope, a),
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)
| Expr::AddAssign(a, b) // maybe handle these differently?
| Expr::SubtractAssign(a, b) // when error handling is added at least
| Expr::MultiplyAssign(a, b)
| Expr::DivideAssign(a, b) =>{
analyze(fs, scope, a);
analyze(fs, scope, b);
}
}
}
// --- translate pass --- //
/* 1b is up? */
#[derive(Debug, PartialEq, Eq)]
pub enum Stkval {
/* 4b blank ; 8b offset */
Local(u8),
/* 4b up levels ; 8b offset */
Shared(u8, u8),
}
/* 3b type tag */
#[derive(Debug)]
pub enum Val {
Stack(Stkval, bool),
/* u16 len, LEN data */
String(String),
/* 1b returnability, 4b arity, insts */
Func(bool, Vec<Inst>),
/* 1b value */
Bool(bool),
/* i64 data */
Int64(i64),
/* f64 data */
Float64(f64),
/* ... */
Nil,
}
/* 5b inst type */
#[derive(Debug)]
pub enum Inst {
/* ... */
Copy(Val),
/* pop a1? ; pop a2? */
Eq(bool, bool, Val, Val),
Gt(bool, bool, Val, Val),
GtEq(bool, bool, Val, Val),
/* is conditional? ; what condition? */
Skip(bool, bool, i16),
/* is conditional? ; what condition? ; pop result? */
Call(bool, bool, bool, Val),
/* pop a1? ; pop a2 */
Add(bool, bool, Val, Val),
Mul(bool, bool, Val, Val),
Div(bool, bool, Val, Val),
Mod(bool, bool, Val, Val),
Pow(bool, bool, Val, Val),
And(bool, bool, Val, Val),
Or(bool, bool, Val, Val),
/* pop a1? */
Not(bool, Val),
/* ... */
Pop(Stkval),
/* pop a2? */
Write(bool, Stkval, Val),
/* ... */
Return(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) -> 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)))
.unwrap()
}
/// Returns stackval for top item of stack.
/// (Panics if empty)
fn top(&self) -> Stkval {
Stkval::Local(self.local.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()
}
/// Pops top stack value and returns its stackval.
fn pop_top(&mut self) -> Stkval {
let to_pop = self.top();
self.local.values_mut().pop();
to_pop
}
fn check_drop(&mut self, v: &Val) -> bool {
if let Val::Stack(Stkval::Local(i), true) = v {
self.local.pop(*i as usize);
true
} else {
false
}
}
fn check_drop2(&mut self, v1: &Val, v2: &Val) -> (bool, bool) {
(self.check_drop(v1), self.check_drop(v2))
}
fn gen_unop(&mut self, r: Expr, f: impl Fn(bool, Val) -> Inst, is_captured: bool) -> Val {
let v1 = self.translate(r, is_captured);
// If nothing will use this,
// don't generate anything
if !is_captured {
return Val::Nil;
}
let a1 = self.check_drop(&v1);
self.insts.push(f(a1, v1));
Val::Stack(self.push_any(), true)
}
fn gen_binop(
&mut self,
l: Expr,
r: Expr,
f: impl Fn(bool, bool, Val, Val) -> Inst,
is_captured: bool,
) -> Val {
let (v1, v2) = (
self.translate(l, is_captured),
self.translate(r, is_captured),
);
// If this is unused, do not generate code
if !is_captured {
return Val::Nil;
}
let (a1, a2) = self.check_drop2(&v1, &v2);
self.insts.push(f(a1, a2, v1, v2));
Val::Stack(self.push_any(), true)
}
fn translate(&mut self, e: Expr, is_captured: bool) -> Val {
match e {
/* organisational */
Expr::Block(mut b) => {
let last = b.exprs.pop();
for e in b.exprs {
self.translate(e, false);
}
// yield last expr
last.map_or(Val::Nil, |e| self.translate(e, is_captured))
}
/* captured literal */
Expr::Literal(lit) if is_captured => {
let v1 = self.translate(Expr::Literal(lit), false);
self.insts.push(Inst::Copy(v1));
Val::Stack(self.push_any(), false)
}
/* 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))) => {
Val::Stack(self.find(&id), rs.now == rs.meta.total.get())
}
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 captured OR referenced,
// just continue translation without adding to stack
if !(is_captured || gets_referenced) {
self.translate(*r, false)
} else {
// get val
let val = match *r {
// the var's value is a literal
Expr::Literal(lit) => self.translate(Expr::Literal(lit), gets_referenced),
// value is an expr
e => self.translate(e, true),
};
// if the var got used, it will be on stack
// so keep track of it
if gets_referenced {
self.local.swap_top(FSValue::Var(id));
}
val
}
}
/* math */
Expr::Add(l, r) => self.gen_binop(*l, *r, Inst::Add, is_captured),
Expr::Multiply(l, r) => self.gen_binop(*l, *r, Inst::Mul, is_captured),
Expr::Divide(l, r) => self.gen_binop(*l, *r, Inst::Div, is_captured),
Expr::Modulo(l, r) => self.gen_binop(*l, *r, Inst::Mod, is_captured),
Expr::Exponent(l, r) => self.gen_binop(*l, *r, Inst::Pow, is_captured),
Expr::Subtract(l, r) => {
// negate
let nv2 = match *r {
// statically
Expr::Literal(Literal::Integer(i)) => Val::Int64(-i),
Expr::Literal(Literal::Float(f)) => Val::Float64(-f),
// at runtime
e => {
let v2 = self.translate(e, is_captured);
let a2 = self.check_drop(&v2);
self.insts.push(Inst::Mul(a2, false, v2, Val::Int64(-1)));
Val::Stack(self.pop_top(), true)
}
};
// add
let v1 = self.translate(*l, is_captured);
let a1 = self.check_drop(&v1);
self.insts.push(Inst::Add(a1, true, v1, nv2));
Val::Stack(self.push_any(), true)
}
/* logic */
Expr::And(l, r) => self.gen_binop(*l, *r, Inst::And, is_captured),
Expr::Or(l, r) => self.gen_binop(*l, *r, Inst::Or, is_captured),
Expr::EqualTo(l, r) => self.gen_binop(*l, *r, Inst::Eq, is_captured),
Expr::GreaterThan(l, r) => self.gen_binop(*l, *r, Inst::Gt, is_captured),
Expr::GreaterThanOrEqualTo(l, r) => self.gen_binop(*l, *r, Inst::GtEq, is_captured),
Expr::Not(r) => self.gen_unop(*r, Inst::Not, is_captured),
Expr::NotEqualTo(l, r) => {
self.translate(Expr::Not(Box::new(Expr::EqualTo(l, r))), is_captured)
}
Expr::LessThan(l, r) => self.translate(Expr::GreaterThan(r, l), is_captured),
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);
}
pub fn translation_demo(e: Expr) -> Vec<Inst> {
// translation pass
let mut fb = FuncBuild::new_root();
fb.translate(e, false);
fb.insts
}

View File

@ -1,51 +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 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
}
}

View File

@ -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),* ) )? )
)
);
)*
}
};
}

View File

@ -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))),
};

View File

@ -1,32 +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 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:?}");
}
// parser::util::display(&Rc::new(Expr::Block(Rc::new(block))));
runtime::exec(&block).unwrap();
}

View File

@ -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)]
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)]
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)
}
}

View File

@ -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),
}
}

303
src/runtime.rs Normal file
View File

@ -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())
}