diff --git a/ablescript/src/ast.rs b/ablescript/src/ast.rs index 90432830..0d68f119 100644 --- a/ablescript/src/ast.rs +++ b/ablescript/src/ast.rs @@ -142,7 +142,7 @@ pub enum ExprKind { Literal(Value), Cart(Vec<(Expr, Expr)>), Index { - cart: Box, + expr: Box, index: Box, }, Variable(String), diff --git a/ablescript/src/interpret.rs b/ablescript/src/interpret.rs index 1f0f0f65..73e9ed8b 100644 --- a/ablescript/src/interpret.rs +++ b/ablescript/src/interpret.rs @@ -141,59 +141,19 @@ impl ExecEnv { let lhs = self.eval_expr(lhs)?; let rhs = self.eval_expr(rhs)?; match kind { - // Arithmetic operators. - Add | Subtract | Multiply | Divide => { - let lhs = lhs.to_i32(); - let rhs = rhs.to_i32(); - - let res = match kind { - Add => lhs.checked_add(rhs), - Subtract => lhs.checked_sub(rhs), - Multiply => lhs.checked_mul(rhs), - Divide => lhs.checked_div(rhs), - _ => unreachable!(), - } - .unwrap_or(consts::ANSWER); - Int(res) - } - - // Numeric comparisons. - Less | Greater => { - let lhs = lhs.to_i32(); - let rhs = rhs.to_i32(); - - let res = match kind { - Less => lhs < rhs, - Greater => lhs > rhs, - _ => unreachable!(), - }; - Bool(res) - } - - // General comparisons. - Equal | NotEqual => { - let res = match kind { - Equal => lhs == rhs, - NotEqual => lhs != rhs, - _ => unreachable!(), - }; - Bool(res) - } - - // Logical connectives. - And | Or => { - let lhs = lhs.to_bool(); - let rhs = rhs.to_bool(); - let res = match kind { - And => lhs && rhs, - Or => lhs || rhs, - _ => unreachable!(), - }; - Bool(res) - } + Add => lhs + rhs, + Subtract => todo!(), + Multiply => todo!(), + Divide => todo!(), + Greater => Value::Bool(lhs > rhs), + Less => Value::Bool(lhs < rhs), + Equal => Value::Bool(lhs == rhs), + NotEqual => Value::Bool(lhs != rhs), + And => todo!(), + Or => todo!(), } } - Not(expr) => Bool(!self.eval_expr(expr)?.to_bool()), + Not(expr) => Bool(!self.eval_expr(expr)?.into_bool()), Literal(value) => value.clone(), ExprKind::Cart(members) => Value::Cart( members @@ -206,12 +166,15 @@ impl ExecEnv { }) .collect::, _>>()?, ), - Index { cart, index } => { - let cart = self.eval_expr(cart)?; + Index { expr, index } => { + let value = self.eval_expr(expr)?; let index = self.eval_expr(index)?; - // TODO: this probably shouldn't be cloned - cart.index(&index).borrow().clone() + value + .into_cart() + .get(&index) + .map(|x| x.borrow().clone()) + .unwrap_or(Value::Nul) } // TODO: not too happy with constructing an artificial @@ -257,24 +220,20 @@ impl ExecEnv { instructions: code.to_owned(), tape_len: tape_len .as_ref() - .map(|tape_len| self.eval_expr(tape_len).map(|v| v.to_i32() as usize)) + .map(|tape_len| self.eval_expr(tape_len).map(|v| v.into_i32() as usize)) .unwrap_or(Ok(crate::brian::DEFAULT_TAPE_SIZE_LIMIT))?, }), ); } StmtKind::If { cond, body } => { - if self.eval_expr(cond)?.to_bool() { + if self.eval_expr(cond)?.into_bool() { return self.eval_stmts_hs(&body.block, true); } } StmtKind::Call { expr, args } => { - let func = self.eval_expr(expr)?; + let func = self.eval_expr(expr)?.into_functio(); - if let Value::Functio(func) = func { - self.fn_call(func, args, &stmt.span)?; - } else { - // Fail silently for now. - } + self.fn_call(func, args, &stmt.span)?; } StmtKind::Loop { body } => loop { let res = self.eval_stmts_hs(&body.block, true)?; @@ -387,6 +346,17 @@ impl ExecEnv { self.stack.pop(); res?; } + Functio::Eval(code) => { + if args.len() != 0 { + return Err(Error { + kind: ErrorKind::MismatchedArgumentError, + span: span.to_owned(), + }); + } + + let stmts = crate::parser::Parser::new(&code).init()?; + self.eval_stmts(&stmts)?; + } } Ok(()) } diff --git a/ablescript/src/parser.rs b/ablescript/src/parser.rs index f42873e5..16c6733d 100644 --- a/ablescript/src/parser.rs +++ b/ablescript/src/parser.rs @@ -98,6 +98,7 @@ impl<'source> Parser<'source> { | Token::Integer(_) | Token::Abool(_) | Token::Bool(_) + | Token::Nul | Token::LeftBracket | Token::LeftParen => Ok(Stmt::new( self.value_flow(token)?, @@ -192,7 +193,7 @@ impl<'source> Parser<'source> { Token::LeftBracket => match buf.take() { Some(buf) => Ok(Expr::new( ExprKind::Index { - cart: Box::new(buf), + expr: Box::new(buf), index: Box::new(self.expr_flow(Token::RightBracket)?), }, start..self.lexer.span().end, @@ -728,7 +729,7 @@ mod tests { let expected = &[Stmt { kind: StmtKind::Print(Expr { kind: ExprKind::Index { - cart: Box::new(Expr { + expr: Box::new(Expr { kind: ExprKind::Cart(vec![( Expr { kind: ExprKind::Literal(Value::Str("able".to_string())), diff --git a/ablescript/src/variables.rs b/ablescript/src/variables.rs index f088b165..57a9c7ca 100644 --- a/ablescript/src/variables.rs +++ b/ablescript/src/variables.rs @@ -1,6 +1,6 @@ use std::{ cell::RefCell, collections::HashMap, convert::TryFrom, fmt::Display, hash::Hash, io::Write, - mem::discriminant, rc::Rc, + mem::discriminant, ops, rc::Rc, vec, }; use rand::Rng; @@ -44,8 +44,11 @@ pub enum Functio { params: Vec, body: Vec, }, + Eval(String), } +pub type Cart = HashMap>>; + #[derive(Debug, Clone)] pub enum Value { Nul, @@ -54,7 +57,7 @@ pub enum Value { Bool(bool), Abool(Abool), Functio(Functio), - Cart(HashMap>>), + Cart(Cart), } impl Hash for Value { @@ -72,24 +75,6 @@ impl Hash for Value { } } -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Value::Nul, Value::Nul) => true, - (Value::Str(left), Value::Str(right)) => left == right, - (Value::Int(left), Value::Int(right)) => left == right, - (Value::Bool(left), Value::Bool(right)) => left == right, - (Value::Abool(left), Value::Abool(right)) => left == right, - (Value::Functio(left), Value::Functio(right)) => left == right, - (Value::Cart(_), Value::Cart(_)) => self.to_string() == other.to_string(), - (_, _) => false, - // TODO: do more coercions! - } - } -} - -impl Eq for Value {} - impl Value { /// Write an AbleScript value to a Brainfuck input stream by /// coercing the value to an integer, then truncating that integer @@ -98,23 +83,24 @@ impl Value { /// any IO errors will cause a panic. pub fn bf_write(&self, stream: &mut impl Write) { stream - .write_all(&[self.clone().to_i32() as u8]) + .write_all(&[self.clone().into_i32() as u8]) .expect("Failed to write to Brainfuck input"); } /// Coerce a value to an integer. - pub fn to_i32(&self) -> i32 { + pub fn into_i32(self) -> i32 { match self { - Value::Abool(a) => *a as _, - Value::Bool(b) => *b as _, + Value::Abool(a) => a as _, + Value::Bool(b) => b as _, Value::Functio(func) => match func { Functio::BfFunctio { instructions, tape_len, } => (instructions.len() + tape_len) as _, Functio::AbleFunctio { params, body } => (params.len() + body.len()) as _, + Functio::Eval(s) => s.parse().unwrap_or(consts::ANSWER), }, - Value::Int(i) => *i, + Value::Int(i) => i, Value::Nul => consts::ANSWER, Value::Str(text) => text.parse().unwrap_or(consts::ANSWER), Value::Cart(c) => c.len() as _, @@ -122,49 +108,262 @@ impl Value { } /// Coerce a Value to a boolean. The conversion cannot fail. - pub fn to_bool(&self) -> bool { + pub fn into_bool(self) -> bool { match self { - Value::Abool(b) => (*b).into(), - Value::Bool(b) => *b, + Value::Abool(b) => b.into(), + Value::Bool(b) => b, Value::Functio(_) => true, - Value::Int(x) => *x != 0, - Value::Nul => true, - Value::Str(s) => !s.is_empty(), + Value::Int(x) => x != 0, + Value::Nul => false, + Value::Str(s) => match s.to_lowercase().as_str() { + "false" | "no" | "🇳🇴" => false, + "true" | "yes" => true, + s => !s.is_empty(), + }, Value::Cart(c) => !c.is_empty(), } } - /// Index a value with another value, as in the "a[b]" syntax. - pub fn index(&self, index: &Value) -> Rc> { - Rc::new(RefCell::new(match self { - Value::Nul => Value::Nul, - Value::Str(s) => Value::Int( - usize::try_from(index.to_i32() - 1) - .ok() - .and_then(|idx| s.as_bytes().get(idx).cloned()) - .map(|value| value as i32) - .unwrap_or(0), - ), - Value::Int(i) => Value::Int( - usize::try_from(index.to_i32() - 1) - .ok() - .and_then(|idx| format!("{}", i).as_bytes().get(idx).cloned()) - .map(|value| value as i32) - .unwrap_or(0), - ), - Value::Bool(b) => Value::Int( - usize::try_from(index.to_i32() - 1) - .ok() - .and_then(|idx| format!("{}", b).as_bytes().get(idx).cloned()) - .map(|value| value as i32) - .unwrap_or(0), - ), - Value::Abool(b) => Value::Int(*b as i32), - Value::Functio(_) => Value::Int(42), - Value::Cart(c) => { - return (c.get(index).cloned()).unwrap_or_else(|| Rc::new(RefCell::new(Value::Nul))) + /// Coerce a Value to an aboolean + pub fn into_abool(self) -> Abool { + match self { + Value::Nul => Abool::Never, + Value::Str(s) => match s.to_lowercase().as_str() { + "never" => Abool::Never, + "sometimes" => Abool::Sometimes, + "always" => Abool::Always, + s => { + if s.is_empty() { + Abool::Never + } else { + Abool::Always + } + } + }, + Value::Int(x) => match x.cmp(&0) { + std::cmp::Ordering::Less => Abool::Never, + std::cmp::Ordering::Equal => Abool::Sometimes, + std::cmp::Ordering::Greater => Abool::Always, + }, + Value::Bool(b) => { + if b { + Abool::Always + } else { + Abool::Never + } } - })) + Value::Abool(a) => a, + Value::Functio(_) => todo!(), + Value::Cart(c) => { + if c.is_empty() { + Abool::Never + } else { + Abool::Always + } + } + } + } + + /// Coerce a Value to a functio + pub fn into_functio(self) -> Functio { + match self { + Value::Nul => Functio::AbleFunctio { + body: vec![], + params: vec![], + }, + Value::Str(s) => Functio::Eval(s), + Value::Int(i) => todo!(), + Value::Bool(_) => todo!(), + Value::Abool(_) => todo!(), + Value::Functio(f) => f, + Value::Cart(_) => todo!(), + } + } + + pub fn into_cart(self) -> Cart { + match self { + Value::Nul => HashMap::new(), + Value::Str(s) => s + .chars() + .enumerate() + .map(|(i, x)| { + ( + Value::Int(i as i32 + 1), + Rc::new(RefCell::new(Value::Str(x.to_string()))), + ) + }) + .collect(), + Value::Int(i) => Value::Str(i.to_string()).into_cart(), + Value::Bool(b) => Value::Str(b.to_string()).into_cart(), + Value::Abool(a) => Value::Str(a.to_string()).into_cart(), + Value::Functio(f) => match f { + Functio::BfFunctio { + instructions, + tape_len, + } => { + let mut cart: Cart = instructions + .into_iter() + .enumerate() + .map(|(i, x)| { + ( + Value::Int(i as i32 + 1), + Rc::new(RefCell::new( + char::from_u32(x as u32) + .map(|x| Value::Str(x.to_string())) + .unwrap_or(Value::Nul), + )), + ) + }) + .collect(); + + cart.insert( + Value::Str("tapelen".to_owned()), + Rc::new(RefCell::new(Value::Int(tape_len as _))), + ); + cart + } + Functio::AbleFunctio { params, body } => { + let params: Cart = params + .into_iter() + .enumerate() + .map(|(i, x)| { + ( + Value::Int(i as i32 + 1), + Rc::new(RefCell::new(Value::Str(x))), + ) + }) + .collect(); + + let body: Cart = body + .into_iter() + .enumerate() + .map(|(i, x)| { + ( + Value::Int(i as i32 + 1), + Rc::new(RefCell::new(Value::Str(format!("{:?}", x)))), + ) + }) + .collect(); + + let mut cart = HashMap::new(); + cart.insert( + Value::Str("params".to_owned()), + Rc::new(RefCell::new(Value::Cart(params))), + ); + + cart.insert( + Value::Str("body".to_owned()), + Rc::new(RefCell::new(Value::Cart(body))), + ); + + cart + } + Functio::Eval(s) => Value::Str(s).into_cart(), + }, + Value::Cart(c) => c, + } + } +} + +impl ops::Add for Value { + type Output = Value; + + fn add(self, rhs: Self) -> Self::Output { + match self { + Value::Nul => todo!(), + Value::Str(s) => Value::Str(format!("{}{}", s, rhs.to_string())), + Value::Int(i) => Value::Int(i + rhs.into_i32()), + Value::Bool(b) => Value::Bool(b ^ rhs.into_bool()), + Value::Abool(a) => Value::Abool({ + let rhs = rhs.into_abool(); + if a == rhs { + a + } else if a == Abool::Sometimes { + if rand::thread_rng().gen() { + Abool::Sometimes + } else { + rhs + } + } else if rhs == Abool::Sometimes { + if rand::thread_rng().gen() { + Abool::Sometimes + } else { + a + } + } else { + Abool::Sometimes + } + }), + Value::Functio(f) => Value::Functio(todo!()), + Value::Cart(c) => { + Value::Cart(c.into_iter().chain(rhs.into_cart().into_iter()).collect()) + } + } + } +} + +impl ops::Sub for Value { + type Output = Value; + + fn sub(self, rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Mul for Value { + type Output = Value; + + fn mul(self, rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Div for Value { + type Output = Value; + + fn div(self, rhs: Self) -> Self::Output { + todo!() + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + let other = other.clone(); + + match self { + Value::Nul => other == Value::Nul, + Value::Str(s) => *s == other.to_string(), + Value::Int(i) => *i == other.into_i32(), + Value::Bool(b) => *b == other.into_bool(), + Value::Abool(a) => *a == other.into_abool(), + Value::Functio(f) => *f == other.into_functio(), + Value::Cart(c) => *c == other.into_cart(), + } + } +} + +impl Eq for Value {} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + use std::cmp::Ordering::*; + let other = other.clone(); + + match self { + Value::Nul => { + if other == Value::Nul { + Some(Equal) + } else { + None + } + } + Value::Str(_) => todo!(), + Value::Int(_) => todo!(), + Value::Bool(_) => todo!(), + Value::Abool(_) => todo!(), + Value::Functio(_) => todo!(), + Value::Cart(_) => todo!(), + } } } @@ -199,6 +398,7 @@ impl Display for Value { body, ) } + Functio::Eval(s) => write!(f, "{}", s), }, Value::Cart(c) => { write!(f, "[")?;