diff --git a/examples/bf.cxpr b/examples/bf.cxpr new file mode 100644 index 0000000..36e058b --- /dev/null +++ b/examples/bf.cxpr @@ -0,0 +1,68 @@ +while true { + print("bf> "); + let program = input(); + let tape = [0]*256; + let ptr = 0; + let i = 0; + while i < len(program) { + let op = program[i]; + if op == '+' { + tape[ptr] += 1; + if tape[ptr] >= 256 { + tape[ptr] -= 256; + } + } elif op == '-' { + tape[ptr] -= 1; + if tape[ptr] < 0 { + tape[ptr] += 256; + } + } elif op == '>' { + ptr += 1; + } elif op == '<' { + ptr -= 1; + } elif op == '.' { + print(chr(tape[ptr])); + } elif op == ',' { + tape[ptr] = ord(input()[0]); + } elif op == '[' { + if tape[ptr] == 0 { + let depth = 0; + let running = true; + while running { + i += 1; + if program[i] == ']' { + if depth == 0 { + running = false; + } + depth -= 1; + } elif program[i] == '[' { + depth += 1; + } + } + } + } elif op == ']' { + if tape[ptr] != 0 { + let depth = 0; + let running = true; + while running { + i -= 1; + if program[i] == '[' { + if depth == 0 { + running = false; + } + depth -= 1; + } elif program[i] == ']' { + depth += 1; + } + } + } + } + + if ptr >= len(tape) { + tape += [0]*256; + } + + i += 1; + } + println(""); +} \ No newline at end of file diff --git a/src/eval.rs b/src/eval.rs index d733d7e..d021efd 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -123,22 +123,28 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result { => eval_comp(lhs, rhs, op, env), o => todo!("{:?}", o) // TODO other operations }, + Expr::Unary { arg, op } => eval_unary(arg, op, env), Expr::List { items } => { let mut list = Vec::with_capacity(items.len()); for item in items { list.push(eval_expr(item, env.clone())?); } - Ok(Value::List(Rc::new(list))) + Ok(Value::from(list)) }, Expr::FuncCall { func, args, pos } => { - let func = eval_expr(&func, env.clone())?; + let func = eval_expr(func, env.clone())?; let mut arg_values = Vec::with_capacity(args.len()); for arg in args { let result = eval_expr(arg, env.clone())?; arg_values.push(result); } func.call(arg_values, pos) - } + }, + Expr::Index { lhs, index, pos } => { + let l = eval_expr(lhs, env.clone())?; + let idx = eval_expr(index, env)?; + l.index(&idx).map_err(|e| RuntimeError { message: e, pos: pos.clone() }) + }, e => todo!("{:?}", e) // TODO other expression types } } @@ -197,6 +203,27 @@ pub fn eval_assignment(lhs: &Box, rhs: &Box, op: &Token, env: EnvRef .set(name.clone(), result.clone()).expect("unreachable"); Ok(result) } + } else if let Expr::Index { lhs, index, pos } = &**lhs { + let l = eval_expr(lhs, env.clone())?; + let idx = eval_expr(index, env.clone())?; + if op.ty == TokenType::Equal { + let r = eval_expr(rhs, env)?; + l.assign_index(&idx, r.clone()).map_err(|e| RuntimeError { message: e, pos: pos.clone() })?; + Ok(r) + } else { + let prev_value = l.index(&idx).map_err(|e| RuntimeError { message: e, pos: pos.clone() })?; + let r = eval_expr(rhs, env)?; + let result = match op.ty { + TokenType::PlusEqual => &prev_value + &r, + TokenType::MinusEqual => &prev_value - &r, + TokenType::StarEqual => &prev_value * &r, + TokenType::SlashEqual => &prev_value / &r, + TokenType::PercentEqual => &prev_value % &r, + _ => todo!() // TODO more operations + }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() })?; + l.assign_index(&idx, result.clone()).map_err(|e| RuntimeError { message: e, pos: pos.clone() })?; + Ok(result) + } } else { unreachable!() } @@ -242,4 +269,12 @@ pub fn eval_comp(lhs: &Box, rhs: &Box, op: &Token, env: EnvRef) -> R .map(|o| Value::from(o as i8)), _ => unreachable!() } +} + +pub fn eval_unary(arg: &Box, op: &Token, env: EnvRef) -> Result { + let a = eval_expr(arg, env)?; + match op.ty { + TokenType::Minus => -a, + _ => todo!(), + }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() }) } \ No newline at end of file diff --git a/src/expr.rs b/src/expr.rs index 632d41c..8a973d4 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -27,7 +27,8 @@ pub enum Expr { Ident { value: Token }, Literal { value: Token }, List { items: Vec }, - FuncCall { func: Box, args: Vec, pos: Position } + FuncCall { func: Box, args: Vec, pos: Position }, + Index { lhs: Box, index: Box, pos: Position }, } impl fmt::Debug for Expr { @@ -39,13 +40,14 @@ impl fmt::Debug for Expr { Self::Literal { value } => write!(f, "(lit {:?})", value), Self::List { items } => write!(f, "(list {:?})", items), Self::FuncCall { func, args, .. } => write!(f, "(call {:?} {:?})", func, args), + Self::Index { lhs, index, .. } => write!(f, "(index {:?} {:?})", lhs, index), } } } impl Expr { pub fn is_lvalue(&self) -> bool { - matches!(self, Expr::Ident{..}) + return matches!(self, Expr::Ident{..} | Expr::Index{..}) } pub fn is_assignment(&self) -> bool { diff --git a/src/parser.rs b/src/parser.rs index 41fec52..7a00483 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -289,14 +289,31 @@ impl Parser { } fn fncall(&mut self) -> Result { - let expr = self.expr_base()?; - if !self.at_end() && self.peek().ty == TokenType::LParen { - let lparen = self.next(); - let args = self.commalist(TokenType::RParen, Self::assignment)?; - Ok(Expr::FuncCall { func: Box::new(expr), args, pos: lparen.pos.clone() }) - } else { - Ok(expr) + let mut expr = self.expr_base()?; + while !self.at_end() { + match self.peek().ty { + TokenType::LParen => expr = self.fncall_inner(expr)?, + TokenType::LBrack => expr = self.arrindex_inner(expr)?, + _ => return Ok(expr) + } } + Ok(expr) + } + + fn fncall_inner(&mut self, expr: Expr) -> Result { + let lparen = self.next(); + let args = self.commalist(TokenType::RParen, Self::assignment)?; + Ok(Expr::FuncCall { func: Box::new(expr), args, pos: lparen.pos.clone() }) + } + + fn arrindex_inner(&mut self, expr: Expr) -> Result { + let lbrack = self.next(); + let index = self.assignment()?; + self.err_on_eof()?; + if self.next().ty != TokenType::RBrack { + return Err(ParserError { message: "Expected RBrack after collection index".into(), pos: lbrack.pos.clone() }); + } + Ok(Expr::Index { lhs: Box::new(expr), index: Box::new(index), pos: lbrack.pos.clone() }) } fn expr_base(&mut self) -> Result { diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index 6c4b3e8..882d904 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -14,6 +14,14 @@ pub fn load(env: &mut Environment) { env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_println, arg_count: 1, name })); name = Rc::from("input"); env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_input, arg_count: 0, name })); + name = Rc::from("ord"); + env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_ord, arg_count: 1, name })); + name = Rc::from("chr"); + env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_chr, arg_count: 1, name })); + name = Rc::from("range"); + env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_range, arg_count: 2, name })); + name = Rc::from("len"); + env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_len, arg_count: 1, name })); } fn fn_str(args: Vec) -> Result { @@ -39,5 +47,55 @@ fn fn_input(_: Vec) -> Result { let mut buffer = String::new(); let stdin = std::io::stdin(); stdin.read_line(&mut buffer).map_err(|e| e.to_string())?; + if buffer.ends_with("\n") { + buffer.pop(); + } Ok(Value::from(buffer)) +} + +fn fn_ord(args: Vec) -> Result { + if let Value::Char(c) = args[0] { + Ok(Value::from(c as u32)) + } else { + Err("Argument to ord must be a char".into()) + } +} + +fn fn_chr(args: Vec) -> Result { + if let Value::Int(i) = args[0] { + if i >= 0 && i < (u32::MAX as i64) { + if let Some(c) = char::from_u32(i as u32) { + return Ok(Value::from(c)) + } + } + Err("Out of range".into()) + } else { + Err("Argument to chr must be an integer".into()) + } +} + +fn fn_range(args: Vec) -> Result { + let min = &args[0]; + let max = &args[1]; + match (min, max) { + (Value::Int(a), Value::Int(b)) => { + if a == b { + Ok(Value::from(vec![])) + } else if a < b { + Ok(Value::from((*a..*b).map(|x| Value::Int(x)).collect::>())) + } else { + Ok(Value::from(((*b+1)..(*a+1)).rev().map(|x| Value::Int(x)).collect::>())) + } + }, + _ => Err("Both arguments to range must be integers".into()) + } +} + +fn fn_len(args: Vec) -> Result { + match &args[0] { + Value::String(s) => Ok(Value::Int(s.len() as i64)), + Value::List(l) => Ok(Value::Int(l.borrow().len() as i64)), + Value::Map(m) => Ok(Value::Int(m.len() as i64)), + v => Err(format!("{:?} has no length", v)) + } } \ No newline at end of file diff --git a/src/value.rs b/src/value.rs index f68d48c..9a4a314 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, collections::HashMap, ops::*, fmt, cmp::Ordering}; +use std::{rc::Rc, collections::HashMap, ops::*, fmt, cmp::Ordering, cell::RefCell}; use num_traits::{Zero, ToPrimitive}; @@ -41,7 +41,7 @@ pub enum Value { Bool(bool), Char(char), String(Rc), - List(Rc>), Map(Rc>), + List(Rc>>), Map(Rc>), BuiltinFn(BuiltinFn), Data(Data), } @@ -55,7 +55,7 @@ impl Value { Complex(z) => !z.is_zero(), Rational(r) => !r.is_zero(), String(s) => !s.len() == 0, - List(l) => !l.len() == 0, + List(l) => !l.borrow().len() == 0, Map(m) => !m.len() == 0, _ => true } @@ -66,7 +66,7 @@ impl Value { Value::String(s) => Ok(Box::new(s.chars() .map(|c| Value::Char(c)))), - Value::List(l) => Ok(Box::new(l.iter().cloned())), + Value::List(l) => Ok(Box::new(l.borrow().clone().into_iter())), _ => Err(()) } } @@ -120,13 +120,51 @@ impl Value { Self::Complex(z) => Rc::from(z.to_string()), Self::Char(c) => Rc::from(format!("'{}'", c)), // TODO escaping Self::String(s) => Rc::from(format!("\"{}\"", s)), // TODO escaping - Self::List(l) => Rc::from(format!("{:?}", l)), // TODO fix + Self::List(l) => Rc::from(format!("{:?}", l.borrow())), // TODO fix Self::Map(m) => Rc::from(format!("{:?}", m)), // TODO fix Self::Type(_) => todo!(), Self::BuiltinFn(bf) => Rc::from(format!("", bf.name, bf.func as *const ())), Self::Data(_) => todo!(), } } + + pub fn index(&self, idx: &Value) -> Result { + match self { + Self::String(s) => match idx { + Value::Int(i) if *i >= 0 => s.chars().nth(*i as usize) + .ok_or_else(|| format!("String index {} out of bounds for length {}", i, s.chars().count())) + .map(|c| Value::Char(c)), + Value::Int(i) => Err(format!("String index {} cannot be negative", i)), + _ => Err(format!("Cannot index {:?} with {:?}", self, idx)) + }, + Self::List(l) => match idx { + Value::Int(i) if *i >= 0 => l.borrow().get(*i as usize) + .ok_or_else(|| format!("List index {} out of bounds for length {}", i, l.borrow().len())) + .map(|v| v.clone()), + Value::Int(i) => Err(format!("List index {} cannot be negative", i)), + _ => Err(format!("Cannot index {:?} with {:?}", self, idx)) + } + Self::Map(_) => todo!(), + v => Err(format!("Cannot index into {:?}", v)) + } + } + + pub fn assign_index(&self, idx: &Value, value: Value) -> Result<(), String> { + match self { + Self::String(s) => todo!("Can't mutate strings yet"), + Self::List(l) => match idx { + Value::Int(i) if *i >= 0 && (*i as usize) < l.borrow().len() => { + l.borrow_mut()[*i as usize] = value; + Ok(()) + } + Value::Int(i) if *i >= 0 => Err(format!("List index {} out of bounds for length {}", i, l.borrow().len())), + Value::Int(i) => Err(format!("List index {} cannot be negative", i)), + _ => Err(format!("Cannot index {:?} with {:?}", self, idx)) + } + Self::Map(_) => todo!(), + v => Err(format!("Cannot index into {:?}", v)) + } + } } impl PartialEq for Value { @@ -202,6 +240,23 @@ macro_rules! value_from { }; } +impl From> for Value { + fn from(x: Vec) -> Self { + Self::List(RefCell::new(x).into()) + } +} + +value_from!(Int, u8 u16 u32 i8 i16 i32 i64); +value_from!(Float, f32 f64); +value_from!(Complex, Complex); +value_from!(Rational, Rational); +value_from!(Bool, bool); +value_from!(String, String Rc); +value_from!(List, RefCell>); +value_from!(Char, char); +value_from!(Map, HashMap); + + macro_rules! impl_numeric_op { ($optrait:ty, $fnname:ident, { $($bonus:tt)* }) => { impl $optrait for &Value { @@ -235,15 +290,15 @@ macro_rules! impl_numeric_op { } } -value_from!(Int, u8 u16 u32 i8 i16 i32 i64); -value_from!(Float, f32 f64); -value_from!(Complex, Complex); -value_from!(Rational, Rational); -value_from!(Bool, bool); -value_from!(String, String Rc); -value_from!(Char, char); -value_from!(List, Vec); -value_from!(Map, HashMap); +impl Neg for Value { + type Output = Result; + fn neg(self) -> Self::Output { + match self { + Value::Int(a) => Ok(Value::Int(-a)), + _ => Err(format!("Unsupported operation 'neg' on {:?}", self)) + } + } +} impl_numeric_op!(Add, add, { (String(a), String(b)) => Ok(((**a).to_owned() + b).into()), @@ -260,11 +315,16 @@ impl_numeric_op!(Add, add, { } (List(a), List(b)) => { let mut a = (**a).clone(); - a.append(&mut (**b).clone()); + a.borrow_mut().append(&mut (**b).borrow().clone()); Ok(a.into()) }, }); impl_numeric_op!(Sub, sub, {}); -impl_numeric_op!(Mul, mul, {}); +impl_numeric_op!(Mul, mul, { + (String(a), Int(b)) | (Int(b), String(a)) + => Ok(Value::from(a.chars().cycle().take(a.chars().count()*(*b as usize)).collect::())), + (List(a), Int(b)) | (Int(b), List(a)) + => Ok(Value::from(a.borrow().iter().cycle().take(a.borrow().len()*(*b as usize)).cloned().collect::>())), +}); impl_numeric_op!(Div, div, {}); impl_numeric_op!(Rem, rem, {}); \ No newline at end of file