You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
14 KiB
Rust
391 lines
14 KiB
Rust
use crate::ast::tree::{FnBody, Node, Operator, Type};
|
|
use crate::error::Error;
|
|
use std::borrow::Borrow;
|
|
|
|
pub(crate) mod native;
|
|
pub(crate) mod obj;
|
|
use obj::Object;
|
|
pub(crate) mod val;
|
|
use crate::rt::obj::{KEY_DEPENDENCIES, KEY_SOURCES, KEY_TYPE};
|
|
use val::{Scope, Val, ValOps};
|
|
|
|
/// Execute a program.
|
|
/// The `root` node must be a `Node::File`.
|
|
pub fn exec(root: &Node) -> Result<Val, Error> {
|
|
match root {
|
|
Node::File(pos, f) => {
|
|
let mut root_scope = Scope::new_root(pos.file.clone());
|
|
let mut val = Val::new(());
|
|
for node in &f.content {
|
|
val = node.eval(&mut root_scope)?;
|
|
}
|
|
Ok(val)
|
|
}
|
|
node => Err(Error::runtime_error(
|
|
empty_pos(),
|
|
format!("cannot exec non-file node {}", node),
|
|
)),
|
|
}
|
|
}
|
|
|
|
impl Node {
|
|
fn eval(&self, scope: &mut Scope) -> Result<Val, Error> {
|
|
match self {
|
|
Node::Bool(_, b) => Ok(Val::new(*b)),
|
|
Node::Int(_, i) => Ok(Val::new(*i)),
|
|
Node::String(_, s) => Ok(Val::new(s.clone())),
|
|
Node::Ident(pos, name) => {
|
|
if let Some(sym) = scope.resolve(name) {
|
|
Ok(sym.clone())
|
|
} else {
|
|
Err(Error::runtime_error(
|
|
pos.clone(),
|
|
format!("Identifier \"{}\" not found in this scope", name),
|
|
))
|
|
}
|
|
}
|
|
Node::Array(_, elems) => {
|
|
let mut vals = Vec::new();
|
|
for e in elems {
|
|
vals.push(e.eval(scope)?);
|
|
}
|
|
Ok(Val::new(vals))
|
|
}
|
|
Node::UnaryExpr(_, expr) => expr.op.eval_unary(scope, expr.rhs.borrow()),
|
|
Node::BinaryExpr(_, expr) => {
|
|
expr.op
|
|
.eval_binary(scope, expr.lhs.borrow(), expr.rhs.borrow())
|
|
}
|
|
Node::IfStmt(_, expr) => exec_if(
|
|
scope,
|
|
expr.condition.borrow(),
|
|
expr.then_body.borrow(),
|
|
if let Some(els) = expr.else_body.borrow() {
|
|
Some(els)
|
|
} else {
|
|
None
|
|
},
|
|
),
|
|
Node::WhileStmt(_, stmt) => {
|
|
exec_while(scope, stmt.condition.borrow(), stmt.body.borrow())
|
|
}
|
|
Node::ForStmt(_, stmt) => exec_for(
|
|
scope,
|
|
if let Some(setup) = &stmt.setup {
|
|
Some(setup)
|
|
} else {
|
|
None
|
|
},
|
|
if let Some(condition) = &stmt.condition {
|
|
Some(condition)
|
|
} else {
|
|
None
|
|
},
|
|
if let Some(exec) = &stmt.exec {
|
|
Some(exec)
|
|
} else {
|
|
None
|
|
},
|
|
&stmt.body,
|
|
),
|
|
Node::CallExpr(_, expr) => {
|
|
// we don't execute the function within the callback because
|
|
// (1) that would block the mutex for way longer than necessary and
|
|
// (2) recursive function calls would cause a deadlock (ask me how i know)
|
|
let (func, mut capture) = expr.func.eval(scope)?.as_fn(|func, capture| {
|
|
(func.clone(), capture.clone())
|
|
})?;
|
|
exec_fn(scope, &mut capture, &func.body, &func.params, &expr.params)
|
|
},
|
|
Node::ArrayExpr(pos, expr) => expr.array.eval(scope)?.as_array(|array| {
|
|
expr.index.eval(scope)?.as_int(|index| {
|
|
if *index >= 0 && *index < array.len() as i128 {
|
|
Ok(array[*index as usize].clone())
|
|
} else {
|
|
Err(Error::runtime_error(
|
|
pos.clone(),
|
|
format!("Array index {} out of bounds", index),
|
|
))
|
|
}
|
|
})?
|
|
})?,
|
|
Node::Break(_) => {
|
|
let mut val = Val::new(());
|
|
val.set_break(true);
|
|
Ok(val)
|
|
}
|
|
Node::ReturnStmt(_, expr) => {
|
|
let mut val = expr.eval(scope)?;
|
|
val.set_return(true);
|
|
Ok(val)
|
|
}
|
|
Node::Block(_, nodes) => {
|
|
let mut child_scope = Scope::new(scope);
|
|
for n in nodes {
|
|
let val = n.eval(&mut child_scope)?;
|
|
if val.is_return() || val.is_break() {
|
|
return Ok(val);
|
|
}
|
|
}
|
|
Ok(Val::new(()))
|
|
}
|
|
Node::TargetStmt(_, stmt) => {
|
|
let name = stmt.name.assert_ident()?;
|
|
let target_obj = Object::target(name.clone());
|
|
scope.object().add_child(target_obj.clone())?;
|
|
let mut target_scope = Scope::new_with_obj(scope, target_obj);
|
|
stmt.val.eval(&mut target_scope)?.assert_void()?;
|
|
Ok(Val::new(()))
|
|
}
|
|
Node::SetStmt(_, stmt) => {
|
|
let name = stmt.name.assert_ident()?;
|
|
let val = stmt.val.eval(scope)?;
|
|
scope.object().set(name.clone(), val);
|
|
Ok(Val::new(()))
|
|
}
|
|
Node::Fn(_, func) => {
|
|
if let Some(name) = &func.name {
|
|
let name = name.assert_ident()?;
|
|
let fn_obj = Object::func(name.clone(), func.clone());
|
|
scope.object().add_child(fn_obj)?;
|
|
scope.define(name.clone(), Val::new((func.clone(), scope.clone())));
|
|
}
|
|
Ok(Val::new((func.clone(), scope.clone())))
|
|
}
|
|
Node::DepList(_, node) => append_list(scope, node, KEY_DEPENDENCIES),
|
|
Node::SourceList(_, node) => append_list(scope, node, KEY_SOURCES),
|
|
Node::TypeStmt(_, node) => {
|
|
let val = node.eval(scope)?;
|
|
scope.object().set(String::from(KEY_TYPE), val);
|
|
Ok(Val::new(()))
|
|
}
|
|
n => panic!("Unexpected node {}", n),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Operator {
|
|
pub fn eval_unary(&self, scope: &mut Scope, node: &Node) -> Result<Val, Error> {
|
|
match self {
|
|
Operator::Minus => node.eval(scope)?.as_int(|i| Ok(Val::new(-*i)))?,
|
|
Operator::Bang => node.eval(scope)?.as_bool(|b| Ok(Val::new(!*b)))?,
|
|
_ => panic!("eval_unary() called on non-unary operator {}", self),
|
|
}
|
|
}
|
|
|
|
pub fn eval_binary(&self, scope: &mut Scope, lhs: &Node, rhs: &Node) -> Result<Val, Error> {
|
|
match self {
|
|
Operator::Eq => match lhs {
|
|
Node::Ident(pos, name) => {
|
|
let val = rhs.eval(scope)?;
|
|
match val.typ() {
|
|
Type::Void => {
|
|
Err(Error::type_error(pos.clone(), Type::Unknown, Type::Void))
|
|
}
|
|
_ => {
|
|
scope.define(name.clone(), val);
|
|
Ok(Val::new(()))
|
|
}
|
|
}
|
|
}
|
|
_ => Err(Error::runtime_error(
|
|
empty_pos(),
|
|
String::from("Cannot assign"),
|
|
)),
|
|
},
|
|
Operator::Plus => {
|
|
let left = lhs.eval(scope)?;
|
|
if left.is_string() {
|
|
let left = left.as_string(|s| s.clone())?;
|
|
rhs.eval(scope)?.as_string(|s| Val::new(left + s))
|
|
} else {
|
|
let left = left.as_int(|i| *i)?;
|
|
rhs.eval(scope)?.as_int(|i| Val::new(left + *i))
|
|
}
|
|
}
|
|
Operator::Minus => exec_math(scope, lhs, rhs, |l, r| l - r),
|
|
Operator::Asterisk => exec_math(scope, lhs, rhs, |l, r| l * r),
|
|
Operator::Slash => exec_math(scope, lhs, rhs, |l, r| l / r),
|
|
Operator::Percent => exec_math(scope, lhs, rhs, |l, r| l % r),
|
|
Operator::Pipe => exec_math(scope, lhs, rhs, |l, r| l | r),
|
|
Operator::Caret => exec_math(scope, lhs, rhs, |l, r| l ^ r),
|
|
Operator::Amp => exec_math(scope, lhs, rhs, |l, r| l & r),
|
|
Operator::GtGt => exec_math(scope, lhs, rhs, |l, r| l >> r),
|
|
Operator::LtLt => exec_math(scope, lhs, rhs, |l, r| l << r),
|
|
Operator::EqEq => exec_cmp(scope, lhs, rhs, |l, r| l == r),
|
|
Operator::Gt => exec_cmp(scope, lhs, rhs, |l, r| l > r),
|
|
Operator::GtEq => exec_cmp(scope, lhs, rhs, |l, r| l >= r),
|
|
Operator::Lt => exec_cmp(scope, lhs, rhs, |l, r| l < r),
|
|
Operator::LtEq => exec_cmp(scope, lhs, rhs, |l, r| l <= r),
|
|
Operator::PipePipe => exec_bool_math(scope, lhs, rhs, |l, r| l || r),
|
|
Operator::AmpAmp => exec_bool_math(scope, lhs, rhs, |l, r| l && r),
|
|
op => {
|
|
if let Some(assignment_op) = op.get_assignment_op() {
|
|
// XXX we evaluate lhs twice here, that's probably Not Good
|
|
let result = assignment_op.eval_binary(scope, lhs, rhs)?;
|
|
lhs.eval(scope)?.update(&result);
|
|
Ok(Val::new(()))
|
|
} else {
|
|
panic!("eval_binary() called on non-binary operator {}", self)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn exec_math<F>(scope: &mut Scope, lhs: &Node, rhs: &Node, op: F) -> Result<Val, Error>
|
|
where
|
|
F: FnOnce(i128, i128) -> i128,
|
|
{
|
|
let left = lhs.eval(scope)?.as_int(|i| *i)?;
|
|
let right = rhs.eval(scope)?.as_int(|i| *i)?;
|
|
Ok(Val::new(op(left, right)))
|
|
}
|
|
|
|
fn exec_bool_math<F>(scope: &mut Scope, lhs: &Node, rhs: &Node, op: F) -> Result<Val, Error>
|
|
where
|
|
F: FnOnce(bool, bool) -> bool,
|
|
{
|
|
let left = lhs.eval(scope)?.as_bool(|b| *b)?;
|
|
let right = rhs.eval(scope)?.as_bool(|b| *b)?;
|
|
Ok(Val::new(op(left, right)))
|
|
}
|
|
|
|
fn exec_cmp<F>(scope: &mut Scope, lhs: &Node, rhs: &Node, op: F) -> Result<Val, Error>
|
|
where
|
|
F: FnOnce(i128, i128) -> bool,
|
|
{
|
|
let left = lhs.eval(scope)?.as_int(|i| *i)?;
|
|
let right = rhs.eval(scope)?.as_int(|i| *i)?;
|
|
Ok(Val::new(op(left, right)))
|
|
}
|
|
|
|
fn exec_fn(
|
|
scope: &mut Scope,
|
|
capture: &mut Scope,
|
|
body: &FnBody,
|
|
param_names: &Vec<Node>,
|
|
param_exprs: &Vec<Node>,
|
|
) -> Result<Val, Error> {
|
|
let mut param_vals = Vec::new();
|
|
for expr in param_exprs {
|
|
param_vals.push(expr.eval(scope)?);
|
|
}
|
|
let param_vals = param_vals;
|
|
Ok(match body {
|
|
FnBody::Native(f) => f(param_vals)?,
|
|
FnBody::Sandbox(node) => {
|
|
let mut child_scope = Scope::new(capture);
|
|
for (i, n) in param_names.iter().enumerate() {
|
|
let param_name = n.assert_ident()?;
|
|
if i < param_vals.len() {
|
|
child_scope.define(param_name.clone(), param_vals[i].clone());
|
|
} else {
|
|
child_scope.define(param_name.clone(), Val::new(()));
|
|
}
|
|
}
|
|
let mut result = node.eval(&mut child_scope)?;
|
|
if result.is_return() {
|
|
result.set_return(false);
|
|
}
|
|
result
|
|
}
|
|
})
|
|
}
|
|
|
|
fn exec_if(
|
|
scope: &mut Scope,
|
|
condition: &Node,
|
|
then: &Node,
|
|
els: Option<&Node>,
|
|
) -> Result<Val, Error> {
|
|
let cond_result = condition.eval(scope)?.as_bool(|b| *b)?;
|
|
let mut child_scope = Scope::new(scope);
|
|
if cond_result {
|
|
then.eval(&mut child_scope)
|
|
} else if let Some(els) = els {
|
|
els.eval(&mut child_scope)
|
|
} else {
|
|
Ok(Val::new(()))
|
|
}
|
|
}
|
|
|
|
fn exec_while(scope: &mut Scope, condition: &Node, block: &Node) -> Result<Val, Error> {
|
|
loop {
|
|
let cond_result = condition.eval(scope)?.as_bool(|b| *b)?;
|
|
let mut child_scope = Scope::new(scope);
|
|
if cond_result {
|
|
let block_result = block.eval(&mut child_scope)?;
|
|
if block_result.is_break() {
|
|
break;
|
|
} else if block_result.is_return() {
|
|
return Ok(block_result);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
Ok(Val::new(()))
|
|
}
|
|
|
|
fn exec_for(
|
|
scope: &mut Scope,
|
|
setup: Option<&Node>,
|
|
condition: Option<&Node>,
|
|
exec: Option<&Node>,
|
|
block: &Node,
|
|
) -> Result<Val, Error> {
|
|
let mut for_scope = Scope::new(scope);
|
|
if let Some(setup) = setup {
|
|
setup.eval(&mut for_scope)?;
|
|
}
|
|
loop {
|
|
let cond_result = if let Some(condition) = condition {
|
|
condition.eval(&mut for_scope)?.as_bool(|b| *b)?
|
|
} else {
|
|
true
|
|
};
|
|
if cond_result {
|
|
let mut block_scope = Scope::new(&for_scope);
|
|
let block_result = block.eval(&mut block_scope)?;
|
|
if block_result.is_break() {
|
|
break;
|
|
} else if block_result.is_return() {
|
|
return Ok(block_result);
|
|
}
|
|
if let Some(exec) = exec {
|
|
exec.eval(&mut for_scope)?;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
Ok(Val::new(()))
|
|
}
|
|
|
|
fn append_list(scope: &mut Scope, node: &Node, key: &str) -> Result<Val, Error> {
|
|
let val = node.eval(scope)?;
|
|
match scope.object().get(key) {
|
|
Some(v) => v.as_array(|a| {
|
|
let result = val.as_array(|new_array| {
|
|
// XXX there are a *lot* of unnecessary copies here
|
|
let mut new_array = new_array.clone();
|
|
a.append(&mut new_array);
|
|
});
|
|
if result.is_err() {
|
|
a.push(val);
|
|
}
|
|
})?,
|
|
None => scope.object().set(String::from(key), val),
|
|
}
|
|
Ok(Val::new(()))
|
|
}
|
|
|
|
pub fn empty_pos() -> crate::lex::token::Position {
|
|
crate::lex::token::Position {
|
|
file: String::from("<unimplemented>"),
|
|
line: 0,
|
|
col: 0,
|
|
}
|
|
}
|