Begin implementing better built-in features

This commit is contained in:
Jeff 2024-08-07 18:24:25 -04:00
parent 06f3a9b746
commit 4d7f59aee2
8 changed files with 482 additions and 119 deletions

View File

@ -5,7 +5,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{Identifier, ReservedIdentifier, Type, Value};
use crate::{Identifier, Type, Value};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct AbstractSyntaxTree<P> {
@ -40,6 +40,16 @@ pub enum Statement<P> {
// Expressions
Add(Box<Node<P>>, Box<Node<P>>),
BuiltInFunctionCall {
function: BuiltInFunction,
type_arguments: Option<Vec<Node<P>>>,
value_arguments: Option<Vec<Node<P>>>,
},
FunctionCall {
function: Box<Node<P>>,
type_arguments: Option<Vec<Node<P>>>,
value_arguments: Option<Vec<Node<P>>>,
},
PropertyAccess(Box<Node<P>>, Box<Node<P>>),
List(Vec<Node<P>>),
Multiply(Box<Node<P>>, Box<Node<P>>),
@ -47,7 +57,6 @@ pub enum Statement<P> {
// Hard-coded values
Constant(Value),
Identifier(Identifier),
ReservedIdentifier(ReservedIdentifier),
}
impl<P> Statement<P> {
@ -55,17 +64,15 @@ impl<P> Statement<P> {
match self {
Statement::Add(left, _) => left.statement.expected_type(variables),
Statement::Assign(_, _) => None,
Statement::BuiltInFunctionCall { function, .. } => function.expected_type(),
Statement::Constant(value) => Some(value.r#type(variables)),
Statement::FunctionCall { function, .. } => function.statement.expected_type(variables),
Statement::Identifier(identifier) => variables
.get(identifier)
.map(|value| value.r#type(variables)),
Statement::List(_) => None,
Statement::Multiply(left, _) => left.statement.expected_type(variables),
Statement::PropertyAccess(_, _) => None,
Statement::ReservedIdentifier(reserved) => match reserved {
ReservedIdentifier::IsEven | ReservedIdentifier::IsOdd => Some(Type::Boolean),
ReservedIdentifier::Length => Some(Type::Integer),
},
}
}
}
@ -75,6 +82,76 @@ impl<P> Display for Statement<P> {
match self {
Statement::Assign(left, right) => write!(f, "{left} = {right}"),
Statement::Add(left, right) => write!(f, "{left} + {right}"),
Statement::BuiltInFunctionCall {
function,
type_arguments: type_parameters,
value_arguments: value_parameters,
} => {
write!(f, "{function}")?;
if let Some(type_parameters) = type_parameters {
write!(f, "<")?;
for (i, type_parameter) in type_parameters.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{type_parameter}")?;
}
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_parameters) = value_parameters {
for (i, value_parameter) in value_parameters.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{value_parameter}")?;
}
}
write!(f, ")")
}
Statement::FunctionCall {
function,
type_arguments: type_parameters,
value_arguments: value_parameters,
} => {
write!(f, "{function}")?;
if let Some(type_parameters) = type_parameters {
write!(f, "<")?;
for (i, type_parameter) in type_parameters.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{type_parameter}")?;
}
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_parameters) = value_parameters {
for (i, value_parameter) in value_parameters.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{value_parameter}")?;
}
}
write!(f, ")")
}
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
Statement::List(nodes) => {
write!(f, "[")?;
@ -89,7 +166,69 @@ impl<P> Display for Statement<P> {
Statement::Multiply(left, right) => write!(f, "{left} * {right}"),
Statement::Constant(value) => write!(f, "{value}"),
Statement::Identifier(identifier) => write!(f, "{identifier}"),
Statement::ReservedIdentifier(identifier) => write!(f, "{identifier}"),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInFunction {
IsEven,
IsOdd,
Length,
}
impl BuiltInFunction {
pub fn name(&self) -> &'static str {
match self {
BuiltInFunction::IsEven => "is_even",
BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::Length => "length",
}
}
pub fn call(
&self,
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
) -> Result<Value, BuiltInFunctionError> {
match self {
BuiltInFunction::IsEven => {
if let Some(value_arguments) = value_arguments {
if value_arguments.len() == 1 {
if let Some(integer) = value_arguments[0].as_integer() {
Ok(Value::boolean(integer % 2 == 0))
} else {
Err(BuiltInFunctionError::ExpectedInteger)
}
} else {
Err(BuiltInFunctionError::WrongNumberOfValueArguments)
}
} else {
Err(BuiltInFunctionError::WrongNumberOfValueArguments)
}
}
BuiltInFunction::IsOdd => todo!(),
BuiltInFunction::Length => todo!(),
}
}
pub fn expected_type(&self) -> Option<Type> {
match self {
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::Length => Some(Type::Integer),
}
}
}
impl Display for BuiltInFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BuiltInFunctionError {
ExpectedInteger,
WrongNumberOfValueArguments,
}

View File

@ -103,7 +103,17 @@ impl<'a, P: Clone> Analyzer<'a, P> {
self.analyze_node(right)?;
}
Statement::BuiltInFunctionCall { .. } => {}
Statement::Constant(_) => {}
Statement::FunctionCall { function, .. } => {
if let Statement::Identifier(_) = &function.statement {
// Function is in the correct position
} else {
return Err(AnalyzerError::ExpectedIdentifier {
actual: function.as_ref().clone(),
});
}
}
Statement::Identifier(_) => {
return Err(AnalyzerError::UnexpectedIdentifier {
identifier: node.clone(),
@ -149,7 +159,6 @@ impl<'a, P: Clone> Analyzer<'a, P> {
self.analyze_node(right)?;
}
Statement::ReservedIdentifier(_) => {}
}
Ok(())
@ -158,6 +167,7 @@ impl<'a, P: Clone> Analyzer<'a, P> {
#[derive(Clone, Debug, PartialEq)]
pub enum AnalyzerError<P> {
ExpectedFunction { position: P },
ExpectedIdentifier { actual: Node<P> },
ExpectedIntegerOrFloat { actual: Node<P> },
UnexpectedIdentifier { identifier: Node<P> },

View File

@ -5,7 +5,7 @@
//! - [`Lexer`], which lexes the input a token at a time
use std::num::{ParseFloatError, ParseIntError};
use crate::{Identifier, ReservedIdentifier, Span, Token};
use crate::{Identifier, Span, Token};
/// Lexes the input and return a vector of tokens and their positions.
///
@ -245,9 +245,9 @@ impl<'a> Lexer<'a> {
let token = match string {
"true" => Token::Boolean(true),
"false" => Token::Boolean(false),
"is_even" => Token::ReservedIdentifier(ReservedIdentifier::IsEven),
"is_odd" => Token::ReservedIdentifier(ReservedIdentifier::IsOdd),
"length" => Token::ReservedIdentifier(ReservedIdentifier::Length),
"is_even" => Token::IsEven,
"is_odd" => Token::IsOdd,
"length" => Token::Length,
_ => Token::Identifier(Identifier::new(string)),
};
@ -298,19 +298,18 @@ mod tests {
}
#[test]
fn integer_property_access() {
let input = "42.is_even";
fn property_access_function_call() {
let input = "42.is_even()";
assert_eq!(
lex(input),
Ok(vec![
(Token::Integer(42), (0, 2)),
(Token::Dot, (2, 3)),
(
Token::ReservedIdentifier(ReservedIdentifier::IsEven),
(3, 10)
),
(Token::Eof, (10, 10)),
(Token::IsEven, (3, 10)),
(Token::LeftParenthesis, (10, 11)),
(Token::RightParenthesis, (11, 12)),
(Token::Eof, (12, 12)),
])
)
}
@ -328,13 +327,7 @@ mod tests {
assert_eq!(
lex(input),
Ok(vec![
(
Token::ReservedIdentifier(ReservedIdentifier::Length),
(0, 6)
),
(Token::Eof, (6, 6)),
])
Ok(vec![(Token::Length, (0, 6)), (Token::Eof, (6, 6)),])
)
}

View File

@ -15,13 +15,13 @@ pub mod r#type;
pub mod value;
pub mod vm;
pub use abstract_tree::{AbstractSyntaxTree, Node, Statement};
pub use abstract_tree::{AbstractSyntaxTree, BuiltInFunction, Node, Statement};
pub use analyzer::{analyze, Analyzer, AnalyzerError};
pub use identifier::Identifier;
pub use lex::{lex, LexError, Lexer};
pub use parse::{parse, ParseError, Parser};
pub use r#type::Type;
pub use token::{ReservedIdentifier, Token};
pub use token::Token;
pub use value::{Value, ValueError};
pub use vm::{run, Vm, VmError};

View File

@ -5,7 +5,10 @@
/// - `Parser` struct, which parses the input a statement at a time
use std::collections::VecDeque;
use crate::{AbstractSyntaxTree, LexError, Lexer, Node, Span, Statement, Token, Value};
use crate::{
abstract_tree::BuiltInFunction, AbstractSyntaxTree, LexError, Lexer, Node, Span, Statement,
Token, Value,
};
/// Parses the input into an abstract syntax tree.
///
@ -206,15 +209,12 @@ impl<'src> Parser<'src> {
(Token::LeftParenthesis, left_span) => {
self.next_token()?;
let instruction = self.parse_node(0)?;
let node = self.parse_node(0)?;
if let (Token::RightParenthesis, right_span) = self.current {
self.next_token()?;
Ok(Node::new(
instruction.statement,
(left_span.0, right_span.1),
))
Ok(Node::new(node.statement, (left_span.0, right_span.1)))
} else {
Err(ParseError::ExpectedClosingParenthesis {
actual: self.current.0.clone(),
@ -225,14 +225,14 @@ impl<'src> Parser<'src> {
(Token::LeftSquareBrace, left_span) => {
self.next_token()?;
let mut instructions = Vec::new();
let mut nodes = Vec::new();
loop {
if let (Token::RightSquareBrace, right_span) = self.current {
self.next_token()?;
return Ok(Node::new(
Statement::List(instructions),
Statement::List(nodes),
(left_span.0, right_span.1),
));
}
@ -244,7 +244,7 @@ impl<'src> Parser<'src> {
}
if let Ok(instruction) = self.parse_node(0) {
instructions.push(instruction);
nodes.push(instruction);
} else {
return Err(ParseError::ExpectedClosingSquareBrace {
actual: self.current.0.clone(),
@ -253,12 +253,52 @@ impl<'src> Parser<'src> {
}
}
}
(Token::ReservedIdentifier(reserved), _) => {
(Token::IsEven, left_span) => {
self.next_token()?;
let mut value_parameters = None;
if let (Token::LeftParenthesis, _) = self.current {
self.next_token()?;
value_parameters = Some(vec![self.parse_node(0)?]);
loop {
self.next_token()?;
if let (Token::RightParenthesis, _) = self.current {
break;
}
if let (Token::Comma, _) = self.current {
self.next_token()?;
}
value_parameters.as_mut().unwrap().push(self.parse_node(0)?);
}
} else {
return Err(ParseError::ExpectedOpeningParenthesis {
actual: self.current.0.clone(),
span: self.current.1,
});
}
if let (Token::RightParenthesis, _) = self.current {
self.next_token()?;
} else {
return Err(ParseError::ExpectedClosingParenthesis {
actual: self.current.0.clone(),
span: self.current.1,
});
}
Ok(Node::new(
Statement::ReservedIdentifier(reserved),
self.current.1,
Statement::BuiltInFunctionCall {
function: BuiltInFunction::IsEven,
type_arguments: None,
value_arguments: value_parameters,
},
left_span,
))
}
_ => Err(ParseError::UnexpectedToken(self.current.0.clone())),
@ -280,6 +320,7 @@ impl<'src> Parser<'src> {
pub enum ParseError {
ExpectedClosingParenthesis { actual: Token, span: Span },
ExpectedClosingSquareBrace { actual: Token, span: Span },
ExpectedOpeningParenthesis { actual: Token, span: Span },
LexError(LexError),
UnexpectedToken(Token),
}
@ -308,6 +349,32 @@ mod tests {
);
}
#[test]
fn property_access_function_call() {
let input = "42.is_even()";
assert_eq!(
parse(input),
Ok(AbstractSyntaxTree {
nodes: [Node::new(
Statement::PropertyAccess(
Box::new(Node::new(Statement::Constant(Value::integer(42)), (0, 2))),
Box::new(Node::new(
Statement::BuiltInFunctionCall {
function: BuiltInFunction::IsEven,
type_arguments: None,
value_arguments: None
},
(3, 10)
)),
),
(0, 10),
)]
.into()
})
);
}
#[test]
fn list_access() {
let input = "[1, 2, 3].0";

View File

@ -1,44 +1,57 @@
use std::fmt::Display;
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::Identifier;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Token {
Eof,
Identifier(Identifier),
// Hard-coded values
Boolean(bool),
Float(f64),
Integer(i64),
// Keywords
IsEven,
IsOdd,
Length,
// Symbols
Comma,
Dot,
Eof,
Equal,
Identifier(Identifier),
ReservedIdentifier(ReservedIdentifier),
Integer(i64),
Plus,
Star,
LeftParenthesis,
RightParenthesis,
LeftSquareBrace,
RightSquareBrace,
Float(f64),
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ReservedIdentifier {
// Number properties
IsEven,
IsOdd,
// List properties
Length,
}
impl Display for ReservedIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ReservedIdentifier::IsEven => write!(f, "is_even"),
ReservedIdentifier::IsOdd => write!(f, "is_odd"),
ReservedIdentifier::Length => write!(f, "length"),
Token::Eof => write!(f, "EOF"),
Token::Identifier(identifier) => write!(f, "{identifier}"),
Token::Boolean(boolean) => write!(f, "{boolean}"),
Token::Float(float) => write!(f, "{float}"),
Token::Integer(integer) => write!(f, "{integer}"),
Token::IsEven => write!(f, "is_even"),
Token::IsOdd => write!(f, "is_odd"),
Token::Length => write!(f, "length"),
Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."),
Token::Equal => write!(f, "="),
Token::Plus => write!(f, "+"),
Token::Star => write!(f, "*"),
Token::LeftParenthesis => write!(f, "("),
Token::RightParenthesis => write!(f, ")"),
Token::LeftSquareBrace => write!(f, "["),
Token::RightSquareBrace => write!(f, "]"),
}
}
}

View File

@ -12,7 +12,7 @@ use serde::{
Deserialize, Deserializer, Serialize,
};
use crate::{identifier::Identifier, Statement, Type};
use crate::{identifier::Identifier, AbstractSyntaxTree, Type, Vm, VmError};
#[derive(Clone, Debug, PartialEq)]
pub struct Value(Arc<ValueInner>);
@ -30,6 +30,10 @@ impl Value {
Value(Arc::new(ValueInner::Float(float)))
}
pub fn function(function: Function) -> Self {
Value(Arc::new(ValueInner::Function(function)))
}
pub fn integer(integer: i64) -> Self {
Value(Arc::new(ValueInner::Integer(integer)))
}
@ -62,6 +66,14 @@ impl Value {
}
}
pub fn as_function(&self) -> Option<&Function> {
if let ValueInner::Function(function) = self.0.as_ref() {
Some(function)
} else {
None
}
}
pub fn as_list(&self) -> Option<&Vec<Value>> {
if let ValueInner::List(list) = self.inner().as_ref() {
Some(list)
@ -539,12 +551,39 @@ pub struct Function {
pub name: Identifier,
pub type_parameters: Option<Vec<Type>>,
pub value_parameters: Option<Vec<(Identifier, Type)>>,
pub body: Vec<Statement<()>>,
pub body: AbstractSyntaxTree<()>,
}
impl Function {
pub fn call(
self,
type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
variables: &HashMap<Identifier, Value>,
) -> Result<Option<Value>, VmError<()>> {
let mut new_variables = variables.clone();
if let (Some(value_parameters), Some(value_arguments)) =
(self.value_parameters, value_arguments)
{
for ((identifier, _), value) in value_parameters.into_iter().zip(value_arguments) {
new_variables.insert(identifier, value);
}
}
let mut vm = Vm::new(self.body);
vm.run(&mut new_variables)
}
pub fn return_type(&self, variables: &HashMap<Identifier, Value>) -> Option<Type> {
self.body.last().unwrap().expected_type(variables)
self.body
.nodes
.iter()
.last()
.unwrap()
.statement
.expected_type(variables)
}
}
@ -580,7 +619,7 @@ impl Display for Function {
write!(f, ") {{")?;
for statement in &self.body {
for statement in &self.body.nodes {
write!(f, "{}", statement)?;
}

View File

@ -1,8 +1,8 @@
use std::collections::HashMap;
use crate::{
parse, AbstractSyntaxTree, Analyzer, AnalyzerError, Identifier, Node, ParseError,
ReservedIdentifier, Span, Statement, Value, ValueError,
abstract_tree::BuiltInFunctionError, parse, AbstractSyntaxTree, Analyzer, AnalyzerError,
Identifier, Node, ParseError, Span, Statement, Value, ValueError,
};
pub fn run(
@ -47,9 +47,6 @@ impl<P: Copy> Vm<P> {
variables: &mut HashMap<Identifier, Value>,
) -> Result<Option<Value>, VmError<P>> {
match node.statement {
Statement::Constant(value) => Ok(Some(value.clone())),
Statement::Identifier(_) => Ok(None),
Statement::ReservedIdentifier(_) => Ok(None),
Statement::Add(left, right) => {
let left_span = left.position;
let left = if let Some(value) = self.run_node(*left, variables)? {
@ -92,6 +89,84 @@ impl<P: Copy> Vm<P> {
Ok(None)
}
Statement::BuiltInFunctionCall {
function,
type_arguments: _,
value_arguments: value_nodes,
} => {
let mut values = if let Some(nodes) = value_nodes {
let mut values = Vec::new();
for node in nodes {
let position = node.position;
let value = if let Some(value) = self.run_node(node, variables)? {
value
} else {
return Err(VmError::ExpectedValue { position });
};
values.push(value);
}
Some(values)
} else {
None
};
let function_call_return = function.call(None, values)?;
Ok(Some(function_call_return))
}
Statement::Constant(value) => Ok(Some(value.clone())),
Statement::FunctionCall {
function: function_node,
type_arguments: type_parameter_nodes,
value_arguments: value_parameter_nodes,
} => {
let function_position = function_node.position;
let function_value =
if let Some(value) = self.run_node(*function_node, variables)? {
value
} else {
return Err(VmError::ExpectedValue {
position: function_position,
});
};
let function = if let Some(function) = function_value.as_function() {
function
} else {
return Err(VmError::AnaylyzerError(AnalyzerError::ExpectedFunction {
position: function_position,
}));
};
let value_parameters = if let Some(value_nodes) = value_parameter_nodes {
let mut value_parameters = Vec::new();
for node in value_nodes {
let position = node.position;
let value = if let Some(value) = self.run_node(node, variables)? {
value
} else {
return Err(VmError::ExpectedValue { position });
};
value_parameters.push(value);
}
Some(value_parameters)
} else {
None
};
Ok(function
.clone()
.call(None, value_parameters, variables)
.map_err(|error| VmError::FunctionCallFailed {
error: Box::new(error),
position: function_position,
})?)
}
Statement::Identifier(_) => Ok(None),
Statement::List(nodes) => {
let values = nodes
.into_iter()
@ -110,7 +185,7 @@ impl<P: Copy> Vm<P> {
Statement::Multiply(_, _) => todo!(),
Statement::PropertyAccess(left, right) => {
let left_span = left.position;
let left = if let Some(value) = self.run_node(*left, variables)? {
let left_value = if let Some(value) = self.run_node(*left, variables)? {
value
} else {
return Err(VmError::ExpectedValue {
@ -119,39 +194,8 @@ impl<P: Copy> Vm<P> {
};
let right_span = right.position;
if let Statement::ReservedIdentifier(reserved) = &right.statement {
match reserved {
ReservedIdentifier::IsEven => {
if let Some(integer) = left.as_integer() {
return Ok(Some(Value::boolean(integer % 2 == 0)));
} else {
return Err(VmError::ExpectedInteger {
position: right_span,
});
}
}
ReservedIdentifier::IsOdd => {
if let Some(integer) = left.as_integer() {
return Ok(Some(Value::boolean(integer % 2 != 0)));
} else {
return Err(VmError::ExpectedInteger {
position: right_span,
});
}
}
ReservedIdentifier::Length => {
if let Some(list) = left.as_list() {
return Ok(Some(Value::integer(list.len() as i64)));
} else {
return Err(VmError::ExpectedList {
position: right_span,
});
}
}
}
}
if let (Some(list), Statement::Constant(value)) = (left.as_list(), &right.statement)
if let (Some(list), Statement::Constant(value)) =
(left_value.as_list(), &right.statement)
{
if let Some(index) = value.as_integer() {
let value = list.get(index as usize).cloned();
@ -160,6 +204,43 @@ impl<P: Copy> Vm<P> {
}
}
if let (
value,
Statement::BuiltInFunctionCall {
function,
type_arguments,
value_arguments: mut value_argument_nodes,
},
) = (left_value, right.statement)
{
if let Some(mut nodes) = value_argument_nodes.take() {
nodes.insert(0, Node::new(Statement::Constant(value), right_span));
}
let value_arguments = if let Some(value_nodes) = value_argument_nodes {
let mut value_arguments = Vec::new();
for node in value_nodes {
let position = node.position;
let value = if let Some(value) = self.run_node(node, variables)? {
value
} else {
return Err(VmError::ExpectedValue { position });
};
value_arguments.push(value);
}
Some(value_arguments)
} else {
None
};
let function_call_return = function.call(None, value_arguments)?;
return Ok(Some(function_call_return));
}
Err(VmError::ExpectedIdentifierOrInteger {
position: right_span,
})
@ -176,11 +257,32 @@ pub enum VmError<P> {
// Anaylsis Failures
// These should be prevented by running the analyzer before the VM
ExpectedIdentifier { position: P },
ExpectedIdentifierOrInteger { position: P },
ExpectedInteger { position: P },
ExpectedList { position: P },
ExpectedValue { position: P },
BuiltInFunctionCallFailed(BuiltInFunctionError),
ExpectedIdentifier {
position: P,
},
ExpectedIdentifierOrInteger {
position: P,
},
ExpectedInteger {
position: P,
},
ExpectedList {
position: P,
},
ExpectedValue {
position: P,
},
FunctionCallFailed {
error: Box<VmError<()>>,
position: P,
},
}
impl<P> From<BuiltInFunctionError> for VmError<P> {
fn from(v: BuiltInFunctionError) -> Self {
Self::BuiltInFunctionCallFailed(v)
}
}
impl<P> From<AnalyzerError<P>> for VmError<P> {
@ -217,7 +319,7 @@ mod tests {
#[test]
fn is_even() {
let input = "42.is_even";
let input = "42.is_even()";
assert_eq!(
run(input, &mut HashMap::new()),
@ -227,7 +329,7 @@ mod tests {
#[test]
fn is_odd() {
let input = "42.is_odd";
let input = "42.is_odd()";
assert_eq!(
run(input, &mut HashMap::new()),
@ -235,6 +337,13 @@ mod tests {
);
}
#[test]
fn length() {
let input = "[1, 2, 3].length()";
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(3))));
}
#[test]
fn list_access() {
let input = "[1, 2, 3].1";
@ -242,13 +351,6 @@ mod tests {
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(2))));
}
#[test]
fn property_access() {
let input = "[1, 2, 3].length";
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(3))));
}
#[test]
fn add() {
let input = "1 + 2";