mod common;
pub mod controller;
mod lexer;
mod parser;
use crate::{
common::{Res, Stage},
lexer::Token::*,
};
use indexmap::IndexMap;
use log::{debug, trace, warn};
use rustc_hash::FxHasher;
use std::{
cmp::{max, min},
collections::HashSet,
fmt,
hash::BuildHasherDefault,
i32,
iter::Iterator,
mem,
sync::Arc,
usize,
};
use strsim::damerau_levenshtein as str_dist;
pub use crate::{
common::{BadderError, SourceRef},
lexer::Token,
parser::{AssignId, AssignIdKind, Ast, Parser},
};
pub type Int = i32;
pub type IntFlag = u8;
pub type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
pub(crate) const UNKNOWN_SRC_REF: SourceRef = SourceRef((0, 0), (0, 0));
pub(crate) const NO_FLAG: IntFlag = 0;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Builtin {
Size,
SeqAdd,
SeqRemove,
}
#[derive(Clone, Hash, PartialEq)]
pub enum FrameData {
Value(Int, IntFlag, SourceRef),
Callable(Vec<Token>, Arc<Ast>, SourceRef),
BuiltinCallable(Builtin),
ExternalCallable,
Ref(usize, Token),
Sequence(Vec<(Int, IntFlag)>, SourceRef),
LoopMarker,
}
impl fmt::Debug for FrameData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Value(x, ..) => write!(f, "Value({})", x),
Callable(..) => write!(f, "Callable"),
BuiltinCallable(..) => write!(f, "BuiltinCallable"),
ExternalCallable => write!(f, "ExternalCallable"),
Sequence(ref vals, ..) => write!(f, "Sequence({:?})", vals),
Ref(i, ref t) => write!(f, "Ref([{}]:{:?})", i, t),
LoopMarker => write!(f, "LoopMarker"),
}
}
}
impl FrameData {
fn desc(&self, id: &Token) -> String {
match *self {
Value(..) => format!("var {:?}", id),
Callable(ref args, ..) => format!(
"{:?}({})",
id,
args.iter()
.map(|a| format!("{:?}", a))
.fold(String::new(), |all, n| all + &n)
),
Sequence(..) | BuiltinCallable(..) => format!("{:?}", id),
Ref(..) => format!("ref->{:?}", id),
_ => "_".into(),
}
}
fn add_builtins_to(frame: &mut FxIndexMap<Token, FrameData>) {
frame.insert(Id("size(s)".into()), BuiltinCallable(Builtin::Size));
frame.insert(Id("add(sv)".into()), BuiltinCallable(Builtin::SeqAdd));
frame.insert(Id("remove(sv)".into()), BuiltinCallable(Builtin::SeqRemove));
}
}
#[derive(Debug)]
enum InterpreterUpFlow {
Error(BadderError),
LoopBreak,
LoopContinue,
FunReturn(Int, IntFlag),
Flagged(Int, IntFlag),
}
#[derive(Clone, Copy, Default)]
pub struct StackKey {
access_up_to: usize,
access_from: usize,
}
impl fmt::Debug for StackKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.access_from == self.access_up_to || self.access_from == self.access_up_to + 1 {
write!(f, "all-frames")
} else if self.access_up_to + 2 == self.access_from {
write!(f, "not-frame({})", self.access_up_to + 1)
} else {
write!(
f,
"not-frame({}-{})",
self.access_up_to + 1,
self.access_from - 1
)
}
}
}
impl StackKey {
fn from_fun_call(fun_declared_frame: usize, call_frame: usize) -> StackKey {
StackKey {
access_up_to: fun_declared_frame,
access_from: call_frame + 1,
}
}
fn can_access(&self, frame_index: usize) -> bool {
frame_index <= self.access_up_to || frame_index >= self.access_from
}
}
use crate::{FrameData::*, InterpreterUpFlow::*};
pub trait Overseer {
#[allow(clippy::result_unit_err)]
fn oversee(
&mut self,
stack: &[FxIndexMap<Token, FrameData>],
ast: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<(), ()>;
fn oversee_after(&mut self, _stack: &[FxIndexMap<Token, FrameData>], _ast: &Ast) {}
fn external_function_signatures(&self) -> &[Token];
fn call_external_function(
&mut self,
id: Token,
args: Vec<(Int, IntFlag)>,
) -> Result<(Int, IntFlag), String>;
}
pub struct NoOverseer;
impl Overseer for NoOverseer {
fn oversee(
&mut self,
_stack: &[FxIndexMap<Token, FrameData>],
_: &Ast,
_: usize,
_: StackKey,
) -> Result<(), ()> {
Ok(())
}
fn external_function_signatures(&self) -> &[Token] {
&[]
}
fn call_external_function(
&mut self,
id: Token,
_: Vec<(Int, IntFlag)>,
) -> Result<(Int, IntFlag), String> {
Err(format!("Unknown external function {:?}", id))
}
}
#[derive(Debug)]
pub struct Interpreter<O: Overseer> {
stack: Vec<FxIndexMap<Token, FrameData>>,
max_stack_len: usize,
overseer: O,
}
#[inline]
fn bool_to_num(b: bool) -> Int {
if b {
1
} else {
0
}
}
fn parent_error<T, S: Into<String>>(desc: S) -> Result<T, InterpreterUpFlow> {
Err(Error(
BadderError::at(UNKNOWN_SRC_REF).describe(Stage::Interpreter, desc.into()),
))
}
fn convert_signed_index(mut i: i32, length: usize) -> usize {
if length == 0 {
return max(i, 0) as usize;
}
let len = length as i32;
if i < 0 {
i = (len + i % len) % len;
}
i as usize
}
impl Default for Interpreter<NoOverseer> {
fn default() -> Interpreter<NoOverseer> {
Interpreter::new(50, NoOverseer)
}
}
impl<O: Overseer> Interpreter<O> {
pub fn new(max_stack_len: usize, overseer: O) -> Interpreter<O> {
let mut init_frame = IndexMap::default();
for ext_fun in overseer.external_function_signatures() {
init_frame.insert(ext_fun.clone(), FrameData::ExternalCallable);
}
FrameData::add_builtins_to(&mut init_frame);
Interpreter {
stack: vec![init_frame],
overseer,
max_stack_len,
}
}
fn unknown_id_err(&mut self, id: &Token, stack_key: StackKey) -> String {
let keys: HashSet<&Token> = self
.stack
.iter()
.enumerate()
.filter(|&(i, _)| stack_key.can_access(i))
.flat_map(|(_, m)| m.keys())
.collect();
if keys.is_empty() {
format!("id `{:?}` not found in scope", id)
} else {
assert!(
!keys.contains(id),
"!keys.contains(id): `{:?}` is available",
id
);
let mut available: Vec<_> = keys
.iter()
.map(|k| k.id_str().unwrap_or("#"))
.filter(|k| !k.starts_with('#'))
.collect();
available.sort_unstable(); if let Some(id) = id.id_str() {
available.sort_by_key(|known| str_dist(id, known));
}
let available = available.join(", ");
format!(
"id `{:?}` not found in scope, available: {{{}}}",
id, available
)
}
}
fn highest_frame_idx(
&self,
key: &Token,
current_scope: usize,
stack_key: StackKey,
) -> Option<usize> {
if self.stack.is_empty() {
return None;
}
let mut idx = min(self.stack.len() - 1, current_scope);
loop {
if stack_key.can_access(idx)
&& self.stack.len() > idx
&& self.stack[idx].contains_key(key)
{
return Some(idx);
}
if idx == 0 {
break;
}
idx -= 1;
}
None
}
#[inline]
fn log_eval(&mut self, ast: &Ast, current_scope: usize, stack_key: StackKey) {
match *ast {
Ast::Line(..) | Ast::LinePair(..) | Ast::Num(..) => (),
_ => trace!(
"base: {}, access: {:?}\nstack: {:?}\neval({:?})",
current_scope,
stack_key,
self.stack,
ast
),
}
}
#[inline]
fn eval_bin_op(
&mut self,
token: &Token,
left: &Ast,
right: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
($expr:expr; with_flag) => {
self.eval($expr, current_scope, stack_key).extract_flag()
};
}
macro_rules! unwrap_checked {
($c:expr) => {
$c.map(Ok).unwrap_or_else(|| parent_error("overflow"))
};
}
match *token {
Pls => unwrap_checked!(eval!(left)?.checked_add(eval!(right)?)),
Sub => unwrap_checked!(eval!(left)?.checked_sub(eval!(right)?)),
Mul => unwrap_checked!(eval!(left)?.checked_mul(eval!(right)?)),
Mod | Div => match (eval!(right)?, token) {
(0, _) => parent_error("Cannot divide by zero"),
(divisor, &Div) => unwrap_checked!(eval!(left)?.checked_div(divisor)),
(divisor, _) => unwrap_checked!(eval!(left)?.checked_rem(divisor)),
},
And => match eval!(left)? {
0 => Ok(0),
_ => {
self.eval(right, current_scope, stack_key)
}
},
Or => match eval!(left; with_flag)? {
(0, _) => {
self.eval(right, current_scope, stack_key)
}
(v, 0) => Ok(v),
(v, flag) => Err(Flagged(v, flag)),
},
Is => Ok(bool_to_num(eval!(left)? == eval!(right)?)),
Gt => Ok(bool_to_num(eval!(left)? > eval!(right)?)),
Lt => Ok(bool_to_num(eval!(left)? < eval!(right)?)),
GtEq => Ok(bool_to_num(eval!(left)? >= eval!(right)?)),
LtEq => Ok(bool_to_num(eval!(left)? <= eval!(right)?)),
_ => parent_error(format!("Unexpected BinOp token `{:?}`", token)),
}
}
#[inline]
fn eval_while(
&mut self,
expr: &Ast,
block: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
}
if eval!(expr)? == 0 {
return Ok(0);
}
let loop_token = Id("#loop".into());
self.stack[current_scope].insert(loop_token.clone(), LoopMarker);
loop {
self.stack.push(IndexMap::default());
let eval = self.eval(block, current_scope + 1, stack_key);
self.stack.pop();
match eval {
Err(LoopBreak) => break,
Ok(_) | Err(Flagged(..)) | Err(LoopContinue) => (),
err @ Err(_) => return err,
};
if eval!(expr)? == 0 {
break;
}
}
self.stack[current_scope].swap_remove(&loop_token);
Ok(0)
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn eval_for_in(
&mut self,
idx_id: &Option<Token>,
item_id: &Token,
list_expr: &Ast,
block: &Ast,
src: SourceRef,
current_scope: usize,
stack_key: StackKey,
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval_seq {
($expr:expr) => {
self.eval_seq($expr, current_scope, stack_key)
};
}
let mut list = eval_seq!(list_expr)?;
if list.is_empty() {
return Ok(0);
}
let loop_token = Id("#loop".into());
self.stack[current_scope].insert(loop_token.clone(), LoopMarker);
let mut index = 0;
while index < list.len() {
let mut frame = IndexMap::default();
if let Some(ref id) = *idx_id {
frame.insert(id.clone(), Value(index as i32, NO_FLAG, src));
}
let (v, flag) = list[index];
frame.insert(item_id.clone(), Value(v, flag, src));
self.stack.push(frame);
let eval = self.eval(block, current_scope + 1, stack_key);
self.stack.pop();
match eval {
Err(LoopBreak) => break,
Ok(_) | Err(Flagged(..)) | Err(LoopContinue) => (),
err @ Err(_) => return err,
};
index += 1;
list = eval_seq!(list_expr)?;
}
self.stack[current_scope].swap_remove(&loop_token);
Ok(0)
}
#[inline]
fn eval_fun_call(
&mut self,
id: &Token,
args: &[Ast],
current_scope: usize,
stack_key: StackKey,
deferred_oversee: (&Ast, usize, StackKey),
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
($expr:expr; with_flag) => {
self.eval($expr, current_scope, stack_key).extract_flag()
};
}
macro_rules! eval_seq {
($expr:expr) => {
self.eval_seq($expr, current_scope, stack_key)
};
}
macro_rules! highest_frame_idx {
($index:expr) => {
self.highest_frame_idx($index, current_scope, stack_key)
};
}
macro_rules! oversee_deferred {
($deferred_oversee:expr) => {{
let (defer_ast, defer_scope, defer_key) = $deferred_oversee;
if self
.overseer
.oversee(&self.stack, defer_ast, defer_scope, defer_key)
.is_err()
{
return Err(Error(
BadderError::at(defer_ast.src()).describe(Stage::Interpreter, "cancelled"),
));
}
}};
}
if let Some(idx) = highest_frame_idx!(id) {
let mut builtin_callable = None;
if let BuiltinCallable(builtin) = self.stack[idx][id] {
builtin_callable = Some(builtin);
}
if let Some(builtin) = builtin_callable {
return self.call_builtin(
builtin,
args,
current_scope,
stack_key,
deferred_oversee,
);
}
if self.stack[idx][id] == ExternalCallable {
let mut evaluated_args = vec![];
for arg in args {
evaluated_args.push(eval!(arg; with_flag)?);
}
oversee_deferred!(deferred_oversee);
return self.call_external(id.clone(), evaluated_args);
}
let (mut arg_ids, callable_block, src) = {
match self.stack[idx][id] {
Callable(ref arg_ids, ref block, src) => {
(arg_ids.clone(), Arc::clone(block), src)
}
_ => {
return parent_error(format!(
"Invalid reference to non callable `{:?}`",
id
));
}
}
};
let mut f_frame = IndexMap::default();
for i in 0..args.len() {
let data = match args[i] {
ref a @ Ast::Seq(..) => Sequence(eval_seq!(a)?, src),
Ast::ReferSeq(ref id, ..) => {
if let Some(idx) = highest_frame_idx!(id) {
Ref(idx, id.clone())
} else {
return parent_error(self.unknown_id_err(id, stack_key));
}
}
ref ast => {
let (v, flag) = eval!(ast; with_flag)?;
Value(v, flag, src)
}
};
f_frame.insert(mem::replace(&mut arg_ids[i], Eol), data);
}
oversee_deferred!(deferred_oversee);
self.stack.push(f_frame);
let out = match self.eval(
&callable_block,
current_scope + 1,
StackKey::from_fun_call(idx, current_scope),
) {
Ok(x) | Err(FunReturn(x, 0)) => Ok(x),
Err(FunReturn(value, flag)) => Err(Flagged(value, flag)),
x => x,
};
self.stack.pop();
out
} else {
parent_error(self.unknown_id_err(id, stack_key))
}
}
#[inline]
fn eval_refer_seq_index(
&mut self,
seq_id: &Token,
index_expr: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
($expr:expr; with_flag) => {
self.eval($expr, current_scope, stack_key).extract_flag()
};
}
if let Some(idx) = self.highest_frame_idx(seq_id, current_scope, stack_key) {
let (mut idx, mut seq_id) = (idx, seq_id.clone());
while let Ref(n_idx, ref n_id) = self.stack[idx][&seq_id] {
idx = n_idx;
seq_id = n_id.clone();
}
let seq_len = match self.stack[idx][&seq_id] {
Sequence(ref v, ..) => Ok(v.len()),
ref data => parent_error(format!(
"Invalid sequence index reference to non-sequence `{}`",
data.desc(&seq_id)
)),
}?;
let actual_index = eval!(index_expr)?;
let index = convert_signed_index(actual_index, seq_len);
if seq_len == 0 {
return parent_error(format!(
"Invalid sequence index {} not in empty sequence",
actual_index
));
} else if seq_len <= index {
return parent_error(format!(
"Invalid sequence index {} not in 0..{} (or negative)",
index, seq_len
));
}
Ok(match self.stack[idx][&seq_id] {
Sequence(ref vec, ..) => match vec[index] {
(v, 0) => v,
(v, flag) => return Err(Flagged(v, flag)),
},
_ => unreachable!(),
})
} else {
parent_error(self.unknown_id_err(seq_id, stack_key))
}
}
#[inline]
fn eval_reassign_seq_index(
&mut self,
seq_id: &Token,
index_expr: &Ast,
expr: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
($expr:expr; with_flag) => {
self.eval($expr, current_scope, stack_key).extract_flag()
};
}
if let Some(idx) = self.highest_frame_idx(seq_id, current_scope, stack_key) {
let (mut idx, mut seq_id) = (idx, seq_id.clone());
while let Ref(n_idx, ref n_id) = self.stack[idx][&seq_id] {
idx = n_idx;
seq_id = n_id.clone();
}
let seq_len = match self.stack[idx][&seq_id] {
Sequence(ref v, ..) => Ok(v.len()),
ref data => parent_error(format!(
"Invalid sequence index reassignment to non-sequence `{}`",
data.desc(&seq_id)
)),
}?;
let index = convert_signed_index(eval!(index_expr)?, seq_len);
if seq_len <= index {
return parent_error(format!(
"Invalid sequence index {} not in 0..{} (or negative)",
index, seq_len
));
}
let new_val = eval!(expr; with_flag)?;
match self.stack[idx].get_mut(&seq_id) {
Some(&mut Sequence(ref mut vec, ..)) => vec[index] = new_val,
_ => unreachable!(),
}
Ok(0)
} else {
parent_error(self.unknown_id_err(seq_id, stack_key))
}
}
fn eval(
&mut self,
ast: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<Int, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
($expr:expr; with_flag) => {
self.eval($expr, current_scope, stack_key).extract_flag()
};
}
macro_rules! eval_seq {
($expr:expr) => {
self.eval_seq($expr, current_scope, stack_key)
};
}
macro_rules! highest_frame_idx {
($index:expr) => {
self.highest_frame_idx($index, current_scope, stack_key)
};
}
self.log_eval(ast, current_scope, stack_key);
let mut deferred_oversee = None;
if let Ast::Call(..) = *ast {
deferred_oversee = Some((ast, current_scope, stack_key))
} else if self
.overseer
.oversee(&self.stack, ast, current_scope, stack_key)
.is_err()
{
return Err(Error(
BadderError::at(ast.src()).describe(Stage::Interpreter, "cancelled"),
));
}
let result = match *ast {
Ast::Num(Num(x), ..) => Ok(x),
Ast::BinOp(ref token, ref left, ref right, ..) => {
self.eval_bin_op(token, left, right, current_scope, stack_key)
}
Ast::LeftUnaryOp(Sub, ref val, ..) => Ok(-eval!(val)?),
Ast::LeftUnaryOp(Not, ref val, ..) => Ok(match eval!(val)? {
0 => 1,
_ => 0,
}),
Ast::Assign(ref id, ref expr, src) => {
let (v, flag) = eval!(expr; with_flag)?;
self.stack[current_scope].insert(id.clone(), Value(v, flag, src));
Ok(v)
}
Ast::Reassign(ref id, ref expr, ..) => {
if let Some(idx) = highest_frame_idx!(id) {
let (v, flag) = eval!(expr; with_flag)?;
let ass_src = match self.stack[idx][id] {
Value(.., src) => src,
_ => unreachable!(),
};
*self.stack[idx].get_mut(id).unwrap() = Value(v, flag, ass_src);
Ok(v)
} else {
parent_error(format!(
"{}, or did you mean `var {:?} =`?",
self.unknown_id_err(id, stack_key),
id
))
}
}
Ast::Refer(ref id, ..) => {
if let Some(idx) = highest_frame_idx!(id) {
match self.stack[idx][id] {
Value(v, flag, ..) => Err(InterpreterUpFlow::Flagged(v, flag)),
_ => parent_error(format!("Invalid reference to non number `{:?}`", id)),
}
} else {
parent_error(self.unknown_id_err(id, stack_key))
}
}
Ast::If(ref expr, ref block, ref else_line, ..) => Ok(match eval!(expr)? {
0 => match *else_line {
Some(ref else_line) => eval!(else_line)?,
None => 0,
},
_ => {
self.stack.push(IndexMap::default());
let eval = self.eval(block, current_scope + 1, stack_key).ignore_flag();
self.stack.pop();
eval?;
0
}
}),
Ast::While(ref expr, ref block, ..) => {
self.eval_while(expr, block, current_scope, stack_key)
}
Ast::ForIn(ref idx_id, ref item_id, ref list_expr, ref block, src) => self.eval_for_in(
idx_id,
item_id,
list_expr,
block,
src,
current_scope,
stack_key,
),
Ast::LoopNav(ref token, ..) => {
let loop_token = Id("#loop".into());
if highest_frame_idx!(&loop_token).is_some() {
match *token {
Break => Err(LoopBreak),
Continue => Err(LoopContinue),
_ => parent_error(format!("Unknown loop nav `{:?}`", token)),
}
} else {
parent_error(format!("Invalid use of loop nav `{:?}`", token))
}
}
Ast::AssignFun(ref id, ref args, ref block, src) => {
let top = self.stack.len() - 1;
match self.stack[current_scope].get(id) {
None | Some(&Callable(..)) => (), Some(other) => {
let desc = format!(
"Declaration `fun {:?}` conflicts with `{}` in same scope",
id,
other.desc(id)
);
return Err(Error(
BadderError::at(ast.src()).describe(Stage::Interpreter, desc),
));
}
};
self.stack[top].insert(id.clone(), Callable(args.clone(), Arc::clone(block), src));
Ok(0)
}
Ast::Call(ref id, ref args, ..) => self.eval_fun_call(
id,
args,
current_scope,
stack_key,
deferred_oversee.unwrap(),
),
Ast::Return(ref expr, ..) => {
let (v, flag) = eval!(expr; with_flag)?;
Err(FunReturn(v, flag))
}
Ast::ReferSeqIndex(ref seq_id, ref index_expr, ..) => {
self.eval_refer_seq_index(seq_id, index_expr, current_scope, stack_key)
}
Ast::ReassignSeqIndex(ref seq_id, ref index_expr, ref expr, ..) => {
self.eval_reassign_seq_index(seq_id, index_expr, expr, current_scope, stack_key)
}
Ast::AssignSeq(ref id, ref list, src) => {
let v = eval_seq!(list)?;
match self.stack[current_scope].get(id) {
None | Some(&Sequence(..)) => (), Some(other) => {
let desc = format!(
"Assignment of `seq {:?}[]` conflicts with `{}` in same scope",
id,
other.desc(id)
);
return Err(Error(
BadderError::at(ast.src()).describe(Stage::Interpreter, desc),
));
}
};
self.stack[current_scope].insert(id.clone(), Sequence(v, src));
Ok(0)
}
Ast::Line(scope, ref expr, ..) => {
let scope = max(current_scope, scope);
if scope > self.max_stack_len {
return parent_error("stack overflow");
}
self.eval(expr, scope, stack_key).ignore_flag()
}
Ast::LinePair(ref line, ref next_line, ..) => {
eval!(line)?;
let mut next = next_line;
while let Ast::LinePair(ref l2, ref l3, ..) = **next {
eval!(l2)?;
next = l3;
}
eval!(next)
}
Ast::Empty(..) => Ok(0),
_ => parent_error(format!("Unexpected syntax {:?}", ast)),
};
self.overseer.oversee_after(&self.stack, ast);
match result {
Err(Error(BadderError {
stage,
description,
src,
})) => {
if src == UNKNOWN_SRC_REF {
Err(Error(
BadderError::at(ast.src()).describe(stage, description),
))
} else {
Err(Error(BadderError::at(src).describe(stage, description)))
}
}
x => x,
}
}
fn eval_seq(
&mut self,
list: &Ast,
current_scope: usize,
stack_key: StackKey,
) -> Result<Vec<(Int, IntFlag)>, InterpreterUpFlow> {
macro_rules! eval {
($expr:expr) => {
self.eval($expr, current_scope, stack_key).ignore_flag()
};
($expr:expr; with_flag) => {
self.eval($expr, current_scope, stack_key).extract_flag()
};
}
let result = match *list {
Ast::Seq(ref exprs, ..) => {
let mut evals = vec![];
for ex in exprs {
evals.push(eval!(ex; with_flag)?);
}
Ok(evals)
}
Ast::ReferSeq(ref id, ..) => {
if let Some(idx) = self.highest_frame_idx(id, current_scope, stack_key) {
let (mut idx, mut id) = (idx, id.clone());
while let Ref(n_idx, ref n_id) = self.stack[idx][&id] {
idx = n_idx;
id = n_id.clone();
}
match self.stack[idx][&id] {
Sequence(ref v, ..) => Ok(v.clone()),
ref data => parent_error(format!(
"Invalid sequence referral to non-sequence `{}`",
data.desc(&id)
)),
}
} else {
parent_error(self.unknown_id_err(id, stack_key))
}
}
_ => parent_error(format!("Unexpected Seq syntax {:?}", list)),
};
match result {
Err(Error(BadderError {
stage,
description,
src,
})) => {
if src == UNKNOWN_SRC_REF {
Err(Error(
BadderError::at(list.src()).describe(stage, description),
))
} else {
Err(Error(BadderError::at(src).describe(stage, description)))
}
}
x => x,
}
}
fn call_builtin(
&mut self,
builtin: Builtin,
args: &[Ast],
current_scope: usize,
stack_key: StackKey,
deferred_oversee: (&Ast, usize, StackKey),
) -> Result<Int, InterpreterUpFlow> {
let arg1 = if args.len() == 2 {
Some(
self.eval(&args[1], current_scope, stack_key)
.extract_flag()?,
)
} else {
None
};
let (defer_ast, defer_scope, defer_key) = deferred_oversee;
if self
.overseer
.oversee(&self.stack, defer_ast, defer_scope, defer_key)
.is_err()
{
return Err(Error(
BadderError::at(defer_ast.src()).describe(Stage::Interpreter, "cancelled"),
));
}
match args[0] {
Ast::ReferSeq(ref id, ..) => {
if let Some(idx) = self.highest_frame_idx(id, current_scope, stack_key) {
let (mut idx, mut id) = (idx, id.clone());
while let Ref(n_idx, ref n_id) = self.stack[idx][&id] {
idx = n_idx;
id = n_id.clone();
}
match self.stack[idx].get_mut(&id) {
Some(&mut Sequence(ref mut v, ..)) => match builtin {
Builtin::Size => Ok(v.len() as i32),
Builtin::SeqAdd => {
if v.len() == (i32::MAX - 1) as usize {
return parent_error("`add(sv)` failed, sequence is max size");
}
v.push(arg1.unwrap());
Ok(0)
}
Builtin::SeqRemove => {
let index = convert_signed_index(arg1.unwrap().0, v.len());
if v.is_empty() {
return parent_error(format!(
"Invalid sequence index {} not in empty sequence",
index
));
} else if v.len() <= index {
return parent_error(format!(
"Invalid sequence index {} not in 0..{} (or negative)",
index,
v.len()
));
}
match v.remove(index) {
(v, 0) => Ok(v),
(v, flag) => Err(Flagged(v, flag)),
}
}
},
Some(ref data) => parent_error(format!(
"Invalid sequence referral to non-sequence `{}`",
data.desc(&id)
)),
None => unreachable!(),
}
} else {
parent_error(self.unknown_id_err(id, stack_key))
}
}
ref ast => {
let literal = self.eval_seq(ast, current_scope, stack_key)?;
match builtin {
Builtin::Size => Ok(literal.len() as i32),
Builtin::SeqAdd => Ok(0),
Builtin::SeqRemove => {
let index = convert_signed_index(arg1.unwrap().0, literal.len());
match literal[index] {
(v, 0) => Ok(v),
(v, flag) => Err(Flagged(v, flag)),
}
}
}
}
}
}
fn call_external(
&mut self,
id: Token,
args: Vec<(Int, IntFlag)>,
) -> Result<Int, InterpreterUpFlow> {
match self.overseer.call_external_function(id, args) {
Ok((result, 0)) => Ok(result),
Ok((result, flag)) => Err(Flagged(result, flag)),
Err(desc) => parent_error(desc),
}
}
pub fn evaluate(&mut self, ast: &Ast) -> Res<Int> {
match self.eval(ast, 0, StackKey::default()).ignore_flag() {
Ok(x) => Ok(x),
Err(Error(desc)) => Err(desc),
Err(err) => panic!("{:?}", err),
}
}
}
trait IgnoreIntFlag {
fn ignore_flag(self) -> Self;
fn extract_flag(self) -> Result<(Int, IntFlag), InterpreterUpFlow>;
}
impl IgnoreIntFlag for Result<Int, InterpreterUpFlow> {
#[inline]
fn ignore_flag(self) -> Self {
match self {
Ok(x) | Err(InterpreterUpFlow::Flagged(x, ..)) => Ok(x),
err => err,
}
}
#[inline]
fn extract_flag(self) -> Result<(Int, IntFlag), InterpreterUpFlow> {
match self {
Ok(x) => Ok((x, 0)),
Err(InterpreterUpFlow::Flagged(x, flag)) => Ok((x, flag)),
Err(err) => Err(err),
}
}
}
#[cfg(test)]
#[macro_use]
mod util {
use super::*;
use std::{
sync::mpsc,
thread,
time::{Duration, Instant},
};
fn eval_within(code: &str, timeout: Duration) -> Res<Int> {
let (sender, receiver) = mpsc::channel();
let before_parse = Instant::now();
debug!("parsing...");
let code: Ast = Parser::parse_str(code)?;
debug!("parsed in {:?}, interpreting...", before_parse.elapsed());
thread::spawn(move || {
let before_interp = Instant::now();
sender.send(Interpreter::default().evaluate(&code)).unwrap();
debug!("interpreted in {:?}", before_interp.elapsed());
});
let now = Instant::now();
while now.elapsed() < timeout {
match receiver.try_recv() {
Ok(res) => return res,
_ => thread::sleep(Duration::from_millis(5)),
}
}
Err(BadderError::at(SourceRef((0, 0), (0, 0))) .describe(
Stage::Interpreter,
format!("Program did not return within {:?}", timeout),
))
}
fn print_program_debug(code: &str) -> Res<()> {
let ast = Parser::parse_str(code)?;
debug!("Program: \n{}", ast.debug_string());
Ok(())
}
pub fn result(code: &str, debug_output: bool) -> Int {
let _ = env_logger::try_init();
if debug_output {
print_program_debug(code).unwrap();
}
eval_within(code, Duration::from_secs(1)).unwrap()
}
pub fn error(code: &str) -> BadderError {
let _ = env_logger::try_init();
let out = eval_within(code, Duration::from_secs(1));
if out.is_ok() {
print_program_debug(code).unwrap();
}
assert!(out.is_err(), "Unexpected {:?}", out);
if let Err(reason) = out {
return reason;
}
unreachable!();
}
#[macro_export]
macro_rules! assert_program {
($( $code:expr );+ => $out:expr, debug_parse=$debug:expr) => {{
let mut code = String::new();
$(
code = code + $code + "\n";
)+
assert_eq!(util::result(&code, $debug), $out);
}};
($( $code:expr );+ => $out:expr) => {{
let mut code = String::new();
$(
code = code + $code + "\n";
)+
assert_eq!(util::result(&code, true), $out);
}};
($( $code:expr );+ =>X $( $sub:expr ),+) => {
let mut code = String::new();
$(
code = code + $code + "\n";
)+
let err = util::error(&code);
let err_lower = err.description.to_lowercase();
$(
let substring_lower = $sub.to_lowercase();
assert!(err_lower.contains(substring_lower.as_str()),
"Substring:`{}` not in error: `{:?}`", $sub, err);
)+
};
($( $code:expr );+ =>X $( $sub:expr ),+; src = $src_ref:expr ) => {
let mut code = String::new();
$(
code = code + $code + "\n";
)+
let err = util::error(&code);
let err_lower = err.description.to_lowercase();
$(
let substring_lower = $sub.to_lowercase();
assert!(err_lower.contains(substring_lower.as_str()),
"Substring:`{}` not in error: `{:?}`", $sub, err);
)+
assert_eq!(err.src, $src_ref);
};
}
}
#[cfg(test)]
mod issues {
use super::*;
#[test]
fn variable_in_function_loop() {
assert_program!("fun some_func()";
" var count";
" for i in 1,2,3";
" count += i";
" count";
"loop";
" if 1";
" some_func()";
" break";
"1" => 1);
}
#[test]
fn if_else_if_else_never_hitting_else() {
assert_program!(
"var x = 123";
"var y = 234";
"if x is 123 and y is 123";
" # bunch of stuff";
" if 1";
" var z = x + y";
" call_some_function()";
"else if y is not 234";
" # more stuff";
" var z";
" if 1 - 1";
" z = x - 2 * y";
" else";
" z = x - y";
" call_another_func()";
"else ";
" x = 3";
" y = 5";
"x + y" => 8);
}
#[test]
fn braces_usage_in_fn_args() {
assert_program!(
"fun foo(n)";
" n";
"foo((3 + 5) / 2)" => 4);
}
#[test]
fn braces_usage_in_fn_args_2() {
assert_program!(
"fun foo(a, b)";
" a + b";
"seq bar[] = 1, 2, 3";
"foo((3 + 5) / 2, 1 + (bar[1] * 4) + 1)" => 14);
}
#[test]
fn braces_usage_in_fn_args_dotcall() {
assert_program!(
"fun foo(n)";
" n";
"((3 + 5) / 2).foo()" => 4);
}
}
#[cfg(test)]
mod fitness {
use super::*;
#[test]
fn long_program() {
assert_program!("var init";
"fun inc()";
&" init += 1\n".repeat(10_000);
"inc()";
"init" => 10_000, debug_parse = false); }
#[test]
fn recursive_overflow() {
assert_program!("fun rec()";
" rec()";
"rec()" =>X "stack overflow"; src = SourceRef((2,5), (2,10)));
}
}
#[cfg(test)]
mod core_lib {
use super::*;
#[test]
fn seq_size() {
assert_program!("seq nums[] = 1,2,3,4,5,4,3,2,1";
"size(nums[])"; "nums[].size()" => 9);
}
#[test]
fn seq_size_literal() {
assert_program!("size((5,4,3,2))" => 4);
}
#[test]
fn seq_remove() {
assert_program!("seq nums[] = 1,2,3";
"remove(nums[], 1)"; "nums[].remove(0)";
"nums[].size()" => 1);
}
#[test]
fn seq_remove_return_removed() {
assert_program!("seq nums[] = 1,2,3";
"nums[].remove(0)" => 1);
}
#[test]
fn seq_remove_literal() {
assert_program!("remove((5,4,3,2), 2)" => 3);
}
#[test]
fn seq_remove_neg_index() {
assert_program!("seq nums[] = 1,2,3";
"nums[].remove(-1)"; "var last";
"for n in nums[]";
" last = n";
"last" => 2);
}
#[test]
fn seq_add() {
assert_program!("seq nums[] = 1,2";
"add(nums[], 4999)"; "nums[].add(5000)";
"nums[3]" => 5000);
}
#[test]
fn seq_add_literal() {
assert_program!("add((5,4,3,2), 5000)" => 0);
}
}
#[cfg(test)]
mod list_functions {
use super::*;
#[test]
fn def_seq_function() {
assert_program!("fun count_number_of(list[], num)";
" var count";
" for n in list[]";
" if n is num";
" count += 1";
" count";
"seq fib[] = 1,1,2,3,5";
"count_number_of(fib[], 1)" => 2);
}
#[test]
fn pass_seq_by_reference() {
assert_program!("fun overwrite_with(list[], num)";
" for i, n in list[]";
" list[i] = num";
"seq fib[] = 1,1,2,3,5";
"overwrite_with(fib[], 4)";
"fib[3]" => 4);
}
#[test]
fn dot_call_syntax() {
assert_program!("fun first(list[])";
" list[0]";
"seq fib[] = 1,1,2,3,5";
"fib[].first()" => 1);
assert_program!("fun last_plus(list[], num)";
" var last";
" for n in list[]";
" last = n";
" last + num";
"seq fib[] = 1,1,2,3,5";
"fib[].last_plus(3)" => 8);
}
}
#[cfg(test)]
mod lists {
use super::*;
#[test]
fn init() {
assert_program!("seq nums[]" => 0);
assert_program!("seq nums[] = 1,2,3" => 0);
assert_program!("var x = 2";
"fun a()";
" 123";
"seq nums[] = x, a(), 3" => 0);
}
#[test]
fn index_access() {
assert_program!("seq nums[] = 1,2,3";
"nums[1]" => 2);
}
#[test]
fn negative_index_access() {
assert_program!("seq nums[] = 1,2,3";
"nums[-1]" => 3);
assert_program!("seq nums[] = 1,2,3";
"nums[-3]" => 1);
assert_program!("seq nums[] = 1,2,3";
"nums[-13]" => 3);
}
#[test]
fn index_reassignment() {
assert_program!("seq nums[] = 1,2,3";
"nums[0] = 435";
"nums[0]" => 435);
}
#[test]
fn manual_iteration() {
assert_program!("seq nums[] = 1,1,2,3,5";
"var sum";
"var i";
"while i < 5";
" sum += nums[i]";
" i += 1";
"sum" => 12);
}
#[test]
fn iteration() {
assert_program!("seq nums[] = 1,1,2,3,5";
"var sum";
"for num in nums[]";
" sum += num";
"sum" => 12);
}
#[test]
fn literal_iteration() {
assert_program!("var product = 1";
"for n in 5,4,3,2,1";
" product *= n";
"product" => 120);
}
#[test]
fn enumerated_iteration() {
assert_program!("seq nums[] = 1,1,2,3,5";
"var index_sum";
"for index, num in nums[]";
" index_sum += index";
"index_sum" => 10);
}
#[test]
fn fib_loop_function() {
assert_program!("fun fib(n)";
" var its = 1";
" var last";
" var curr = 1";
" while its < n";
" var next = curr + last";
" last = curr";
" curr = next";
" its += 1";
" curr";
"fib(12)" => 144);
}
#[test]
fn distinct_signature_from_var() {
assert_program!("var f = 12";
"seq f[] = 13";
"f[0]" => 13);
assert_program!("var f = 12";
"seq f[] = 13";
"f" => 12);
}
#[test]
fn distinct_signature_from_fun() {
assert_program!("fun f()";
" 12";
"seq f[] = 13";
"f[0]" => 13);
assert_program!("fun f()";
" 12";
"seq f[] = 13";
"f()" => 12);
}
}
#[cfg(test)]
mod functions {
use super::*;
#[test]
fn basic_function() {
assert_program!("fun two()";
" return 2";
"two()" => 2);
}
#[test]
fn function_scope() {
assert_program!("var n = 0";
"fun npp()";
" n += 1";
" return n";
"var n1 = npp()";
"var n2 = npp()";
"n1 + n2" => 3);
}
#[test]
fn function_ref_scope() {
assert_program!("var out";
"var c = -11";
"if 1";
" var a = 12"; " var b = -5";
" var c = 123";
" if 2";
" fun a_and_b_p1()";
" var c = 1"; " return a + b + c";
" var b = 6"; " var c = -123123";
" if 3";
" var a = 1";
" var c = 999";
" if 4";
" var b = 643";
" out = a_and_b_p1()"; "out" => 19);
}
#[test]
fn function_arg() {
assert_program!("fun plus_one(a_number)";
" return a_number + 1";
"plus_one(12)" => 13);
}
#[test]
fn function_return_last_line() {
assert_program!("fun double(n)";
" n * 2";
"double(12)" => 24);
}
#[test]
fn function_complex_args() {
assert_program!("var x = 12";
"var always_add = 12";
"fun plus_2(x)";
" x + 2";
"fun sum(x, y, z)";
" always_add + x + y + z";
"sum(1, x, plus_2(always_add))" => 39);
}
#[test]
fn fib_function() {
assert_program!("fun fib(n)";
" if n < 3";
" return 1";
" fib(n-1) + fib(n-2)";
"fib(12)" => 144);
}
#[test]
fn dot_call_style() {
assert_program!("fun is_even(num)";
" num % 2 is 0";
"12.is_even()" => 1);
assert_program!("fun divisible_by(num, divisor)";
" num % divisor is 0";
"18.divisible_by(6)" => 1);
}
#[test]
fn dot_chaining() {
assert_program!("fun double(n)";
" n * 2";
"(5.double().double()).double()" => 40);
}
#[test]
fn overwrite_fun_with_fun() {
assert_program!("fun f()";
" 1";
"fun f()";
" 2";
"f()" => 2);
}
#[test]
fn distinct_signature_from_var() {
assert_program!("var f = 12";
"fun f()";
" 2";
"f()" => 2);
assert_program!("var f = 12";
"fun f()";
" 2";
"f" => 12);
}
#[test]
fn call_by_signature_same_scope() {
assert_program!("fun max(a, b)";
" if b > a";
" return b";
" a";
"fun max(a, b, c)";
" max(a, max(b, c))";
"max(1, 3, 2)" => 3);
}
#[test]
fn call_by_signature_across_scope() {
assert_program!("fun number(a)";
" a";
"var out";
"if 1";
" fun number(b, c)";
" b + c";
" out = number(2)";
"out" => 2);
}
}
#[cfg(test)]
mod loops {
use super::*;
#[test]
fn loop_and_break() {
assert_program!("var x = 1";
"loop";
" x *= 2";
" if x > 10";
" break";
"x" => 16);
}
#[test]
fn loop_continue_break() {
assert_program!("var x = 1";
"loop";
" x -= 1";
" if x <= -5";
" x *= -3";
" continue";
" if x > 10";
" break";
"x" => 14);
}
#[test]
fn while_loop() {
assert_program!("var x = 1";
"while x < 50";
" x *= 3";
"x" => 81);
}
#[test]
fn while_continue_break() {
assert_program!("var x";
"while x < 50";
" x += 30";
" if x >= 50";
" break";
" if x % 2 is 1";
" continue";
" x -= 31";
"x" => 59);
}
}
#[cfg(test)]
mod if_scope {
use super::*;
#[test]
fn if_flow() {
assert_program!("var x = 200";
"var y = 100";
"if x is 200";
" x = 123";
"if y is 200";
" y = 123";
"x + y" => 223);
assert_program!("var x = 0";
"var y = 0";
"if x is y";
" x = 20";
"if x is y";
" y = 30";
"x + y" => 20);
}
#[test]
fn if_scope() {
assert_program!("var x = 200";
"var y = 111";
"if x is 200";
" var x = 300"; " y = 300"; "x + y" => 500);
assert_program!("if 1";
" var x = 234";
"x" =>X "x", "scope"; src = SourceRef((3,1), (3,2)));
}
#[test]
fn if_junkspace() {
assert_program!("";
"# about to do an if";
"var x # define a juicy var";
"";
"if x is 0 # this should always work";
"";
" "; " ";
" # hmm";
" # why did i write this?"; " x += 50";
"# blankers helps the program be readable maybe";
"";
"";
" x *= 2";
" ";
" ";
"x";
"";
"# finished now" => 100);
}
#[test]
fn if_scope_err() {
assert_program!("var x = 200";
"if x is not 0";
" x -= 1"; " x += 1";
"x" =>X "indent"; src = SourceRef((3,1), (3,4)));
}
#[test]
fn multi_scope() {
assert_program!("var x = 200";
"if x is not 0";
" var y = x";
" if y is 200";
" var z = y";
" if z is not 999";
" y /= 2";
" x = y + z";
"x" => 300);
}
#[test]
fn if_else() {
assert_program!("var x";
"if x";
" x = 14";
"else";
" x = -9000";
"x" => -9000);
}
#[test]
fn if_else_chain() {
assert_program!("var x = 3";
"if x is 0";
" x = -1";
"else if x is 1";
" x = -2";
"else if x is 2";
" x = -3";
"else if x is 3";
" x = -4";
"else if x is 4";
" x = -5";
"else";
" x = -999";
"x" => -4);
}
}
#[cfg(test)]
mod single_scope {
use super::*;
#[test]
fn blank_program() {
assert_program!("" => 0);
assert_program!("\n" => 0);
assert_program!("\n\n\n\n\n" => 0);
}
#[test]
fn multi_line() {
assert_program!("";
"12";
"";
"123" => 123);
}
#[test]
fn variable_assignment() {
assert_program!("var one = 1";
"var two = 1 + 1";
"";
"one + two" => 3);
}
#[test]
fn variable_mutation() {
assert_program!("var one = 1";
"one = 3";
"one" => 3);
assert_program!("var one = 1";
"one = one + 50";
"one" => 51);
}
#[test]
fn variable_reassignment() {
assert_program!("var b = 1";
"var b = 2";
"b" => 2);
assert_program!("var a = 1";
"var a = a * 3";
"a" => 3);
}
#[test]
fn reassignment_no_assign_err() {
assert_program!("b = 1" =>X "`b`", "not found"; src = SourceRef((1,1), (1,6)));
}
#[test]
fn comments() {
assert_program!("var b = 1 # assign b to 1";
"var b = 2 # same effect as `b = 2`";
"b" => 2);
assert_program!("## start with some notes";
"123 # just return a number";
"";
"";
"#### some closing comment..." => 123);
}
#[test]
fn empty_declaration() {
assert_program!("var b";
"b" => 0);
}
#[test]
fn operator_assignment() {
assert_program!("var v = 12";
"v += 3"; "v -= 5"; "v *= 2"; "v /= 4";
"v" => 5)
}
}
#[cfg(test)]
mod booleans {
use super::*;
#[test]
fn is() {
assert_program!("1 is 2" => 0);
assert_program!("2 is 1 + 1" => 1);
}
#[test]
fn is_not() {
assert_program!("3 is not 3" => 0);
assert_program!("3 is not 4" => 1);
}
#[test]
fn not() {
assert_program!("not 1" => 0);
assert_program!("not 0" => 1);
assert_program!("not 5 is 4" => 1);
assert_program!("not 5 or 4 and 0" => 0);
}
#[test]
fn multi_compare_err() {
assert_program!("2 is 2 is 1" =>X "is");
assert_program!("2 > 2 < 1" =>X "<");
assert_program!("2 > 2 is 0" =>X "is");
}
#[test]
fn and() {
assert_program!("1 + 2 and 2 + 3 and 3 + 4" => 7);
assert_program!("not 1 - 1 and not 2 + 3" => 0);
assert_program!("1 + 2 and 7 is 2 and 3 + 4" => 0);
}
#[test]
fn or() {
assert_program!("1 + 2 or 2 + 3 or 3 + 4" => 3);
assert_program!("0 or 1 and 0 or 0" => 0);
assert_program!("0 or 0 or 123" => 123);
assert_program!("0 or 234 or 0" => 234);
}
#[test]
fn greater_than() {
assert_program!("1 > 1" => 0);
assert_program!("1 > 2" => 0);
assert_program!("1 > 0" => 1);
assert_program!("1 > -2" => 1);
}
#[test]
fn less_than() {
assert_program!("1 < 1" => 0);
assert_program!("1 < 2" => 1);
assert_program!("1 < 0" => 0);
assert_program!("1 < -2" => 0);
}
#[test]
fn greater_than_or_equal_to() {
assert_program!("1 >= 1" => 1);
assert_program!("1 >= 2" => 0);
assert_program!("1 >= 0" => 1);
assert_program!("1 >= -2" => 1);
}
#[test]
fn less_than_or_equal_to() {
assert_program!("1 <= 1" => 1);
assert_program!("1 <= 2" => 1);
assert_program!("1 <= 0" => 0);
assert_program!("1 <= -2" => 0);
}
}
#[cfg(test)]
mod numerics {
use super::*;
#[test]
fn num() {
assert_program!("0" => 0);
assert_program!("123" => 123);
}
#[test]
fn negative() {
assert_program!("-235" => -235);
}
#[test]
fn double_negative_err() {
assert_program!("--235" =>X "-");
}
#[test]
fn negative_brace() {
assert_program!("-(-45)" => 45);
}
#[test]
fn plus() {
assert_program!("12 + 2" => 14);
assert_program!("-12 + 2 + 23" => 13);
}
#[test]
fn subtract() {
assert_program!("12 - 2" => 10);
assert_program!("-12 - 2 - 4" => -18);
}
#[test]
fn plus_sub() {
assert_program!("12 - 2 + 3" => 13);
assert_program!("-12 + 2 - 4" => -14);
}
#[test]
fn multiply() {
assert_program!("3 * 4" => 12);
assert_program!("4 * -5 * 3" => -60);
}
#[test]
fn divide() {
assert_program!("5 / 2" => 2); assert_program!("-12 / 2 / 2" => -3);
}
#[test]
fn precedence() {
assert_program!("3 + 6 / 2 * 7 + 1" => 25);
}
#[test]
fn negative_mixed_in() {
assert_program!("3 + 6 / 2 * -7 + 1" => -17);
}
#[test]
fn outer_brackets() {
assert_program!("(7 + 3 * 4)" => 19);
}
#[test]
fn brackets() {
assert_program!("7 + 3 * (10 / (12 / (3 + 1) - 1))" => 22);
}
#[test]
fn redundant_brackets() {
assert_program!("7 + (((3 + 2)))" => 12);
}
#[test]
fn div_by_0_err() {
assert_program!("1/0" =>X "divide by zero");
}
#[test]
fn modulus() {
assert_program!("52 % 2" => 0);
assert_program!("53 % 2" => 1);
assert_program!("53 % 50" => 3);
assert_program!("14023 % 50" => 23);
}
}