Add map parsing
This commit is contained in:
parent
55a8661618
commit
929468338d
@ -1,6 +1,6 @@
|
||||
//! In-memory representation of a Dust program.
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
collections::{BTreeMap, HashMap, VecDeque},
|
||||
fmt::{self, Display, Formatter},
|
||||
};
|
||||
|
||||
@ -67,6 +67,7 @@ pub enum Statement {
|
||||
|
||||
// Value collection expressions
|
||||
List(Vec<Node<Statement>>),
|
||||
Map(Vec<(Node<Identifier>, Node<Statement>)>),
|
||||
|
||||
// Hard-coded values
|
||||
Constant(Value),
|
||||
@ -83,9 +84,26 @@ impl Statement {
|
||||
Statement::Identifier(identifier) => variables
|
||||
.get(identifier)
|
||||
.map(|value| value.r#type(variables)),
|
||||
Statement::List(nodes) => nodes
|
||||
.first()
|
||||
.and_then(|node| node.inner.expected_type(variables)),
|
||||
Statement::List(nodes) => {
|
||||
let item_type = nodes.first().unwrap().inner.expected_type(variables)?;
|
||||
|
||||
Some(Type::List {
|
||||
length: nodes.len(),
|
||||
item_type: Box::new(item_type),
|
||||
})
|
||||
}
|
||||
Statement::Map(nodes) => {
|
||||
let mut types = BTreeMap::new();
|
||||
|
||||
for (identifier, item) in nodes {
|
||||
types.insert(
|
||||
identifier.inner.clone(),
|
||||
item.inner.expected_type(variables)?,
|
||||
);
|
||||
}
|
||||
|
||||
Some(Type::Map(types))
|
||||
}
|
||||
Statement::PropertyAccess(_, _) => None,
|
||||
}
|
||||
}
|
||||
@ -181,14 +199,30 @@ impl Display for Statement {
|
||||
Statement::Identifier(identifier) => write!(f, "{identifier}"),
|
||||
Statement::List(nodes) => {
|
||||
write!(f, "[")?;
|
||||
|
||||
for (i, node) in nodes.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{node}")?;
|
||||
}
|
||||
|
||||
write!(f, "]")
|
||||
}
|
||||
Statement::Map(nodes) => {
|
||||
write!(f, "{{")?;
|
||||
|
||||
for (i, (identifier, node)) in nodes.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{identifier} = {node}")?;
|
||||
}
|
||||
|
||||
write!(f, "}}")
|
||||
}
|
||||
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +163,11 @@ impl<'a> Analyzer<'a> {
|
||||
self.analyze_node(statement)?;
|
||||
}
|
||||
}
|
||||
Statement::Map(properties) => {
|
||||
for (_key, value_node) in properties {
|
||||
self.analyze_node(value_node)?;
|
||||
}
|
||||
}
|
||||
Statement::PropertyAccess(left, right) => {
|
||||
if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) =
|
||||
&left.inner
|
||||
|
@ -4,7 +4,7 @@
|
||||
//! - `parse` convenience function
|
||||
//! - `Parser` struct, which parses the input a statement at a time
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
collections::{BTreeMap, VecDeque},
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
};
|
||||
@ -172,10 +172,7 @@ impl<'src> Parser<'src> {
|
||||
let identifier = if let Statement::Identifier(identifier) = left_node.inner {
|
||||
identifier
|
||||
} else {
|
||||
return Err(ParseError::ExpectedIdentifier {
|
||||
actual: left_node.inner,
|
||||
position: left_node.position,
|
||||
});
|
||||
todo!()
|
||||
};
|
||||
let right_node = self.parse_node(self.current_precedence())?;
|
||||
let right_end = right_node.position.1;
|
||||
@ -347,6 +344,51 @@ impl<'src> Parser<'src> {
|
||||
|
||||
Ok(Node::new(Statement::Constant(Value::string(string)), span))
|
||||
}
|
||||
(Token::LeftCurlyBrace, left_span) => {
|
||||
self.next_token()?;
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
loop {
|
||||
if let (Token::RightCurlyBrace, right_span) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::Map(nodes),
|
||||
(left_span.0, right_span.1),
|
||||
));
|
||||
}
|
||||
|
||||
let identifier = if let (Token::Identifier(text), right_span) = self.current {
|
||||
self.next_token()?;
|
||||
|
||||
Node::new(Identifier::new(text), right_span)
|
||||
} else {
|
||||
return Err(ParseError::ExpectedIdentifier {
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
};
|
||||
|
||||
if let Token::Equal = self.current.0 {
|
||||
self.next_token()?;
|
||||
} else {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenOwned::Equal,
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
}
|
||||
|
||||
let current_value_node = self.parse_node(0)?;
|
||||
|
||||
nodes.push((identifier, current_value_node));
|
||||
|
||||
if let Token::Comma = self.current.0 {
|
||||
self.next_token()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
(Token::LeftParenthesis, left_span) => {
|
||||
self.next_token()?;
|
||||
|
||||
@ -357,7 +399,8 @@ impl<'src> Parser<'src> {
|
||||
|
||||
Ok(Node::new(node.inner, (left_span.0, right_span.1)))
|
||||
} else {
|
||||
Err(ParseError::ExpectedClosingParenthesis {
|
||||
Err(ParseError::ExpectedToken {
|
||||
expected: TokenOwned::RightParenthesis,
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
})
|
||||
@ -387,7 +430,8 @@ impl<'src> Parser<'src> {
|
||||
if let Ok(instruction) = self.parse_node(0) {
|
||||
nodes.push(instruction);
|
||||
} else {
|
||||
return Err(ParseError::ExpectedClosingSquareBrace {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenOwned::RightSquareBrace,
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
@ -412,7 +456,8 @@ impl<'src> Parser<'src> {
|
||||
if let (Token::LeftParenthesis, _) = self.current {
|
||||
self.next_token()?;
|
||||
} else {
|
||||
return Err(ParseError::ExpectedOpeningParenthesis {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenOwned::LeftParenthesis,
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
@ -438,7 +483,8 @@ impl<'src> Parser<'src> {
|
||||
value_arguments = Some(vec![node]);
|
||||
}
|
||||
} else {
|
||||
return Err(ParseError::ExpectedClosingParenthesis {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenOwned::RightParenthesis,
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
@ -480,20 +526,12 @@ pub enum ParseError {
|
||||
error: LexError,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
ExpectedClosingParenthesis {
|
||||
actual: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedClosingSquareBrace {
|
||||
actual: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedIdentifier {
|
||||
actual: Statement,
|
||||
actual: TokenOwned,
|
||||
position: (usize, usize),
|
||||
},
|
||||
ExpectedOpeningParenthesis {
|
||||
ExpectedToken {
|
||||
expected: TokenOwned,
|
||||
actual: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
@ -507,10 +545,8 @@ impl ParseError {
|
||||
pub fn position(&self) -> Span {
|
||||
match self {
|
||||
Self::LexError { position, .. } => *position,
|
||||
Self::ExpectedClosingParenthesis { position, .. } => *position,
|
||||
Self::ExpectedClosingSquareBrace { position, .. } => *position,
|
||||
Self::ExpectedIdentifier { position, .. } => *position,
|
||||
Self::ExpectedOpeningParenthesis { position, .. } => *position,
|
||||
Self::ExpectedToken { position, .. } => *position,
|
||||
Self::UnexpectedToken { position, .. } => *position,
|
||||
}
|
||||
}
|
||||
@ -529,18 +565,12 @@ impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::LexError { error, .. } => write!(f, "{}", error),
|
||||
Self::ExpectedClosingParenthesis { actual, .. } => {
|
||||
write!(f, "Expected closing parenthesis, found {actual}",)
|
||||
}
|
||||
Self::ExpectedClosingSquareBrace { actual, .. } => {
|
||||
write!(f, "Expected closing square brace, found {actual}",)
|
||||
}
|
||||
Self::ExpectedIdentifier { actual, .. } => {
|
||||
write!(f, "Expected identifier, found {actual}")
|
||||
}
|
||||
Self::ExpectedOpeningParenthesis { actual, .. } => {
|
||||
write!(f, "Expected opening parenthesis, found {actual}",)
|
||||
}
|
||||
Self::ExpectedToken {
|
||||
expected, actual, ..
|
||||
} => write!(f, "Expected token {expected}, found {actual}"),
|
||||
Self::UnexpectedToken { actual, .. } => write!(f, "Unexpected token {actual}"),
|
||||
}
|
||||
}
|
||||
@ -548,10 +578,36 @@ impl Display for ParseError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{abstract_tree::BinaryOperator, Identifier};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[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]
|
||||
fn less_than() {
|
||||
let input = "1 < 2";
|
||||
|
@ -51,7 +51,6 @@ pub enum Type {
|
||||
length: usize,
|
||||
item_type: Box<Type>,
|
||||
},
|
||||
ListOf(Box<Type>),
|
||||
Map(BTreeMap<Identifier, Type>),
|
||||
Range,
|
||||
String,
|
||||
@ -111,11 +110,6 @@ impl Type {
|
||||
}
|
||||
}
|
||||
}
|
||||
(Type::ListOf(left), Type::ListOf(right)) => {
|
||||
if left.check(right).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
(
|
||||
Type::Structure {
|
||||
name: left_name,
|
||||
@ -160,29 +154,6 @@ impl Type {
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
(
|
||||
Type::ListOf(left_type),
|
||||
Type::List {
|
||||
item_type: right_type,
|
||||
..
|
||||
},
|
||||
)
|
||||
| (
|
||||
Type::List {
|
||||
item_type: right_type,
|
||||
..
|
||||
},
|
||||
Type::ListOf(left_type),
|
||||
) => {
|
||||
if right_type.check(left_type).is_err() {
|
||||
return Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
});
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
(
|
||||
Type::Function {
|
||||
type_parameters: left_type_parameters,
|
||||
@ -274,7 +245,6 @@ impl Display for Type {
|
||||
}
|
||||
Type::Integer => write!(f, "int"),
|
||||
Type::List { length, item_type } => write!(f, "[{length}; {}]", item_type),
|
||||
Type::ListOf(item_type) => write!(f, "[{}]", item_type),
|
||||
Type::Map(map) => {
|
||||
write!(f, "{{ ")?;
|
||||
|
||||
@ -349,10 +319,6 @@ mod tests {
|
||||
}),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
Type::ListOf(Box::new(Type::Integer)).check(&Type::ListOf(Box::new(Type::Integer))),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
|
||||
@ -393,7 +359,6 @@ mod tests {
|
||||
length: 10,
|
||||
item_type: Box::new(Type::Integer),
|
||||
},
|
||||
Type::ListOf(Box::new(Type::Boolean)),
|
||||
Type::Map(BTreeMap::new()),
|
||||
Type::Range,
|
||||
Type::String,
|
||||
@ -415,16 +380,4 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_list_types() {
|
||||
let list = Type::List {
|
||||
length: 42,
|
||||
item_type: Box::new(Type::Integer),
|
||||
};
|
||||
let list_of = Type::ListOf(Box::new(Type::Integer));
|
||||
|
||||
assert_eq!(list.check(&list_of), Ok(()));
|
||||
assert_eq!(list_of.check(&list), Ok(()));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Virtual machine for running the abstract syntax tree.
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{BTreeMap, HashMap},
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
};
|
||||
@ -216,6 +216,22 @@ impl Vm {
|
||||
|
||||
Ok(Some(Value::list(values)))
|
||||
}
|
||||
Statement::Map(nodes) => {
|
||||
let mut values = BTreeMap::new();
|
||||
|
||||
for (identifier, value_node) in nodes {
|
||||
let position = value_node.position;
|
||||
let value = if let Some(value) = self.run_node(value_node, variables)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue { position });
|
||||
};
|
||||
|
||||
values.insert(identifier.inner, value);
|
||||
}
|
||||
|
||||
Ok(Some(Value::map(values)))
|
||||
}
|
||||
Statement::PropertyAccess(left, right) => {
|
||||
let left_span = left.position;
|
||||
let left_value = if let Some(value) = self.run_node(*left, variables)? {
|
||||
|
Loading…
Reference in New Issue
Block a user