Compare commits
No commits in common. "ed82f3c64f8c6168b7eede44dcd86d7efb41a796" and "24a2642f17ef77b64e6f6d2c7446e4c1e51c2785" have entirely different histories.
ed82f3c64f
...
24a2642f17
@ -34,15 +34,12 @@ impl<T: Display> Display for Node<T> {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
// Variable assignment
|
// Top-level statements
|
||||||
Assignment {
|
Assignment {
|
||||||
identifier: Node<Identifier>,
|
identifier: Node<Identifier>,
|
||||||
value_node: Box<Node<Statement>>,
|
value_node: Box<Node<Statement>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
// A sequence of statements
|
|
||||||
Block(Vec<Node<Statement>>),
|
|
||||||
|
|
||||||
// Logic, math and comparison expressions
|
// Logic, math and comparison expressions
|
||||||
BinaryOperation {
|
BinaryOperation {
|
||||||
left: Box<Node<Statement>>,
|
left: Box<Node<Statement>>,
|
||||||
@ -72,19 +69,14 @@ pub enum Statement {
|
|||||||
List(Vec<Node<Statement>>),
|
List(Vec<Node<Statement>>),
|
||||||
Map(Vec<(Node<Identifier>, Node<Statement>)>),
|
Map(Vec<(Node<Identifier>, Node<Statement>)>),
|
||||||
|
|
||||||
// Hard-coded value
|
// Hard-coded values
|
||||||
Constant(Value),
|
Constant(Value),
|
||||||
|
|
||||||
// A statement that always returns None. Created with a semicolon, it causes the preceding
|
|
||||||
// statement to return None. This is analagous to the semicolon or unit type in Rust.
|
|
||||||
Nil(Box<Node<Statement>>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Statement {
|
impl Statement {
|
||||||
pub fn expected_type(&self, variables: &HashMap<Identifier, Value>) -> Option<Type> {
|
pub fn expected_type(&self, variables: &HashMap<Identifier, Value>) -> Option<Type> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Assignment { .. } => None,
|
Statement::Assignment { .. } => None,
|
||||||
Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(variables),
|
|
||||||
Statement::BinaryOperation { left, .. } => left.inner.expected_type(variables),
|
Statement::BinaryOperation { left, .. } => left.inner.expected_type(variables),
|
||||||
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
||||||
Statement::Constant(value) => Some(value.r#type(variables)),
|
Statement::Constant(value) => Some(value.r#type(variables)),
|
||||||
@ -113,7 +105,6 @@ impl Statement {
|
|||||||
Some(Type::Map(types))
|
Some(Type::Map(types))
|
||||||
}
|
}
|
||||||
Statement::PropertyAccess(_, _) => None,
|
Statement::PropertyAccess(_, _) => None,
|
||||||
Statement::Nil(_) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,19 +118,6 @@ impl Display for Statement {
|
|||||||
} => {
|
} => {
|
||||||
write!(f, "{identifier} = {value}")
|
write!(f, "{identifier} = {value}")
|
||||||
}
|
}
|
||||||
Statement::Block(statements) => {
|
|
||||||
write!(f, "{{ ")?;
|
|
||||||
|
|
||||||
for (i, statement) in statements.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
write!(f, " ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{statement}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, " }}")
|
|
||||||
}
|
|
||||||
Statement::BinaryOperation {
|
Statement::BinaryOperation {
|
||||||
left,
|
left,
|
||||||
operator,
|
operator,
|
||||||
@ -245,7 +223,6 @@ impl Display for Statement {
|
|||||||
|
|
||||||
write!(f, "}}")
|
write!(f, "}}")
|
||||||
}
|
}
|
||||||
Statement::Nil(node) => write!(f, "{node};"),
|
|
||||||
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
|
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,7 +238,6 @@ pub enum BinaryOperator {
|
|||||||
Subtract,
|
Subtract,
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
Equal,
|
|
||||||
Greater,
|
Greater,
|
||||||
GreaterOrEqual,
|
GreaterOrEqual,
|
||||||
Less,
|
Less,
|
||||||
@ -278,7 +254,6 @@ impl Display for BinaryOperator {
|
|||||||
BinaryOperator::Add => write!(f, "+"),
|
BinaryOperator::Add => write!(f, "+"),
|
||||||
BinaryOperator::And => write!(f, "&&"),
|
BinaryOperator::And => write!(f, "&&"),
|
||||||
BinaryOperator::Divide => write!(f, "/"),
|
BinaryOperator::Divide => write!(f, "/"),
|
||||||
BinaryOperator::Equal => write!(f, "=="),
|
|
||||||
BinaryOperator::Greater => write!(f, ">"),
|
BinaryOperator::Greater => write!(f, ">"),
|
||||||
BinaryOperator::GreaterOrEqual => write!(f, ">="),
|
BinaryOperator::GreaterOrEqual => write!(f, ">="),
|
||||||
BinaryOperator::Less => write!(f, "<"),
|
BinaryOperator::Less => write!(f, "<"),
|
||||||
|
@ -140,11 +140,6 @@ impl<'a> Analyzer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::Block(statements) => {
|
|
||||||
for statement in statements {
|
|
||||||
self.analyze_node(statement)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Statement::BuiltInFunctionCall { .. } => {}
|
Statement::BuiltInFunctionCall { .. } => {}
|
||||||
Statement::Constant(_) => {}
|
Statement::Constant(_) => {}
|
||||||
Statement::FunctionCall { function, .. } => {
|
Statement::FunctionCall { function, .. } => {
|
||||||
@ -199,9 +194,6 @@ impl<'a> Analyzer<'a> {
|
|||||||
|
|
||||||
self.analyze_node(right)?;
|
self.analyze_node(right)?;
|
||||||
}
|
}
|
||||||
Statement::Nil(node) => {
|
|
||||||
self.analyze_node(node)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,17 +1,4 @@
|
|||||||
//! Key used to identify a value or type.
|
//! Key used to identify a value or type.
|
||||||
//!
|
|
||||||
//! Identifiers are used to uniquely identify values and types in Dust programs. They are
|
|
||||||
//! cached to avoid duplication. This means that two identifiers with the same text are the same
|
|
||||||
//! object in memory.
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//! ```
|
|
||||||
//! # use dust_lang::Identifier;
|
|
||||||
//! let foo = Identifier::new("foo");
|
|
||||||
//! let also_foo = Identifier::new("foo");
|
|
||||||
//!
|
|
||||||
//! assert_eq!(foo.hard_count(), 2);
|
|
||||||
//! ```
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
@ -21,24 +8,20 @@ use std::{
|
|||||||
|
|
||||||
use serde::{de::Visitor, Deserialize, Serialize};
|
use serde::{de::Visitor, Deserialize, Serialize};
|
||||||
|
|
||||||
/// In-use identifiers.
|
|
||||||
static IDENTIFIER_CACHE: OnceLock<RwLock<HashSet<Identifier>>> = OnceLock::new();
|
static IDENTIFIER_CACHE: OnceLock<RwLock<HashSet<Identifier>>> = OnceLock::new();
|
||||||
|
|
||||||
/// Returns the identifier cache.
|
|
||||||
fn identifier_cache<'a>() -> &'a RwLock<HashSet<Identifier>> {
|
fn identifier_cache<'a>() -> &'a RwLock<HashSet<Identifier>> {
|
||||||
IDENTIFIER_CACHE.get_or_init(|| RwLock::new(HashSet::new()))
|
IDENTIFIER_CACHE.get_or_init(|| RwLock::new(HashSet::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Key used to identify a value or type.
|
/// Key used to identify a value or type.
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Identifier(Arc<String>);
|
pub struct Identifier(Arc<String>);
|
||||||
|
|
||||||
impl Identifier {
|
impl Identifier {
|
||||||
/// Creates a new identifier or returns a clone of an existing one from a cache.
|
|
||||||
pub fn new<T: ToString>(text: T) -> Self {
|
pub fn new<T: ToString>(text: T) -> Self {
|
||||||
let cache = identifier_cache().read().unwrap();
|
let cache = identifier_cache().read().unwrap();
|
||||||
|
|
||||||
let new = Identifier(Arc::new(text.to_string()));
|
let new = Identifier(Arc::new(text.to_string()));
|
||||||
|
|
||||||
if cache.contains(&new) {
|
if cache.contains(&new) {
|
||||||
@ -55,10 +38,6 @@ impl Identifier {
|
|||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hard_count(&self) -> usize {
|
|
||||||
Arc::strong_count(&self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Identifier {
|
impl From<&str> for Identifier {
|
||||||
|
@ -143,15 +143,9 @@ impl Lexer {
|
|||||||
(Token::RightParenthesis, (self.position - 1, self.position))
|
(Token::RightParenthesis, (self.position - 1, self.position))
|
||||||
}
|
}
|
||||||
'=' => {
|
'=' => {
|
||||||
if let Some('=') = self.peek_second_char(source) {
|
self.position += 1;
|
||||||
self.position += 2;
|
|
||||||
|
|
||||||
(Token::DoubleEqual, (self.position - 2, self.position))
|
(Token::Equal, (self.position - 1, self.position))
|
||||||
} else {
|
|
||||||
self.position += 1;
|
|
||||||
|
|
||||||
(Token::Equal, (self.position - 1, self.position))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
'[' => {
|
'[' => {
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
@ -215,22 +209,6 @@ impl Lexer {
|
|||||||
|
|
||||||
(Token::Percent, (self.position - 1, self.position))
|
(Token::Percent, (self.position - 1, self.position))
|
||||||
}
|
}
|
||||||
'&' => {
|
|
||||||
if let Some('&') = self.peek_second_char(source) {
|
|
||||||
self.position += 2;
|
|
||||||
|
|
||||||
(Token::DoubleAmpersand, (self.position - 2, self.position))
|
|
||||||
} else {
|
|
||||||
self.position += 1;
|
|
||||||
|
|
||||||
return Err(LexError::UnexpectedCharacter(c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
';' => {
|
|
||||||
self.position += 1;
|
|
||||||
|
|
||||||
(Token::Semicolon, (self.position - 1, self.position))
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
|
|
||||||
@ -457,42 +435,6 @@ impl From<ParseIntError> for LexError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block() {
|
|
||||||
let input = "{ x = 42; y = 'foobar' }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
lex(input),
|
|
||||||
Ok(vec![
|
|
||||||
(Token::LeftCurlyBrace, (0, 1)),
|
|
||||||
(Token::Identifier("x"), (2, 3)),
|
|
||||||
(Token::Equal, (4, 5)),
|
|
||||||
(Token::Integer(42), (6, 8)),
|
|
||||||
(Token::Semicolon, (8, 9)),
|
|
||||||
(Token::Identifier("y"), (10, 11)),
|
|
||||||
(Token::Equal, (12, 13)),
|
|
||||||
(Token::String("foobar"), (14, 22)),
|
|
||||||
(Token::RightCurlyBrace, (23, 24)),
|
|
||||||
(Token::Eof, (24, 24)),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn equal() {
|
|
||||||
let input = "42 == 42";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
lex(input),
|
|
||||||
Ok(vec![
|
|
||||||
(Token::Integer(42), (0, 2)),
|
|
||||||
(Token::DoubleEqual, (3, 5)),
|
|
||||||
(Token::Integer(42), (6, 8)),
|
|
||||||
(Token::Eof, (8, 8)),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn modulo() {
|
fn modulo() {
|
||||||
let input = "42 % 2";
|
let input = "42 % 2";
|
||||||
|
@ -166,40 +166,6 @@ impl<'src> Parser<'src> {
|
|||||||
(left_start, right_end),
|
(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, _) => {
|
(Token::Greater, _) => {
|
||||||
let operator = Node::new(BinaryOperator::Greater, self.current.1);
|
let operator = Node::new(BinaryOperator::Greater, self.current.1);
|
||||||
|
|
||||||
@ -302,12 +268,6 @@ impl<'src> Parser<'src> {
|
|||||||
(left_start, right_end),
|
(left_start, right_end),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
(Token::Semicolon, (_, right_end)) => {
|
|
||||||
return Ok(Node::new(
|
|
||||||
Statement::Nil(Box::new(left_node)),
|
|
||||||
(left_start, *right_end),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
(Token::Star, _) => {
|
(Token::Star, _) => {
|
||||||
let operator = Node::new(BinaryOperator::Multiply, self.current.1);
|
let operator = Node::new(BinaryOperator::Multiply, self.current.1);
|
||||||
|
|
||||||
@ -392,13 +352,13 @@ impl<'src> Parser<'src> {
|
|||||||
if let (Token::Equal, _) = self.current {
|
if let (Token::Equal, _) = self.current {
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
|
|
||||||
let value_node = self.parse_node(0)?;
|
let value = self.parse_node(0)?;
|
||||||
let right_end = value_node.position.1;
|
let right_end = value.position.1;
|
||||||
|
|
||||||
Ok(Node::new(
|
Ok(Node::new(
|
||||||
Statement::Assignment {
|
Statement::Assignment {
|
||||||
identifier: Node::new(Identifier::new(text), span),
|
identifier: Node::new(Identifier::new(text), span),
|
||||||
value_node: Box::new(value_node),
|
value_node: Box::new(value),
|
||||||
},
|
},
|
||||||
(span.0, right_end),
|
(span.0, right_end),
|
||||||
))
|
))
|
||||||
@ -417,89 +377,45 @@ impl<'src> Parser<'src> {
|
|||||||
(Token::LeftCurlyBrace, left_span) => {
|
(Token::LeftCurlyBrace, left_span) => {
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
|
|
||||||
// If the next token is a right curly brace, this is an empty map
|
let mut nodes = Vec::new();
|
||||||
if let (Token::RightCurlyBrace, right_span) = self.current {
|
|
||||||
self.next_token()?;
|
|
||||||
|
|
||||||
return Ok(Node::new(
|
|
||||||
Statement::Map(Vec::new()),
|
|
||||||
(left_span.0, right_span.1),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut statement = None;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// If a closing brace is found, return the new statement
|
|
||||||
if let (Token::RightCurlyBrace, right_span) = self.current {
|
if let (Token::RightCurlyBrace, right_span) = self.current {
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
|
|
||||||
return Ok(Node::new(statement.unwrap(), (left_span.0, right_span.1)));
|
return Ok(Node::new(
|
||||||
|
Statement::Map(nodes),
|
||||||
|
(left_span.0, right_span.1),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_node = self.parse_node(0)?;
|
let identifier = if let (Token::Identifier(text), right_span) = self.current {
|
||||||
|
self.next_token()?;
|
||||||
|
|
||||||
// If the next node is an assignment, this might be a map
|
Node::new(Identifier::new(text), right_span)
|
||||||
if let Statement::Assignment {
|
} else {
|
||||||
identifier,
|
return Err(ParseError::ExpectedIdentifier {
|
||||||
value_node,
|
actual: self.current.0.to_owned(),
|
||||||
} = next_node.inner
|
position: self.current.1,
|
||||||
{
|
});
|
||||||
// If the current token is a comma, right curly brace, or the new
|
};
|
||||||
// statement is already a map
|
|
||||||
if self.current.0 == Token::Comma
|
|
||||||
|| statement
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|statement| matches!(statement, Statement::Map(_)))
|
|
||||||
{
|
|
||||||
// The new statement is a map
|
|
||||||
if let Statement::Map(map_properties) =
|
|
||||||
statement.get_or_insert_with(|| Statement::Map(Vec::new()))
|
|
||||||
{
|
|
||||||
// Ignore commas after properties
|
|
||||||
if let Token::Comma = self.current.0 {
|
|
||||||
self.next_token()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the new property to the map
|
if let Token::Equal = self.current.0 {
|
||||||
map_properties.push((identifier, *value_node));
|
self.next_token()?;
|
||||||
}
|
} else {
|
||||||
// Otherwise, the new statement is a block
|
return Err(ParseError::ExpectedToken {
|
||||||
} else if let Statement::Block(statements) =
|
expected: TokenOwned::Equal,
|
||||||
statement.get_or_insert_with(|| Statement::Block(Vec::new()))
|
actual: self.current.0.to_owned(),
|
||||||
{
|
position: self.current.1,
|
||||||
if self.current.0 == Token::Semicolon {
|
});
|
||||||
self.next_token()?;
|
}
|
||||||
|
|
||||||
statements.push(Node::new(
|
let current_value_node = self.parse_node(0)?;
|
||||||
Statement::Nil(Box::new(Node::new(
|
|
||||||
Statement::Assignment {
|
|
||||||
identifier,
|
|
||||||
value_node,
|
|
||||||
},
|
|
||||||
next_node.position,
|
|
||||||
))),
|
|
||||||
(next_node.position.0, self.current.1 .1),
|
|
||||||
));
|
|
||||||
|
|
||||||
continue;
|
nodes.push((identifier, current_value_node));
|
||||||
} else {
|
|
||||||
statements.push(Node::new(
|
|
||||||
Statement::Assignment {
|
|
||||||
identifier,
|
|
||||||
value_node,
|
|
||||||
},
|
|
||||||
next_node.position,
|
|
||||||
));
|
|
||||||
|
|
||||||
continue;
|
if let Token::Comma = self.current.0 {
|
||||||
}
|
self.next_token()?;
|
||||||
}
|
|
||||||
} else if let Statement::Block(statements) =
|
|
||||||
statement.get_or_insert_with(|| Statement::Block(Vec::new()))
|
|
||||||
{
|
|
||||||
// Add the assignment statement to the block
|
|
||||||
statements.push(next_node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -623,8 +539,6 @@ impl<'src> Parser<'src> {
|
|||||||
|
|
||||||
fn current_precedence(&self) -> u8 {
|
fn current_precedence(&self) -> u8 {
|
||||||
match self.current.0 {
|
match self.current.0 {
|
||||||
Token::DoubleEqual => 7,
|
|
||||||
Token::DoubleAmpersand => 6,
|
|
||||||
Token::Greater | Token::GreaterEqual | Token::Less | Token::LessEqual => 5,
|
Token::Greater | Token::GreaterEqual | Token::Less | Token::LessEqual => 5,
|
||||||
Token::Dot => 4,
|
Token::Dot => 4,
|
||||||
Token::Percent => 3,
|
Token::Percent => 3,
|
||||||
@ -645,7 +559,7 @@ pub enum ParseError {
|
|||||||
},
|
},
|
||||||
ExpectedIdentifier {
|
ExpectedIdentifier {
|
||||||
actual: TokenOwned,
|
actual: TokenOwned,
|
||||||
position: Span,
|
position: (usize, usize),
|
||||||
},
|
},
|
||||||
ExpectedToken {
|
ExpectedToken {
|
||||||
expected: TokenOwned,
|
expected: TokenOwned,
|
||||||
@ -699,206 +613,6 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn misplaced_semicolon() {
|
|
||||||
let input = ";";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Err(ParseError::UnexpectedToken {
|
|
||||||
actual: TokenOwned::Semicolon,
|
|
||||||
position: (0, 1)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_with_one_statement() {
|
|
||||||
let input = "{ 40 + 2 }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(
|
|
||||||
Statement::Block(vec![Node::new(
|
|
||||||
Statement::BinaryOperation {
|
|
||||||
left: Box::new(Node::new(
|
|
||||||
Statement::Constant(Value::integer(40)),
|
|
||||||
(2, 4)
|
|
||||||
)),
|
|
||||||
operator: Node::new(BinaryOperator::Add, (5, 6)),
|
|
||||||
right: Box::new(Node::new(
|
|
||||||
Statement::Constant(Value::integer(2)),
|
|
||||||
(7, 8)
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
(2, 8)
|
|
||||||
)]),
|
|
||||||
(0, 10)
|
|
||||||
)]
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_with_assignment() {
|
|
||||||
let input = "{ foo = 42; bar = 42; baz = '42' }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(
|
|
||||||
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::Constant(Value::integer(42)),
|
|
||||||
(8, 10)
|
|
||||||
))
|
|
||||||
},
|
|
||||||
(2, 10)
|
|
||||||
),)),
|
|
||||||
(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::Constant(Value::integer(42)),
|
|
||||||
(18, 20)
|
|
||||||
))
|
|
||||||
},
|
|
||||||
(12, 20)
|
|
||||||
),)),
|
|
||||||
(12, 25)
|
|
||||||
),
|
|
||||||
Node::new(
|
|
||||||
Statement::Assignment {
|
|
||||||
identifier: Node::new(Identifier::new("baz"), (22, 25)),
|
|
||||||
value_node: Box::new(Node::new(
|
|
||||||
Statement::Constant(Value::string("42")),
|
|
||||||
(28, 32)
|
|
||||||
))
|
|
||||||
},
|
|
||||||
(22, 32)
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
(0, 34)
|
|
||||||
)]
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_map() {
|
|
||||||
let input = "{}";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(Statement::Map(vec![]), (0, 2))].into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn map_with_trailing_comma() {
|
|
||||||
let input = "{ foo = 42, bar = 42, baz = '42', }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(
|
|
||||||
Statement::Map(vec![
|
|
||||||
(
|
|
||||||
Node::new(Identifier::new("foo"), (2, 5)),
|
|
||||||
Node::new(Statement::Constant(Value::integer(42)), (8, 10))
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Node::new(Identifier::new("bar"), (12, 15)),
|
|
||||||
Node::new(Statement::Constant(Value::integer(42)), (18, 20))
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Node::new(Identifier::new("baz"), (22, 25)),
|
|
||||||
Node::new(Statement::Constant(Value::string("42")), (28, 32))
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
(0, 35)
|
|
||||||
)]
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn map_with_two_properties() {
|
|
||||||
let input = "{ x = 42, y = 'foobar' }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(
|
|
||||||
Statement::Map(vec![
|
|
||||||
(
|
|
||||||
Node::new(Identifier::new("x"), (2, 3)),
|
|
||||||
Node::new(Statement::Constant(Value::integer(42)), (6, 8))
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Node::new(Identifier::new("y"), (10, 11)),
|
|
||||||
Node::new(Statement::Constant(Value::string("foobar")), (14, 22))
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
(0, 24)
|
|
||||||
)]
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn map_with_one_property() {
|
|
||||||
let input = "{ x = 42, }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(
|
|
||||||
Statement::Map(vec![(
|
|
||||||
Node::new(Identifier::new("x"), (2, 3)),
|
|
||||||
Node::new(Statement::Constant(Value::integer(42)), (6, 8))
|
|
||||||
)]),
|
|
||||||
(0, 11)
|
|
||||||
)]
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn equal() {
|
|
||||||
let input = "42 == 42";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse(input),
|
|
||||||
Ok(AbstractSyntaxTree {
|
|
||||||
nodes: [Node::new(
|
|
||||||
Statement::BinaryOperation {
|
|
||||||
left: Box::new(Node::new(Statement::Constant(Value::integer(42)), (0, 2))),
|
|
||||||
operator: Node::new(BinaryOperator::Equal, (3, 5)),
|
|
||||||
right: Box::new(Node::new(Statement::Constant(Value::integer(42)), (6, 8)))
|
|
||||||
},
|
|
||||||
(0, 8)
|
|
||||||
)]
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn modulo() {
|
fn modulo() {
|
||||||
let input = "42 % 2";
|
let input = "42 % 2";
|
||||||
@ -952,6 +666,31 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map() {
|
||||||
|
let input = "{ x = 42, y = 'foobar' }";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse(input),
|
||||||
|
Ok(AbstractSyntaxTree {
|
||||||
|
nodes: [Node::new(
|
||||||
|
Statement::Map(vec![
|
||||||
|
(
|
||||||
|
Node::new(Identifier::new("x"), (2, 3)),
|
||||||
|
Node::new(Statement::Constant(Value::integer(42)), (6, 8))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Node::new(Identifier::new("y"), (10, 11)),
|
||||||
|
Node::new(Statement::Constant(Value::string("foobar")), (14, 22))
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
(0, 24)
|
||||||
|
)]
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn less_than() {
|
fn less_than() {
|
||||||
let input = "1 < 2";
|
let input = "1 < 2";
|
||||||
|
@ -27,7 +27,6 @@ pub enum Token<'src> {
|
|||||||
Comma,
|
Comma,
|
||||||
Dot,
|
Dot,
|
||||||
DoubleAmpersand,
|
DoubleAmpersand,
|
||||||
DoubleEqual,
|
|
||||||
DoublePipe,
|
DoublePipe,
|
||||||
Equal,
|
Equal,
|
||||||
Greater,
|
Greater,
|
||||||
@ -43,7 +42,6 @@ pub enum Token<'src> {
|
|||||||
RightCurlyBrace,
|
RightCurlyBrace,
|
||||||
RightParenthesis,
|
RightParenthesis,
|
||||||
RightSquareBrace,
|
RightSquareBrace,
|
||||||
Semicolon,
|
|
||||||
Slash,
|
Slash,
|
||||||
Star,
|
Star,
|
||||||
}
|
}
|
||||||
@ -55,7 +53,6 @@ impl<'src> Token<'src> {
|
|||||||
Token::Comma => TokenOwned::Comma,
|
Token::Comma => TokenOwned::Comma,
|
||||||
Token::Dot => TokenOwned::Dot,
|
Token::Dot => TokenOwned::Dot,
|
||||||
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
|
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
|
||||||
Token::DoubleEqual => TokenOwned::DoubleEqual,
|
|
||||||
Token::DoublePipe => TokenOwned::DoublePipe,
|
Token::DoublePipe => TokenOwned::DoublePipe,
|
||||||
Token::Eof => TokenOwned::Eof,
|
Token::Eof => TokenOwned::Eof,
|
||||||
Token::Equal => TokenOwned::Equal,
|
Token::Equal => TokenOwned::Equal,
|
||||||
@ -79,7 +76,6 @@ impl<'src> Token<'src> {
|
|||||||
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
|
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
|
||||||
Token::RightParenthesis => TokenOwned::RightParenthesis,
|
Token::RightParenthesis => TokenOwned::RightParenthesis,
|
||||||
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
|
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
|
||||||
Token::Semicolon => TokenOwned::Semicolon,
|
|
||||||
Token::Star => TokenOwned::Star,
|
Token::Star => TokenOwned::Star,
|
||||||
Token::Slash => TokenOwned::Slash,
|
Token::Slash => TokenOwned::Slash,
|
||||||
Token::String(text) => TokenOwned::String(text.to_string()),
|
Token::String(text) => TokenOwned::String(text.to_string()),
|
||||||
@ -93,7 +89,6 @@ impl<'src> Token<'src> {
|
|||||||
Token::Comma => ",",
|
Token::Comma => ",",
|
||||||
Token::Dot => ".",
|
Token::Dot => ".",
|
||||||
Token::DoubleAmpersand => "&&",
|
Token::DoubleAmpersand => "&&",
|
||||||
Token::DoubleEqual => "==",
|
|
||||||
Token::DoublePipe => "||",
|
Token::DoublePipe => "||",
|
||||||
Token::Eof => "EOF",
|
Token::Eof => "EOF",
|
||||||
Token::Equal => "=",
|
Token::Equal => "=",
|
||||||
@ -117,7 +112,6 @@ impl<'src> Token<'src> {
|
|||||||
Token::RightCurlyBrace => "}",
|
Token::RightCurlyBrace => "}",
|
||||||
Token::RightParenthesis => ")",
|
Token::RightParenthesis => ")",
|
||||||
Token::RightSquareBrace => "]",
|
Token::RightSquareBrace => "]",
|
||||||
Token::Semicolon => ";",
|
|
||||||
Token::Star => "*",
|
Token::Star => "*",
|
||||||
Token::String(_) => "string",
|
Token::String(_) => "string",
|
||||||
Token::Slash => "/",
|
Token::Slash => "/",
|
||||||
@ -142,9 +136,6 @@ impl<'src> PartialEq for Token<'src> {
|
|||||||
(Token::Boolean(left), Token::Boolean(right)) => left == right,
|
(Token::Boolean(left), Token::Boolean(right)) => left == right,
|
||||||
(Token::Comma, Token::Comma) => true,
|
(Token::Comma, Token::Comma) => true,
|
||||||
(Token::Dot, Token::Dot) => true,
|
(Token::Dot, Token::Dot) => true,
|
||||||
(Token::DoubleAmpersand, Token::DoubleAmpersand) => true,
|
|
||||||
(Token::DoubleEqual, Token::DoubleEqual) => true,
|
|
||||||
(Token::DoublePipe, Token::DoublePipe) => true,
|
|
||||||
(Token::Eof, Token::Eof) => true,
|
(Token::Eof, Token::Eof) => true,
|
||||||
(Token::Equal, Token::Equal) => true,
|
(Token::Equal, Token::Equal) => true,
|
||||||
(Token::Greater, Token::Greater) => true,
|
(Token::Greater, Token::Greater) => true,
|
||||||
@ -166,7 +157,6 @@ impl<'src> PartialEq for Token<'src> {
|
|||||||
(Token::RightCurlyBrace, Token::RightCurlyBrace) => true,
|
(Token::RightCurlyBrace, Token::RightCurlyBrace) => true,
|
||||||
(Token::RightParenthesis, Token::RightParenthesis) => true,
|
(Token::RightParenthesis, Token::RightParenthesis) => true,
|
||||||
(Token::RightSquareBrace, Token::RightSquareBrace) => true,
|
(Token::RightSquareBrace, Token::RightSquareBrace) => true,
|
||||||
(Token::Semicolon, Token::Semicolon) => true,
|
|
||||||
(Token::Star, Token::Star) => true,
|
(Token::Star, Token::Star) => true,
|
||||||
(Token::Slash, Token::Slash) => true,
|
(Token::Slash, Token::Slash) => true,
|
||||||
(Token::String(left), Token::String(right)) => left == right,
|
(Token::String(left), Token::String(right)) => left == right,
|
||||||
@ -202,7 +192,6 @@ pub enum TokenOwned {
|
|||||||
Comma,
|
Comma,
|
||||||
Dot,
|
Dot,
|
||||||
DoubleAmpersand,
|
DoubleAmpersand,
|
||||||
DoubleEqual,
|
|
||||||
DoublePipe,
|
DoublePipe,
|
||||||
Equal,
|
Equal,
|
||||||
Greater,
|
Greater,
|
||||||
@ -218,7 +207,6 @@ pub enum TokenOwned {
|
|||||||
RightCurlyBrace,
|
RightCurlyBrace,
|
||||||
RightParenthesis,
|
RightParenthesis,
|
||||||
RightSquareBrace,
|
RightSquareBrace,
|
||||||
Semicolon,
|
|
||||||
Star,
|
Star,
|
||||||
Slash,
|
Slash,
|
||||||
}
|
}
|
||||||
@ -230,7 +218,6 @@ impl Display for TokenOwned {
|
|||||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
TokenOwned::Comma => Token::Comma.fmt(f),
|
||||||
TokenOwned::Dot => Token::Dot.fmt(f),
|
TokenOwned::Dot => Token::Dot.fmt(f),
|
||||||
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
||||||
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
|
|
||||||
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
|
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
|
||||||
TokenOwned::Eof => Token::Eof.fmt(f),
|
TokenOwned::Eof => Token::Eof.fmt(f),
|
||||||
TokenOwned::Equal => Token::Equal.fmt(f),
|
TokenOwned::Equal => Token::Equal.fmt(f),
|
||||||
@ -254,7 +241,6 @@ impl Display for TokenOwned {
|
|||||||
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
||||||
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
|
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||||
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
||||||
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
|
|
||||||
TokenOwned::Star => Token::Star.fmt(f),
|
TokenOwned::Star => Token::Star.fmt(f),
|
||||||
TokenOwned::Slash => Token::Slash.fmt(f),
|
TokenOwned::Slash => Token::Slash.fmt(f),
|
||||||
TokenOwned::String(string) => write!(f, "{string}"),
|
TokenOwned::String(string) => write!(f, "{string}"),
|
||||||
|
@ -96,7 +96,6 @@ impl Vm {
|
|||||||
BinaryOperator::Add => left_value.add(&right_value),
|
BinaryOperator::Add => left_value.add(&right_value),
|
||||||
BinaryOperator::And => left_value.and(&right_value),
|
BinaryOperator::And => left_value.and(&right_value),
|
||||||
BinaryOperator::Divide => left_value.divide(&right_value),
|
BinaryOperator::Divide => left_value.divide(&right_value),
|
||||||
BinaryOperator::Equal => Ok(Value::boolean(left_value == right_value)),
|
|
||||||
BinaryOperator::Greater => left_value.greater_than(&right_value),
|
BinaryOperator::Greater => left_value.greater_than(&right_value),
|
||||||
BinaryOperator::GreaterOrEqual => {
|
BinaryOperator::GreaterOrEqual => {
|
||||||
left_value.greater_than_or_equal(&right_value)
|
left_value.greater_than_or_equal(&right_value)
|
||||||
@ -115,15 +114,6 @@ impl Vm {
|
|||||||
|
|
||||||
Ok(Some(result))
|
Ok(Some(result))
|
||||||
}
|
}
|
||||||
Statement::Block(statements) => {
|
|
||||||
let mut previous_value = None;
|
|
||||||
|
|
||||||
for statement in statements {
|
|
||||||
previous_value = self.run_node(statement, variables)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(previous_value)
|
|
||||||
}
|
|
||||||
Statement::BuiltInFunctionCall {
|
Statement::BuiltInFunctionCall {
|
||||||
function,
|
function,
|
||||||
type_arguments: _,
|
type_arguments: _,
|
||||||
@ -243,11 +233,6 @@ impl Vm {
|
|||||||
|
|
||||||
Ok(Some(Value::map(values)))
|
Ok(Some(Value::map(values)))
|
||||||
}
|
}
|
||||||
Statement::Nil(node) => {
|
|
||||||
let _return = self.run_node(*node, variables)?;
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Statement::PropertyAccess(left, right) => {
|
Statement::PropertyAccess(left, right) => {
|
||||||
let left_span = left.position;
|
let left_span = left.position;
|
||||||
let left_value = if let Some(value) = self.run_node(*left, variables)? {
|
let left_value = if let Some(value) = self.run_node(*left, variables)? {
|
||||||
@ -448,26 +433,6 @@ impl Display for VmError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn map_equal() {
|
|
||||||
let input = "{ y = 'foo', } == { y = 'foo', }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(input, &mut HashMap::new()),
|
|
||||||
Ok(Some(Value::boolean(true)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn integer_equal() {
|
|
||||||
let input = "42 == 42";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(input, &mut HashMap::new()),
|
|
||||||
Ok(Some(Value::boolean(true)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn modulo() {
|
fn modulo() {
|
||||||
let input = "42 % 2";
|
let input = "42 % 2";
|
||||||
|
Loading…
Reference in New Issue
Block a user