Compare commits
3 Commits
ed82f3c64f
...
f50b765c1e
Author | SHA1 | Date | |
---|---|---|---|
f50b765c1e | |||
82fbf796f3 | |||
60f8aab805 |
@ -34,12 +34,6 @@ impl<T: Display> Display for Node<T> {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Statement {
|
||||
// Variable assignment
|
||||
Assignment {
|
||||
identifier: Node<Identifier>,
|
||||
value_node: Box<Node<Statement>>,
|
||||
},
|
||||
|
||||
// A sequence of statements
|
||||
Block(Vec<Node<Statement>>),
|
||||
|
||||
@ -70,7 +64,7 @@ pub enum Statement {
|
||||
|
||||
// Value collection expressions
|
||||
List(Vec<Node<Statement>>),
|
||||
Map(Vec<(Node<Identifier>, Node<Statement>)>),
|
||||
Map(Vec<(Node<Statement>, Node<Statement>)>),
|
||||
|
||||
// Hard-coded value
|
||||
Constant(Value),
|
||||
@ -83,7 +77,6 @@ pub enum Statement {
|
||||
impl Statement {
|
||||
pub fn expected_type(&self, variables: &HashMap<Identifier, Value>) -> Option<Type> {
|
||||
match self {
|
||||
Statement::Assignment { .. } => None,
|
||||
Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(variables),
|
||||
Statement::BinaryOperation { left, .. } => left.inner.expected_type(variables),
|
||||
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
||||
@ -104,10 +97,9 @@ impl Statement {
|
||||
let mut types = BTreeMap::new();
|
||||
|
||||
for (identifier, item) in nodes {
|
||||
types.insert(
|
||||
identifier.inner.clone(),
|
||||
item.inner.expected_type(variables)?,
|
||||
);
|
||||
if let Statement::Identifier(identifier) = &identifier.inner {
|
||||
types.insert(identifier.clone(), item.inner.expected_type(variables)?);
|
||||
}
|
||||
}
|
||||
|
||||
Some(Type::Map(types))
|
||||
@ -121,12 +113,6 @@ impl Statement {
|
||||
impl Display for Statement {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Statement::Assignment {
|
||||
identifier,
|
||||
value_node: value,
|
||||
} => {
|
||||
write!(f, "{identifier} = {value}")
|
||||
}
|
||||
Statement::Block(statements) => {
|
||||
write!(f, "{{ ")?;
|
||||
|
||||
@ -270,12 +256,18 @@ pub enum BinaryOperator {
|
||||
// Logic
|
||||
And,
|
||||
Or,
|
||||
|
||||
// Assignment
|
||||
Assign,
|
||||
AddAssign,
|
||||
}
|
||||
|
||||
impl Display for BinaryOperator {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
BinaryOperator::Add => write!(f, "+"),
|
||||
BinaryOperator::AddAssign => write!(f, "+="),
|
||||
BinaryOperator::Assign => write!(f, "="),
|
||||
BinaryOperator::And => write!(f, "&&"),
|
||||
BinaryOperator::Divide => write!(f, "/"),
|
||||
BinaryOperator::Equal => write!(f, "=="),
|
||||
|
@ -77,23 +77,19 @@ impl<'a> Analyzer<'a> {
|
||||
|
||||
fn analyze_node(&self, node: &Node<Statement>) -> Result<(), AnalyzerError> {
|
||||
match &node.inner {
|
||||
Statement::Assignment {
|
||||
value_node: value, ..
|
||||
} => {
|
||||
self.analyze_node(value)?;
|
||||
|
||||
if value.inner.expected_type(self.variables).is_none() {
|
||||
return Err(AnalyzerError::ExpectedValue {
|
||||
actual: value.as_ref().clone(),
|
||||
position: value.position,
|
||||
});
|
||||
}
|
||||
}
|
||||
Statement::BinaryOperation {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => {
|
||||
if let BinaryOperator::AddAssign | BinaryOperator::Assign = operator.inner {
|
||||
if let Statement::Identifier(_) = left.inner {
|
||||
self.analyze_node(right)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
self.analyze_node(left)?;
|
||||
self.analyze_node(right)?;
|
||||
|
||||
@ -157,11 +153,12 @@ impl<'a> Analyzer<'a> {
|
||||
});
|
||||
}
|
||||
}
|
||||
Statement::Identifier(_) => {
|
||||
return Err(AnalyzerError::UnexpectedIdentifier {
|
||||
identifier: node.clone(),
|
||||
position: node.position,
|
||||
});
|
||||
Statement::Identifier(identifier) => {
|
||||
if !self.variables.contains_key(identifier) {
|
||||
return Err(AnalyzerError::UndefinedVariable {
|
||||
identifier: node.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Statement::List(statements) => {
|
||||
for statement in statements {
|
||||
@ -251,6 +248,9 @@ pub enum AnalyzerError {
|
||||
actual: Node<Statement>,
|
||||
position: Span,
|
||||
},
|
||||
UndefinedVariable {
|
||||
identifier: Node<Statement>,
|
||||
},
|
||||
UnexpectedIdentifier {
|
||||
identifier: Node<Statement>,
|
||||
position: Span,
|
||||
@ -274,6 +274,7 @@ impl AnalyzerError {
|
||||
AnalyzerError::ExpectedIntegerFloatOrString { position, .. } => *position,
|
||||
AnalyzerError::ExpectedSameType { position, .. } => *position,
|
||||
AnalyzerError::ExpectedString { position, .. } => *position,
|
||||
AnalyzerError::UndefinedVariable { identifier } => identifier.position,
|
||||
AnalyzerError::UnexpectedIdentifier { position, .. } => *position,
|
||||
AnalyzerError::UnexectedString { position, .. } => *position,
|
||||
}
|
||||
@ -315,6 +316,9 @@ impl Display for AnalyzerError {
|
||||
AnalyzerError::ExpectedValue { actual, .. } => {
|
||||
write!(f, "Expected value, found {}", actual)
|
||||
}
|
||||
AnalyzerError::UndefinedVariable { identifier } => {
|
||||
write!(f, "Undefined variable {}", identifier)
|
||||
}
|
||||
AnalyzerError::UnexpectedIdentifier { identifier, .. } => {
|
||||
write!(f, "Unexpected identifier {}", identifier)
|
||||
}
|
||||
@ -443,7 +447,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_identifier() {
|
||||
fn undefined_variable() {
|
||||
let abstract_tree = AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::Identifier(Identifier::new("x")),
|
||||
@ -456,9 +460,8 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
analyzer.analyze(),
|
||||
Err(AnalyzerError::UnexpectedIdentifier {
|
||||
identifier: Node::new(Statement::Identifier(Identifier::new("x")), (0, 1)),
|
||||
position: (0, 1)
|
||||
Err(AnalyzerError::UndefinedVariable {
|
||||
identifier: Node::new(Statement::Identifier(Identifier::new("x")), (0, 1))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
num::{ParseFloatError, ParseIntError},
|
||||
};
|
||||
|
||||
use crate::{Span, Token};
|
||||
@ -24,9 +23,9 @@ use crate::{Span, Token};
|
||||
/// [
|
||||
/// (Token::Identifier("x"), (0, 1)),
|
||||
/// (Token::Equal, (2, 3)),
|
||||
/// (Token::Integer(1), (4, 5)),
|
||||
/// (Token::Integer("1"), (4, 5)),
|
||||
/// (Token::Plus, (6, 7)),
|
||||
/// (Token::Integer(2), (8, 9)),
|
||||
/// (Token::Integer("2"), (8, 9)),
|
||||
/// (Token::Eof, (9, 9)),
|
||||
/// ]
|
||||
/// );
|
||||
@ -77,9 +76,9 @@ pub fn lex<'chars, 'src: 'chars>(input: &'src str) -> Result<Vec<(Token<'chars>,
|
||||
/// [
|
||||
/// (Token::Identifier("x"), (0, 1)),
|
||||
/// (Token::Equal, (2, 3)),
|
||||
/// (Token::Integer(1), (4, 5)),
|
||||
/// (Token::Integer("1"), (4, 5)),
|
||||
/// (Token::Plus, (6, 7)),
|
||||
/// (Token::Integer(2), (8, 9)),
|
||||
/// (Token::Integer("2"), (8, 9)),
|
||||
/// (Token::Eof, (9, 9)),
|
||||
/// ]
|
||||
/// )
|
||||
@ -110,7 +109,7 @@ impl Lexer {
|
||||
self.position += 9;
|
||||
|
||||
(
|
||||
Token::Float(f64::NEG_INFINITY),
|
||||
Token::Float("-Infinity"),
|
||||
(self.position - 9, self.position),
|
||||
)
|
||||
} else {
|
||||
@ -123,9 +122,15 @@ impl Lexer {
|
||||
'"' => self.lex_string('"', source)?,
|
||||
'\'' => self.lex_string('\'', source)?,
|
||||
'+' => {
|
||||
self.position += 1;
|
||||
if let Some('=') = self.peek_second_char(source) {
|
||||
self.position += 2;
|
||||
|
||||
(Token::Plus, (self.position - 1, self.position))
|
||||
(Token::PlusEqual, (self.position - 2, self.position))
|
||||
} else {
|
||||
self.position += 1;
|
||||
|
||||
(Token::Plus, (self.position - 1, self.position))
|
||||
}
|
||||
}
|
||||
'*' => {
|
||||
self.position += 1;
|
||||
@ -231,6 +236,17 @@ impl Lexer {
|
||||
|
||||
(Token::Semicolon, (self.position - 1, self.position))
|
||||
}
|
||||
'|' => {
|
||||
if let Some('|') = self.peek_second_char(source) {
|
||||
self.position += 2;
|
||||
|
||||
(Token::DoublePipe, (self.position - 2, self.position))
|
||||
} else {
|
||||
self.position += 1;
|
||||
|
||||
return Err(LexError::UnexpectedCharacter(c));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.position += 1;
|
||||
|
||||
@ -244,6 +260,15 @@ impl Lexer {
|
||||
Ok((token, span))
|
||||
}
|
||||
|
||||
/// Peek at the next token without consuming the source.
|
||||
pub fn peek_token<'src>(&mut self, source: &'src str) -> Result<(Token<'src>, Span), LexError> {
|
||||
let token = self.next_token(source)?;
|
||||
|
||||
self.position -= token.0.as_str().len();
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Progress to the next character.
|
||||
fn next_char(&mut self, source: &str) -> Option<char> {
|
||||
if let Some(c) = source[self.position..].chars().next() {
|
||||
@ -335,14 +360,12 @@ impl Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
let text = &source[start_pos..self.position];
|
||||
|
||||
if is_float {
|
||||
let float = source[start_pos..self.position].parse::<f64>()?;
|
||||
|
||||
Ok((Token::Float(float), (start_pos, self.position)))
|
||||
Ok((Token::Float(text), (start_pos, self.position)))
|
||||
} else {
|
||||
let integer = source[start_pos..self.position].parse::<i64>()?;
|
||||
|
||||
Ok((Token::Integer(integer), (start_pos, self.position)))
|
||||
Ok((Token::Integer(text), (start_pos, self.position)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,13 +386,13 @@ impl Lexer {
|
||||
|
||||
let string = &source[start_pos..self.position];
|
||||
let token = match string {
|
||||
"true" => Token::Boolean(true),
|
||||
"false" => Token::Boolean(false),
|
||||
"Infinity" => Token::Float(f64::INFINITY),
|
||||
"true" => Token::Boolean("true"),
|
||||
"false" => Token::Boolean("false"),
|
||||
"Infinity" => Token::Float("Infinity"),
|
||||
"is_even" => Token::IsEven,
|
||||
"is_odd" => Token::IsOdd,
|
||||
"length" => Token::Length,
|
||||
"NaN" => Token::Float(f64::NAN),
|
||||
"NaN" => Token::Float("NaN"),
|
||||
"read_line" => Token::ReadLine,
|
||||
"write_line" => Token::WriteLine,
|
||||
_ => Token::Identifier(string),
|
||||
@ -410,16 +433,12 @@ impl Default for Lexer {
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum LexError {
|
||||
FloatError(ParseFloatError),
|
||||
IntegerError(ParseIntError),
|
||||
UnexpectedCharacter(char),
|
||||
}
|
||||
|
||||
impl Error for LexError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::FloatError(parse_float_error) => Some(parse_float_error),
|
||||
Self::IntegerError(parse_int_error) => Some(parse_int_error),
|
||||
Self::UnexpectedCharacter(_) => None,
|
||||
}
|
||||
}
|
||||
@ -428,12 +447,6 @@ impl Error for LexError {
|
||||
impl Display for LexError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::FloatError(parse_float_error) => {
|
||||
write!(f, "Failed to parse float: {}", parse_float_error)
|
||||
}
|
||||
Self::IntegerError(parse_int_error) => {
|
||||
write!(f, "Failed to parse integer: {}", parse_int_error)
|
||||
}
|
||||
Self::UnexpectedCharacter(character) => {
|
||||
write!(f, "Unexpected character: '{}'", character)
|
||||
}
|
||||
@ -441,22 +454,40 @@ impl Display for LexError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseFloatError> for LexError {
|
||||
fn from(error: std::num::ParseFloatError) -> Self {
|
||||
Self::FloatError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for LexError {
|
||||
fn from(error: std::num::ParseIntError) -> Self {
|
||||
Self::IntegerError(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_assign() {
|
||||
let input = "x += 42";
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Identifier("x"), (0, 1)),
|
||||
(Token::PlusEqual, (2, 4)),
|
||||
(Token::Integer("42"), (5, 7)),
|
||||
(Token::Eof, (7, 7)),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
let input = "true || false";
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Boolean("true"), (0, 4)),
|
||||
(Token::DoublePipe, (5, 7)),
|
||||
(Token::Boolean("false"), (8, 13)),
|
||||
(Token::Eof, (13, 13)),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block() {
|
||||
let input = "{ x = 42; y = 'foobar' }";
|
||||
@ -467,7 +498,7 @@ mod tests {
|
||||
(Token::LeftCurlyBrace, (0, 1)),
|
||||
(Token::Identifier("x"), (2, 3)),
|
||||
(Token::Equal, (4, 5)),
|
||||
(Token::Integer(42), (6, 8)),
|
||||
(Token::Integer("42"), (6, 8)),
|
||||
(Token::Semicolon, (8, 9)),
|
||||
(Token::Identifier("y"), (10, 11)),
|
||||
(Token::Equal, (12, 13)),
|
||||
@ -485,9 +516,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(42), (0, 2)),
|
||||
(Token::Integer("42"), (0, 2)),
|
||||
(Token::DoubleEqual, (3, 5)),
|
||||
(Token::Integer(42), (6, 8)),
|
||||
(Token::Integer("42"), (6, 8)),
|
||||
(Token::Eof, (8, 8)),
|
||||
])
|
||||
)
|
||||
@ -500,9 +531,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(42), (0, 2)),
|
||||
(Token::Integer("42"), (0, 2)),
|
||||
(Token::Percent, (3, 4)),
|
||||
(Token::Integer(2), (5, 6)),
|
||||
(Token::Integer("2"), (5, 6)),
|
||||
(Token::Eof, (6, 6)),
|
||||
])
|
||||
)
|
||||
@ -515,9 +546,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(42), (0, 2)),
|
||||
(Token::Integer("42"), (0, 2)),
|
||||
(Token::Slash, (3, 4)),
|
||||
(Token::Integer(2), (5, 6)),
|
||||
(Token::Integer("2"), (5, 6)),
|
||||
(Token::Eof, (6, 6)),
|
||||
])
|
||||
)
|
||||
@ -533,7 +564,7 @@ mod tests {
|
||||
(Token::LeftCurlyBrace, (0, 1)),
|
||||
(Token::Identifier("x"), (2, 3)),
|
||||
(Token::Equal, (4, 5)),
|
||||
(Token::Integer(42), (6, 8)),
|
||||
(Token::Integer("42"), (6, 8)),
|
||||
(Token::Comma, (8, 9)),
|
||||
(Token::Identifier("y"), (10, 11)),
|
||||
(Token::Equal, (12, 13)),
|
||||
@ -591,7 +622,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Float(f64::INFINITY), (0, 8)),
|
||||
(Token::Float("Infinity"), (0, 8)),
|
||||
(Token::Eof, (8, 8)),
|
||||
])
|
||||
)
|
||||
@ -604,7 +635,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Float(f64::NEG_INFINITY), (0, 9)),
|
||||
(Token::Float("-Infinity"), (0, 9)),
|
||||
(Token::Eof, (9, 9)),
|
||||
])
|
||||
)
|
||||
@ -614,7 +645,7 @@ mod tests {
|
||||
fn nan() {
|
||||
let input = "NaN";
|
||||
|
||||
assert!(lex(input).is_ok_and(|tokens| tokens[0].0 == Token::Float(f64::NAN)));
|
||||
assert!(lex(input).is_ok_and(|tokens| tokens[0].0 == Token::Float("NaN")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -623,7 +654,10 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![(Token::Float(42.42e42), (0, 8)), (Token::Eof, (8, 8)),])
|
||||
Ok(vec![
|
||||
(Token::Float("42.42e42"), (0, 8)),
|
||||
(Token::Eof, (8, 8)),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
@ -634,7 +668,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(i64::MAX), (0, 19)),
|
||||
(Token::Integer("9223372036854775807"), (0, 19)),
|
||||
(Token::Eof, (19, 19)),
|
||||
])
|
||||
)
|
||||
@ -647,7 +681,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(i64::MIN), (0, 20)),
|
||||
(Token::Integer("-9223372036854775808"), (0, 20)),
|
||||
(Token::Eof, (20, 20)),
|
||||
])
|
||||
)
|
||||
@ -660,9 +694,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(-42), (0, 3)),
|
||||
(Token::Integer("-42"), (0, 3)),
|
||||
(Token::Minus, (4, 5)),
|
||||
(Token::Integer(-42), (6, 9)),
|
||||
(Token::Integer("-42"), (6, 9)),
|
||||
(Token::Eof, (9, 9)),
|
||||
])
|
||||
)
|
||||
@ -674,7 +708,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![(Token::Integer(-42), (0, 3)), (Token::Eof, (3, 3))])
|
||||
Ok(vec![(Token::Integer("-42"), (0, 3)), (Token::Eof, (3, 3))])
|
||||
)
|
||||
}
|
||||
|
||||
@ -743,7 +777,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![(Token::Boolean(true), (0, 4)), (Token::Eof, (4, 4)),])
|
||||
Ok(vec![(Token::Boolean("true"), (0, 4)), (Token::Eof, (4, 4)),])
|
||||
)
|
||||
}
|
||||
|
||||
@ -753,7 +787,10 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![(Token::Boolean(false), (0, 5)), (Token::Eof, (5, 5))])
|
||||
Ok(vec![
|
||||
(Token::Boolean("false"), (0, 5)),
|
||||
(Token::Eof, (5, 5))
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
@ -764,7 +801,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(42), (0, 2)),
|
||||
(Token::Integer("42"), (0, 2)),
|
||||
(Token::Dot, (2, 3)),
|
||||
(Token::IsEven, (3, 10)),
|
||||
(Token::LeftParenthesis, (10, 11)),
|
||||
@ -811,7 +848,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![(Token::Float(1.23), (0, 4)), (Token::Eof, (4, 4)),])
|
||||
Ok(vec![(Token::Float("1.23"), (0, 4)), (Token::Eof, (4, 4)),])
|
||||
)
|
||||
}
|
||||
|
||||
@ -823,7 +860,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Float(123456789.123456789), (0, 19)),
|
||||
(Token::Float("123456789.123456789"), (0, 19)),
|
||||
(Token::Eof, (19, 19)),
|
||||
])
|
||||
)
|
||||
@ -836,9 +873,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(1), (0, 1)),
|
||||
(Token::Integer("1"), (0, 1)),
|
||||
(Token::Plus, (2, 3)),
|
||||
(Token::Integer(2), (4, 5)),
|
||||
(Token::Integer("2"), (4, 5)),
|
||||
(Token::Eof, (5, 5)),
|
||||
])
|
||||
)
|
||||
@ -851,9 +888,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(1), (0, 1)),
|
||||
(Token::Integer("1"), (0, 1)),
|
||||
(Token::Star, (2, 3)),
|
||||
(Token::Integer(2), (4, 5)),
|
||||
(Token::Integer("2"), (4, 5)),
|
||||
(Token::Eof, (5, 5)),
|
||||
])
|
||||
)
|
||||
@ -866,11 +903,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
lex(input),
|
||||
Ok(vec![
|
||||
(Token::Integer(1), (0, 1)),
|
||||
(Token::Integer("1"), (0, 1)),
|
||||
(Token::Plus, (2, 3)),
|
||||
(Token::Integer(2), (4, 5)),
|
||||
(Token::Integer("2"), (4, 5)),
|
||||
(Token::Star, (6, 7)),
|
||||
(Token::Integer(3), (8, 9)),
|
||||
(Token::Integer("3"), (8, 9)),
|
||||
(Token::Eof, (9, 9)),
|
||||
])
|
||||
);
|
||||
@ -885,11 +922,11 @@ mod tests {
|
||||
Ok(vec![
|
||||
(Token::Identifier("a"), (0, 1)),
|
||||
(Token::Equal, (2, 3)),
|
||||
(Token::Integer(1), (4, 5)),
|
||||
(Token::Integer("1"), (4, 5)),
|
||||
(Token::Plus, (6, 7)),
|
||||
(Token::Integer(2), (8, 9)),
|
||||
(Token::Integer("2"), (8, 9)),
|
||||
(Token::Star, (10, 11)),
|
||||
(Token::Integer(3), (12, 13)),
|
||||
(Token::Integer("3"), (12, 13)),
|
||||
(Token::Eof, (13, 13)),
|
||||
])
|
||||
);
|
||||
|
@ -7,6 +7,8 @@ use std::{
|
||||
collections::VecDeque,
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
num::{ParseFloatError, ParseIntError},
|
||||
str::ParseBoolError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -150,280 +152,79 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
fn parse_node(&mut self, precedence: u8) -> Result<Node<Statement>, ParseError> {
|
||||
let left_node = self.parse_primary()?;
|
||||
let left_start = left_node.position.0;
|
||||
let left = self.parse_primary()?;
|
||||
|
||||
if precedence < self.current_precedence() {
|
||||
match &self.current {
|
||||
(Token::Dot, _) => {
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::PropertyAccess(Box::new(left_node), Box::new(right_node)),
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::DoubleAmpersand, _) => {
|
||||
let operator = Node::new(BinaryOperator::And, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::DoubleEqual, _) => {
|
||||
let operator = Node::new(BinaryOperator::Equal, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Greater, _) => {
|
||||
let operator = Node::new(BinaryOperator::Greater, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::GreaterEqual, _) => {
|
||||
let operator = Node::new(BinaryOperator::GreaterOrEqual, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Less, _) => {
|
||||
let operator = Node::new(BinaryOperator::Less, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::LessEqual, _) => {
|
||||
let operator = Node::new(BinaryOperator::LessOrEqual, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Minus, _) => {
|
||||
let operator = Node::new(BinaryOperator::Subtract, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Plus, _) => {
|
||||
let operator = Node::new(BinaryOperator::Add, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Semicolon, (_, right_end)) => {
|
||||
return Ok(Node::new(
|
||||
Statement::Nil(Box::new(left_node)),
|
||||
(left_start, *right_end),
|
||||
))
|
||||
}
|
||||
(Token::Star, _) => {
|
||||
let operator = Node::new(BinaryOperator::Multiply, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Slash, _) => {
|
||||
let operator = Node::new(BinaryOperator::Divide, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::Percent, _) => {
|
||||
let operator = Node::new(BinaryOperator::Modulo, self.current.1);
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left_node),
|
||||
operator,
|
||||
right: Box::new(right_node),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.parse_infix(left)
|
||||
} else {
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
Ok(left_node)
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> Result<Node<Statement>, ParseError> {
|
||||
match self.current {
|
||||
(Token::Boolean(boolean), span) => {
|
||||
(Token::Boolean(text), position) => {
|
||||
self.next_token()?;
|
||||
|
||||
let boolean = text
|
||||
.parse()
|
||||
.map_err(|error| ParseError::BooleanError { error, position })?;
|
||||
|
||||
Ok(Node::new(
|
||||
Statement::Constant(Value::boolean(boolean)),
|
||||
span,
|
||||
position,
|
||||
))
|
||||
}
|
||||
(Token::Float(float), span) => {
|
||||
(Token::Float(text), position) => {
|
||||
self.next_token()?;
|
||||
|
||||
Ok(Node::new(Statement::Constant(Value::float(float)), span))
|
||||
let float = text
|
||||
.parse()
|
||||
.map_err(|error| ParseError::FloatError { error, position })?;
|
||||
|
||||
Ok(Node::new(
|
||||
Statement::Constant(Value::float(float)),
|
||||
position,
|
||||
))
|
||||
}
|
||||
(Token::Integer(int), span) => {
|
||||
(Token::Integer(text), position) => {
|
||||
self.next_token()?;
|
||||
|
||||
Ok(Node::new(Statement::Constant(Value::integer(int)), span))
|
||||
let integer = text
|
||||
.parse()
|
||||
.map_err(|error| ParseError::IntegerError { error, position })?;
|
||||
|
||||
Ok(Node::new(
|
||||
Statement::Constant(Value::integer(integer)),
|
||||
position,
|
||||
))
|
||||
}
|
||||
(Token::Identifier(text), span) => {
|
||||
(Token::Identifier(text), position) => {
|
||||
self.next_token()?;
|
||||
|
||||
if let (Token::Equal, _) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
let value_node = self.parse_node(0)?;
|
||||
let right_end = value_node.position.1;
|
||||
|
||||
Ok(Node::new(
|
||||
Statement::Assignment {
|
||||
identifier: Node::new(Identifier::new(text), span),
|
||||
value_node: Box::new(value_node),
|
||||
},
|
||||
(span.0, right_end),
|
||||
))
|
||||
} else {
|
||||
Ok(Node::new(
|
||||
Statement::Identifier(Identifier::new(text)),
|
||||
span,
|
||||
))
|
||||
}
|
||||
Ok(Node::new(
|
||||
Statement::Identifier(Identifier::new(text)),
|
||||
position,
|
||||
))
|
||||
}
|
||||
(Token::String(string), span) => {
|
||||
(Token::String(string), position) => {
|
||||
self.next_token()?;
|
||||
|
||||
Ok(Node::new(Statement::Constant(Value::string(string)), span))
|
||||
Ok(Node::new(
|
||||
Statement::Constant(Value::string(string)),
|
||||
position,
|
||||
))
|
||||
}
|
||||
(Token::LeftCurlyBrace, left_span) => {
|
||||
(Token::LeftCurlyBrace, left_position) => {
|
||||
self.next_token()?;
|
||||
|
||||
// If the next token is a right curly brace, this is an empty map
|
||||
if let (Token::RightCurlyBrace, right_span) = self.current {
|
||||
if let (Token::RightCurlyBrace, right_position) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::Map(Vec::new()),
|
||||
(left_span.0, right_span.1),
|
||||
(left_position.0, right_position.1),
|
||||
));
|
||||
}
|
||||
|
||||
@ -431,22 +232,29 @@ impl<'src> Parser<'src> {
|
||||
|
||||
loop {
|
||||
// If a closing brace is found, return the new statement
|
||||
if let (Token::RightCurlyBrace, right_span) = self.current {
|
||||
if let (Token::RightCurlyBrace, right_position) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
return Ok(Node::new(statement.unwrap(), (left_span.0, right_span.1)));
|
||||
return Ok(Node::new(
|
||||
statement.unwrap(),
|
||||
(left_position.0, right_position.1),
|
||||
));
|
||||
}
|
||||
|
||||
let next_node = self.parse_node(0)?;
|
||||
|
||||
// If the next node is an assignment, this might be a map
|
||||
if let Statement::Assignment {
|
||||
identifier,
|
||||
value_node,
|
||||
if let Statement::BinaryOperation {
|
||||
left,
|
||||
operator:
|
||||
Node {
|
||||
inner: BinaryOperator::Assign,
|
||||
..
|
||||
},
|
||||
right,
|
||||
} = next_node.inner
|
||||
{
|
||||
// If the current token is a comma, right curly brace, or the new
|
||||
// statement is already a map
|
||||
// If the current token is a comma, or the new statement is already a map
|
||||
if self.current.0 == Token::Comma
|
||||
|| statement
|
||||
.as_ref()
|
||||
@ -462,39 +270,10 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
// Add the new property to the map
|
||||
map_properties.push((identifier, *value_node));
|
||||
}
|
||||
// Otherwise, the new statement is a block
|
||||
} else if let Statement::Block(statements) =
|
||||
statement.get_or_insert_with(|| Statement::Block(Vec::new()))
|
||||
{
|
||||
if self.current.0 == Token::Semicolon {
|
||||
self.next_token()?;
|
||||
|
||||
statements.push(Node::new(
|
||||
Statement::Nil(Box::new(Node::new(
|
||||
Statement::Assignment {
|
||||
identifier,
|
||||
value_node,
|
||||
},
|
||||
next_node.position,
|
||||
))),
|
||||
(next_node.position.0, self.current.1 .1),
|
||||
));
|
||||
|
||||
continue;
|
||||
} else {
|
||||
statements.push(Node::new(
|
||||
Statement::Assignment {
|
||||
identifier,
|
||||
value_node,
|
||||
},
|
||||
next_node.position,
|
||||
));
|
||||
|
||||
continue;
|
||||
map_properties.push((*left, *right));
|
||||
}
|
||||
}
|
||||
// Otherwise, the new statement is a block
|
||||
} else if let Statement::Block(statements) =
|
||||
statement.get_or_insert_with(|| Statement::Block(Vec::new()))
|
||||
{
|
||||
@ -503,15 +282,15 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
}
|
||||
}
|
||||
(Token::LeftParenthesis, left_span) => {
|
||||
(Token::LeftParenthesis, left_position) => {
|
||||
self.next_token()?;
|
||||
|
||||
let node = self.parse_node(0)?;
|
||||
|
||||
if let (Token::RightParenthesis, right_span) = self.current {
|
||||
if let (Token::RightParenthesis, right_position) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
Ok(Node::new(node.inner, (left_span.0, right_span.1)))
|
||||
Ok(Node::new(node.inner, (left_position.0, right_position.1)))
|
||||
} else {
|
||||
Err(ParseError::ExpectedToken {
|
||||
expected: TokenOwned::RightParenthesis,
|
||||
@ -520,18 +299,18 @@ impl<'src> Parser<'src> {
|
||||
})
|
||||
}
|
||||
}
|
||||
(Token::LeftSquareBrace, left_span) => {
|
||||
(Token::LeftSquareBrace, left_position) => {
|
||||
self.next_token()?;
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
loop {
|
||||
if let (Token::RightSquareBrace, right_span) = self.current {
|
||||
if let (Token::RightSquareBrace, right_position) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::List(nodes),
|
||||
(left_span.0, right_span.1),
|
||||
(left_position.0, right_position.1),
|
||||
));
|
||||
}
|
||||
|
||||
@ -554,7 +333,7 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
(
|
||||
Token::IsEven | Token::IsOdd | Token::Length | Token::ReadLine | Token::WriteLine,
|
||||
left_span,
|
||||
left_position,
|
||||
) => {
|
||||
let function = match self.current.0 {
|
||||
Token::IsEven => BuiltInFunction::IsEven,
|
||||
@ -611,7 +390,7 @@ impl<'src> Parser<'src> {
|
||||
type_arguments: None,
|
||||
value_arguments,
|
||||
},
|
||||
left_span,
|
||||
left_position,
|
||||
))
|
||||
}
|
||||
_ => Err(ParseError::UnexpectedToken {
|
||||
@ -621,10 +400,80 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_infix(&mut self, left: Node<Statement>) -> Result<Node<Statement>, ParseError> {
|
||||
let left_start = left.position.0;
|
||||
|
||||
// Postfix operations
|
||||
if let Token::Semicolon = &self.current.0 {
|
||||
self.next_token()?;
|
||||
|
||||
let right_end = self.current.1 .1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::Nil(Box::new(left)),
|
||||
(left_start, right_end),
|
||||
));
|
||||
};
|
||||
|
||||
// Infix operations
|
||||
let binary_operator = match &self.current {
|
||||
(Token::Dot, _) => {
|
||||
self.next_token()?;
|
||||
|
||||
let right = self.parse_node(0)?;
|
||||
let right_end = right.position.1;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::PropertyAccess(Box::new(left), Box::new(right)),
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
(Token::DoubleAmpersand, _) => Node::new(BinaryOperator::And, self.current.1),
|
||||
(Token::DoubleEqual, _) => Node::new(BinaryOperator::Equal, self.current.1),
|
||||
(Token::DoublePipe, _) => Node::new(BinaryOperator::Or, self.current.1),
|
||||
(Token::Equal, _) => Node::new(BinaryOperator::Assign, self.current.1),
|
||||
(Token::Greater, _) => Node::new(BinaryOperator::Greater, self.current.1),
|
||||
(Token::GreaterEqual, _) => Node::new(BinaryOperator::GreaterOrEqual, self.current.1),
|
||||
(Token::Less, _) => Node::new(BinaryOperator::Less, self.current.1),
|
||||
(Token::LessEqual, _) => Node::new(BinaryOperator::LessOrEqual, self.current.1),
|
||||
(Token::Minus, _) => Node::new(BinaryOperator::Subtract, self.current.1),
|
||||
(Token::Plus, _) => Node::new(BinaryOperator::Add, self.current.1),
|
||||
(Token::PlusEqual, _) => Node::new(BinaryOperator::AddAssign, self.current.1),
|
||||
(Token::Star, _) => Node::new(BinaryOperator::Multiply, self.current.1),
|
||||
(Token::Slash, _) => Node::new(BinaryOperator::Divide, self.current.1),
|
||||
(Token::Percent, _) => Node::new(BinaryOperator::Modulo, self.current.1),
|
||||
_ => {
|
||||
self.next_token()?;
|
||||
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let left_start = left.position.0;
|
||||
let right = self.parse_node(0)?;
|
||||
let right_end = right.position.1;
|
||||
|
||||
Ok(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left),
|
||||
operator: binary_operator,
|
||||
right: Box::new(right),
|
||||
},
|
||||
(left_start, right_end),
|
||||
))
|
||||
}
|
||||
|
||||
fn current_precedence(&self) -> u8 {
|
||||
match self.current.0 {
|
||||
Token::Semicolon => 10,
|
||||
Token::Equal | Token::PlusEqual => 8,
|
||||
Token::DoubleEqual => 7,
|
||||
Token::DoubleAmpersand => 6,
|
||||
Token::DoubleAmpersand | Token::DoublePipe => 6,
|
||||
Token::Greater | Token::GreaterEqual | Token::Less | Token::LessEqual => 5,
|
||||
Token::Dot => 4,
|
||||
Token::Percent => 3,
|
||||
@ -635,10 +484,25 @@ impl<'src> Parser<'src> {
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_token(&mut self) -> Token {
|
||||
self.lexer
|
||||
.peek_token(self.source)
|
||||
.map(|(token, _)| token)
|
||||
.unwrap_or(Token::Eof)
|
||||
}
|
||||
|
||||
fn next_is_postfix(&mut self) -> bool {
|
||||
matches!(self.peek_token(), Token::Semicolon)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ParseError {
|
||||
BooleanError {
|
||||
error: ParseBoolError,
|
||||
position: Span,
|
||||
},
|
||||
LexError {
|
||||
error: LexError,
|
||||
position: Span,
|
||||
@ -656,15 +520,26 @@ pub enum ParseError {
|
||||
actual: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
FloatError {
|
||||
error: ParseFloatError,
|
||||
position: Span,
|
||||
},
|
||||
IntegerError {
|
||||
error: ParseIntError,
|
||||
position: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn position(&self) -> Span {
|
||||
match self {
|
||||
Self::LexError { position, .. } => *position,
|
||||
Self::ExpectedIdentifier { position, .. } => *position,
|
||||
Self::ExpectedToken { position, .. } => *position,
|
||||
Self::UnexpectedToken { position, .. } => *position,
|
||||
ParseError::BooleanError { position, .. } => *position,
|
||||
ParseError::ExpectedIdentifier { position, .. } => *position,
|
||||
ParseError::ExpectedToken { position, .. } => *position,
|
||||
ParseError::FloatError { position, .. } => *position,
|
||||
ParseError::IntegerError { position, .. } => *position,
|
||||
ParseError::LexError { position, .. } => *position,
|
||||
ParseError::UnexpectedToken { position, .. } => *position,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -681,13 +556,16 @@ impl Error for ParseError {
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::LexError { error, .. } => write!(f, "{}", error),
|
||||
Self::BooleanError { error, .. } => write!(f, "{}", error),
|
||||
Self::ExpectedIdentifier { actual, .. } => {
|
||||
write!(f, "Expected identifier, found {actual}")
|
||||
}
|
||||
Self::ExpectedToken {
|
||||
expected, actual, ..
|
||||
} => write!(f, "Expected token {expected}, found {actual}"),
|
||||
Self::FloatError { error, .. } => write!(f, "{}", error),
|
||||
Self::IntegerError { error, .. } => write!(f, "{}", error),
|
||||
Self::LexError { error, .. } => write!(f, "{}", error),
|
||||
Self::UnexpectedToken { actual, .. } => write!(f, "Unexpected token {actual}"),
|
||||
}
|
||||
}
|
||||
@ -699,6 +577,55 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_assign() {
|
||||
let input = "a += 1";
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("a")),
|
||||
(0, 1)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::AddAssign, (2, 4)),
|
||||
right: Box::new(Node::new(Statement::Constant(Value::integer(1)), (5, 6))),
|
||||
},
|
||||
(0, 6)
|
||||
)]
|
||||
.into()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
let input = "true || false";
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Constant(Value::boolean(true)),
|
||||
(0, 4)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Or, (5, 7)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::boolean(false)),
|
||||
(8, 13)
|
||||
)),
|
||||
},
|
||||
(0, 13)
|
||||
)]
|
||||
.into()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn misplaced_semicolon() {
|
||||
let input = ";";
|
||||
@ -752,37 +679,49 @@ mod tests {
|
||||
Statement::Block(vec![
|
||||
Node::new(
|
||||
Statement::Nil(Box::new(Node::new(
|
||||
Statement::Assignment {
|
||||
identifier: Node::new(Identifier::new("foo"), (2, 5)),
|
||||
value_node: Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("foo")),
|
||||
(2, 5)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Assign, (6, 8)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::integer(42)),
|
||||
(8, 10)
|
||||
))
|
||||
(9, 11)
|
||||
)),
|
||||
},
|
||||
(2, 10)
|
||||
(2, 11)
|
||||
),)),
|
||||
(2, 15)
|
||||
),
|
||||
Node::new(
|
||||
Statement::Nil(Box::new(Node::new(
|
||||
Statement::Assignment {
|
||||
identifier: Node::new(Identifier::new("bar"), (12, 15)),
|
||||
value_node: Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("bar")),
|
||||
(16, 19)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Assign, (20, 22)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::integer(42)),
|
||||
(18, 20)
|
||||
))
|
||||
(23, 25)
|
||||
)),
|
||||
},
|
||||
(12, 20)
|
||||
),)),
|
||||
(12, 25)
|
||||
),
|
||||
Node::new(
|
||||
Statement::Assignment {
|
||||
identifier: Node::new(Identifier::new("baz"), (22, 25)),
|
||||
value_node: Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("baz")),
|
||||
(26, 29)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Assign, (30, 32)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::string("42")),
|
||||
(28, 32)
|
||||
))
|
||||
(22, 32)
|
||||
)),
|
||||
},
|
||||
(22, 32)
|
||||
)
|
||||
@ -816,15 +755,15 @@ mod tests {
|
||||
nodes: [Node::new(
|
||||
Statement::Map(vec![
|
||||
(
|
||||
Node::new(Identifier::new("foo"), (2, 5)),
|
||||
Node::new(Statement::Identifier(Identifier::new("foo")), (2, 5)),
|
||||
Node::new(Statement::Constant(Value::integer(42)), (8, 10))
|
||||
),
|
||||
(
|
||||
Node::new(Identifier::new("bar"), (12, 15)),
|
||||
Node::new(Statement::Identifier(Identifier::new("bar")), (12, 15)),
|
||||
Node::new(Statement::Constant(Value::integer(42)), (18, 20))
|
||||
),
|
||||
(
|
||||
Node::new(Identifier::new("baz"), (22, 25)),
|
||||
Node::new(Statement::Identifier(Identifier::new("baz")), (22, 25)),
|
||||
Node::new(Statement::Constant(Value::string("42")), (28, 32))
|
||||
),
|
||||
]),
|
||||
@ -845,11 +784,11 @@ mod tests {
|
||||
nodes: [Node::new(
|
||||
Statement::Map(vec![
|
||||
(
|
||||
Node::new(Identifier::new("x"), (2, 3)),
|
||||
Node::new(Statement::Identifier(Identifier::new("x")), (2, 3)),
|
||||
Node::new(Statement::Constant(Value::integer(42)), (6, 8))
|
||||
),
|
||||
(
|
||||
Node::new(Identifier::new("y"), (10, 11)),
|
||||
Node::new(Statement::Identifier(Identifier::new("y")), (10, 11)),
|
||||
Node::new(Statement::Constant(Value::string("foobar")), (14, 22))
|
||||
)
|
||||
]),
|
||||
@ -869,7 +808,7 @@ mod tests {
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::Map(vec![(
|
||||
Node::new(Identifier::new("x"), (2, 3)),
|
||||
Node::new(Statement::Identifier(Identifier::new("x")), (2, 3)),
|
||||
Node::new(Statement::Constant(Value::integer(42)), (6, 8))
|
||||
)]),
|
||||
(0, 11)
|
||||
@ -939,19 +878,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_assignment() {
|
||||
let input = "false = 1";
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Err(ParseError::UnexpectedToken {
|
||||
actual: TokenOwned::Equal,
|
||||
position: (6, 7)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn less_than() {
|
||||
let input = "1 < 2";
|
||||
@ -1363,9 +1289,13 @@ mod tests {
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::Assignment {
|
||||
identifier: Node::new(Identifier::new("a"), (0, 1)),
|
||||
value_node: Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("a")),
|
||||
(0, 1)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Assign, (2, 3)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Constant(Value::integer(1)),
|
||||
|
@ -6,14 +6,14 @@ use serde::{Deserialize, Serialize};
|
||||
/// Source code token.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Token<'src> {
|
||||
// End of file
|
||||
Eof,
|
||||
|
||||
Identifier(&'src str),
|
||||
|
||||
// Hard-coded values
|
||||
Boolean(bool),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
Boolean(&'src str),
|
||||
Float(&'src str),
|
||||
Identifier(&'src str),
|
||||
Integer(&'src str),
|
||||
String(&'src str),
|
||||
|
||||
// Keywords
|
||||
@ -40,6 +40,7 @@ pub enum Token<'src> {
|
||||
Minus,
|
||||
Percent,
|
||||
Plus,
|
||||
PlusEqual,
|
||||
RightCurlyBrace,
|
||||
RightParenthesis,
|
||||
RightSquareBrace,
|
||||
@ -51,7 +52,7 @@ pub enum Token<'src> {
|
||||
impl<'src> Token<'src> {
|
||||
pub fn to_owned(&self) -> TokenOwned {
|
||||
match self {
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(*boolean),
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
||||
Token::Comma => TokenOwned::Comma,
|
||||
Token::Dot => TokenOwned::Dot,
|
||||
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
|
||||
@ -59,11 +60,11 @@ impl<'src> Token<'src> {
|
||||
Token::DoublePipe => TokenOwned::DoublePipe,
|
||||
Token::Eof => TokenOwned::Eof,
|
||||
Token::Equal => TokenOwned::Equal,
|
||||
Token::Float(float) => TokenOwned::Float(*float),
|
||||
Token::Float(float) => TokenOwned::Float(float.to_string()),
|
||||
Token::Greater => TokenOwned::Greater,
|
||||
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
|
||||
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
|
||||
Token::Integer(integer) => TokenOwned::Integer(*integer),
|
||||
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
|
||||
Token::IsEven => TokenOwned::IsEven,
|
||||
Token::IsOdd => TokenOwned::IsOdd,
|
||||
Token::LeftCurlyBrace => TokenOwned::LeftCurlyBrace,
|
||||
@ -75,6 +76,7 @@ impl<'src> Token<'src> {
|
||||
Token::Minus => TokenOwned::Minus,
|
||||
Token::Percent => TokenOwned::Percent,
|
||||
Token::Plus => TokenOwned::Plus,
|
||||
Token::PlusEqual => TokenOwned::PlusEqual,
|
||||
Token::ReadLine => TokenOwned::ReadLine,
|
||||
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
|
||||
Token::RightParenthesis => TokenOwned::RightParenthesis,
|
||||
@ -87,9 +89,11 @@ impl<'src> Token<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Token::Boolean(_) => "boolean",
|
||||
Token::Boolean(boolean_text) => boolean_text,
|
||||
Token::Identifier(text) => text,
|
||||
Token::Integer(integer_text) => integer_text,
|
||||
Token::Comma => ",",
|
||||
Token::Dot => ".",
|
||||
Token::DoubleAmpersand => "&&",
|
||||
@ -100,8 +104,6 @@ impl<'src> Token<'src> {
|
||||
Token::Float(_) => "float",
|
||||
Token::Greater => ">",
|
||||
Token::GreaterEqual => ">=",
|
||||
Token::Identifier(_) => "identifier",
|
||||
Token::Integer(_) => "integer",
|
||||
Token::IsEven => "is_even",
|
||||
Token::IsOdd => "is_odd",
|
||||
Token::LeftCurlyBrace => "{",
|
||||
@ -113,6 +115,7 @@ impl<'src> Token<'src> {
|
||||
Token::Minus => "-",
|
||||
Token::Percent => "%",
|
||||
Token::Plus => "+",
|
||||
Token::PlusEqual => "+=",
|
||||
Token::ReadLine => "read_line",
|
||||
Token::RightCurlyBrace => "}",
|
||||
Token::RightParenthesis => ")",
|
||||
@ -128,17 +131,13 @@ impl<'src> Token<'src> {
|
||||
|
||||
impl<'src> Display for Token<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> PartialEq for Token<'src> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
// Floats are compared by their bit representation.
|
||||
(Token::Float(left), Token::Float(right)) => left.to_bits() == right.to_bits(),
|
||||
|
||||
// Compare all other variants normally.
|
||||
(Token::Boolean(left), Token::Boolean(right)) => left == right,
|
||||
(Token::Comma, Token::Comma) => true,
|
||||
(Token::Dot, Token::Dot) => true,
|
||||
@ -147,6 +146,7 @@ impl<'src> PartialEq for Token<'src> {
|
||||
(Token::DoublePipe, Token::DoublePipe) => true,
|
||||
(Token::Eof, Token::Eof) => true,
|
||||
(Token::Equal, Token::Equal) => true,
|
||||
(Token::Float(left), Token::Float(right)) => left == right,
|
||||
(Token::Greater, Token::Greater) => true,
|
||||
(Token::GreaterEqual, Token::GreaterEqual) => true,
|
||||
(Token::Identifier(left), Token::Identifier(right)) => left == right,
|
||||
@ -162,6 +162,7 @@ impl<'src> PartialEq for Token<'src> {
|
||||
(Token::Minus, Token::Minus) => true,
|
||||
(Token::Percent, Token::Percent) => true,
|
||||
(Token::Plus, Token::Plus) => true,
|
||||
(Token::PlusEqual, Token::PlusEqual) => true,
|
||||
(Token::ReadLine, Token::ReadLine) => true,
|
||||
(Token::RightCurlyBrace, Token::RightCurlyBrace) => true,
|
||||
(Token::RightParenthesis, Token::RightParenthesis) => true,
|
||||
@ -186,9 +187,9 @@ pub enum TokenOwned {
|
||||
Identifier(String),
|
||||
|
||||
// Hard-coded values
|
||||
Boolean(bool),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
Boolean(String),
|
||||
Float(String),
|
||||
Integer(String),
|
||||
String(String),
|
||||
|
||||
// Keywords
|
||||
@ -215,6 +216,7 @@ pub enum TokenOwned {
|
||||
Minus,
|
||||
Percent,
|
||||
Plus,
|
||||
PlusEqual,
|
||||
RightCurlyBrace,
|
||||
RightParenthesis,
|
||||
RightSquareBrace,
|
||||
@ -250,6 +252,7 @@ impl Display for TokenOwned {
|
||||
TokenOwned::Minus => Token::Minus.fmt(f),
|
||||
TokenOwned::Percent => Token::Percent.fmt(f),
|
||||
TokenOwned::Plus => Token::Plus.fmt(f),
|
||||
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
|
||||
TokenOwned::ReadLine => Token::ReadLine.fmt(f),
|
||||
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
||||
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||
|
@ -52,28 +52,74 @@ impl Vm {
|
||||
variables: &mut HashMap<Identifier, Value>,
|
||||
) -> Result<Option<Value>, VmError> {
|
||||
match node.inner {
|
||||
Statement::Assignment {
|
||||
identifier,
|
||||
value_node,
|
||||
} => {
|
||||
let value_node_position = value_node.position;
|
||||
let value = if let Some(value) = self.run_node(*value_node, variables)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue {
|
||||
position: value_node_position,
|
||||
});
|
||||
};
|
||||
|
||||
variables.insert(identifier.inner, value);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
Statement::BinaryOperation {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => {
|
||||
let right_position = right.position;
|
||||
|
||||
if let BinaryOperator::Assign = operator.inner {
|
||||
let identifier = if let Statement::Identifier(identifier) = left.inner {
|
||||
identifier
|
||||
} else {
|
||||
return Err(VmError::ExpectedIdentifier {
|
||||
position: left.position,
|
||||
});
|
||||
};
|
||||
|
||||
let value = if let Some(value) = self.run_node(*right, variables)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue {
|
||||
position: right_position,
|
||||
});
|
||||
};
|
||||
|
||||
variables.insert(identifier, value.clone());
|
||||
|
||||
return Ok(Some(value));
|
||||
}
|
||||
|
||||
if let BinaryOperator::AddAssign = operator.inner {
|
||||
let identifier = if let Statement::Identifier(identifier) = left.inner {
|
||||
identifier
|
||||
} else {
|
||||
return Err(VmError::ExpectedIdentifier {
|
||||
position: left.position,
|
||||
});
|
||||
};
|
||||
|
||||
let right_value = if let Some(value) = self.run_node(*right, variables)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue {
|
||||
position: right_position,
|
||||
});
|
||||
};
|
||||
|
||||
let left_value =
|
||||
variables
|
||||
.get(&identifier)
|
||||
.ok_or_else(|| VmError::UndefinedVariable {
|
||||
identifier: Node::new(
|
||||
Statement::Identifier(identifier.clone()),
|
||||
left.position,
|
||||
),
|
||||
})?;
|
||||
|
||||
let new_value = left_value.add(&right_value).map_err(|value_error| {
|
||||
VmError::ValueError {
|
||||
error: value_error,
|
||||
position: right_position,
|
||||
}
|
||||
})?;
|
||||
|
||||
variables.insert(identifier, new_value.clone());
|
||||
|
||||
return Ok(Some(new_value));
|
||||
}
|
||||
|
||||
let left_position = left.position;
|
||||
let left_value = if let Some(value) = self.run_node(*left, variables)? {
|
||||
value
|
||||
@ -83,7 +129,6 @@ impl Vm {
|
||||
});
|
||||
};
|
||||
|
||||
let right_position = right.position;
|
||||
let right_value = if let Some(value) = self.run_node(*right, variables)? {
|
||||
value
|
||||
} else {
|
||||
@ -92,7 +137,7 @@ impl Vm {
|
||||
});
|
||||
};
|
||||
|
||||
let result = match operator.inner {
|
||||
match operator.inner {
|
||||
BinaryOperator::Add => left_value.add(&right_value),
|
||||
BinaryOperator::And => left_value.and(&right_value),
|
||||
BinaryOperator::Divide => left_value.divide(&right_value),
|
||||
@ -107,13 +152,13 @@ impl Vm {
|
||||
BinaryOperator::Multiply => left_value.multiply(&right_value),
|
||||
BinaryOperator::Or => left_value.or(&right_value),
|
||||
BinaryOperator::Subtract => left_value.subtract(&right_value),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.map(Some)
|
||||
.map_err(|value_error| VmError::ValueError {
|
||||
error: value_error,
|
||||
position: node.position,
|
||||
})?;
|
||||
|
||||
Ok(Some(result))
|
||||
})
|
||||
}
|
||||
Statement::Block(statements) => {
|
||||
let mut previous_value = None;
|
||||
@ -206,9 +251,8 @@ impl Vm {
|
||||
if let Some(value) = variables.get(&identifier) {
|
||||
Ok(Some(value.clone()))
|
||||
} else {
|
||||
Err(VmError::UndefinedIdentifier {
|
||||
identifier,
|
||||
position: node.position,
|
||||
Err(VmError::UndefinedVariable {
|
||||
identifier: Node::new(Statement::Identifier(identifier), node.position),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -231,6 +275,13 @@ impl Vm {
|
||||
let mut values = BTreeMap::new();
|
||||
|
||||
for (identifier, value_node) in nodes {
|
||||
let identifier = if let Statement::Identifier(identifier) = identifier.inner {
|
||||
identifier
|
||||
} else {
|
||||
return Err(VmError::ExpectedIdentifier {
|
||||
position: identifier.position,
|
||||
});
|
||||
};
|
||||
let position = value_node.position;
|
||||
let value = if let Some(value) = self.run_node(value_node, variables)? {
|
||||
value
|
||||
@ -238,7 +289,7 @@ impl Vm {
|
||||
return Err(VmError::ExpectedValue { position });
|
||||
};
|
||||
|
||||
values.insert(identifier.inner, value);
|
||||
values.insert(identifier, value);
|
||||
}
|
||||
|
||||
Ok(Some(Value::map(values)))
|
||||
@ -347,9 +398,8 @@ pub enum VmError {
|
||||
ExpectedValue {
|
||||
position: Span,
|
||||
},
|
||||
UndefinedIdentifier {
|
||||
identifier: Identifier,
|
||||
position: Span,
|
||||
UndefinedVariable {
|
||||
identifier: Node<Statement>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -366,7 +416,7 @@ impl VmError {
|
||||
Self::ExpectedFunction { position, .. } => *position,
|
||||
Self::ExpectedList { position } => *position,
|
||||
Self::ExpectedValue { position } => *position,
|
||||
Self::UndefinedIdentifier { position, .. } => *position,
|
||||
Self::UndefinedVariable { identifier } => identifier.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -430,15 +480,8 @@ impl Display for VmError {
|
||||
Self::ExpectedValue { position } => {
|
||||
write!(f, "Expected a value at position: {:?}", position)
|
||||
}
|
||||
Self::UndefinedIdentifier {
|
||||
identifier,
|
||||
position,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Undefined identifier: {} at position: {:?}",
|
||||
identifier, position
|
||||
)
|
||||
Self::UndefinedVariable { identifier } => {
|
||||
write!(f, "Undefined identifier: {}", identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -448,6 +491,23 @@ impl Display for VmError {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_assign() {
|
||||
let input = "x = 1; x += 1; x";
|
||||
|
||||
assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(2))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
let input = "true || false";
|
||||
|
||||
assert_eq!(
|
||||
run(input, &mut HashMap::new()),
|
||||
Ok(Some(Value::boolean(true)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_equal() {
|
||||
let input = "{ y = 'foo', } == { y = 'foo', }";
|
||||
|
Loading…
Reference in New Issue
Block a user