Fix dot notations precedence; Add some miscellaneous expansions
This commit is contained in:
parent
a61c1756f2
commit
0fb0b63a97
@ -62,10 +62,6 @@ pub enum Statement {
|
||||
value_arguments: Option<Vec<Node<Statement>>>,
|
||||
},
|
||||
|
||||
// Property access expression
|
||||
// TODO: This should be a binary operation
|
||||
PropertyAccess(Box<Node<Statement>>, Box<Node<Statement>>),
|
||||
|
||||
// Loops
|
||||
While {
|
||||
condition: Box<Node<Statement>>,
|
||||
@ -113,7 +109,11 @@ impl Statement {
|
||||
pub fn expected_type(&self, context: &Context) -> Option<Type> {
|
||||
match self {
|
||||
Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context),
|
||||
Statement::BinaryOperation { left, operator, .. } => match operator.inner {
|
||||
Statement::BinaryOperation {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => match operator.inner {
|
||||
BinaryOperator::Add
|
||||
| BinaryOperator::Divide
|
||||
| BinaryOperator::Modulo
|
||||
@ -129,6 +129,21 @@ impl Statement {
|
||||
| BinaryOperator::Or => Some(Type::Boolean),
|
||||
|
||||
BinaryOperator::Assign | BinaryOperator::AddAssign => None,
|
||||
|
||||
BinaryOperator::FieldAccess => {
|
||||
let left_type = left.inner.expected_type(context)?;
|
||||
|
||||
if let Type::Map(properties) = left_type {
|
||||
let key = match &right.inner {
|
||||
Statement::Identifier(identifier) => identifier,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
properties.get(key).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
||||
Statement::Constant(value) => Some(value.r#type(context)),
|
||||
@ -157,7 +172,6 @@ impl Statement {
|
||||
Some(Type::Map(types))
|
||||
}
|
||||
Statement::Nil(_) => None,
|
||||
Statement::PropertyAccess(_, _) => None,
|
||||
Statement::UnaryOperation { operator, operand } => match operator.inner {
|
||||
UnaryOperator::Negate => Some(operand.inner.expected_type(context)?),
|
||||
UnaryOperator::Not => Some(Type::Boolean),
|
||||
@ -202,7 +216,11 @@ impl Display for Statement {
|
||||
operator,
|
||||
right,
|
||||
} => {
|
||||
if let BinaryOperator::FieldAccess = operator.inner {
|
||||
write!(f, "{left}{operator}{right}")
|
||||
} else {
|
||||
write!(f, "{left} {operator} {right}")
|
||||
}
|
||||
}
|
||||
Statement::BuiltInFunctionCall {
|
||||
function,
|
||||
@ -340,7 +358,6 @@ impl Display for Statement {
|
||||
write!(f, "}}")
|
||||
}
|
||||
Statement::Nil(node) => write!(f, "{node};"),
|
||||
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
|
||||
Statement::UnaryOperation { operator, operand } => {
|
||||
write!(f, "{operator}{operand}")
|
||||
}
|
||||
@ -353,6 +370,8 @@ impl Display for Statement {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BinaryOperator {
|
||||
FieldAccess,
|
||||
|
||||
// Math
|
||||
Add,
|
||||
Divide,
|
||||
@ -385,6 +404,7 @@ impl Display for BinaryOperator {
|
||||
BinaryOperator::And => write!(f, "&&"),
|
||||
BinaryOperator::Divide => write!(f, "/"),
|
||||
BinaryOperator::Equal => write!(f, "=="),
|
||||
BinaryOperator::FieldAccess => write!(f, "."),
|
||||
BinaryOperator::Greater => write!(f, ">"),
|
||||
BinaryOperator::GreaterOrEqual => write!(f, ">="),
|
||||
BinaryOperator::Less => write!(f, "<"),
|
||||
|
@ -96,6 +96,44 @@ impl<'a> Analyzer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if let BinaryOperator::FieldAccess = operator.inner {
|
||||
self.analyze_statement(left)?;
|
||||
|
||||
if let Statement::Identifier(_) = right.inner {
|
||||
// Do not expect a value for property accessors
|
||||
} else {
|
||||
self.analyze_statement(right)?;
|
||||
}
|
||||
|
||||
if let Some(Type::List { .. }) = left.inner.expected_type(self.context) {
|
||||
let right_type = right.inner.expected_type(self.context);
|
||||
|
||||
if let Some(Type::Integer) = right_type {
|
||||
// Allow indexing lists with integers
|
||||
} else if let Some(Type::Range) = right_type {
|
||||
// Allow indexing lists with ranges
|
||||
} else {
|
||||
return Err(AnalyzerError::ExpectedIntegerOrRange {
|
||||
actual: right.as_ref().clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Type::Map { .. }) = left.inner.expected_type(self.context) {
|
||||
if let Some(Type::String) = right.inner.expected_type(self.context) {
|
||||
// Allow indexing maps with strings
|
||||
} else if let Statement::Identifier(_) = right.inner {
|
||||
// Allow indexing maps with identifiers
|
||||
} else {
|
||||
return Err(AnalyzerError::ExpectedIdentifierOrString {
|
||||
actual: right.as_ref().clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.analyze_statement(left)?;
|
||||
self.analyze_statement(right)?;
|
||||
|
||||
@ -324,41 +362,6 @@ impl<'a> Analyzer<'a> {
|
||||
Statement::Nil(node) => {
|
||||
self.analyze_statement(node)?;
|
||||
}
|
||||
Statement::PropertyAccess(left, right) => {
|
||||
self.analyze_statement(left)?;
|
||||
|
||||
if let Statement::Identifier(_) = right.inner {
|
||||
// Do not expect a value for property accessors
|
||||
} else {
|
||||
self.analyze_statement(right)?;
|
||||
}
|
||||
|
||||
if let Some(Type::List { .. }) = left.inner.expected_type(self.context) {
|
||||
let right_type = right.inner.expected_type(self.context);
|
||||
|
||||
if let Some(Type::Integer) = right_type {
|
||||
// Allow indexing lists with integers
|
||||
} else if let Some(Type::Range) = right_type {
|
||||
// Allow indexing lists with ranges
|
||||
} else {
|
||||
return Err(AnalyzerError::ExpectedIntegerOrRange {
|
||||
actual: right.as_ref().clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Type::Map { .. }) = left.inner.expected_type(self.context) {
|
||||
if let Some(Type::String) = right.inner.expected_type(self.context) {
|
||||
// Allow indexing maps with strings
|
||||
} else if let Statement::Identifier(_) = right.inner {
|
||||
// Allow indexing maps with identifiers
|
||||
} else {
|
||||
return Err(AnalyzerError::ExpectedIdentifierOrString {
|
||||
actual: right.as_ref().clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Statement::UnaryOperation { operator, operand } => {
|
||||
self.analyze_statement(operand)?;
|
||||
|
||||
|
@ -264,6 +264,11 @@ impl Lexer {
|
||||
|
||||
(Token::Bang, (self.position - 1, self.position))
|
||||
}
|
||||
':' => {
|
||||
self.position += 1;
|
||||
|
||||
(Token::Colon, (self.position - 1, self.position))
|
||||
}
|
||||
_ => {
|
||||
self.position += 1;
|
||||
|
||||
@ -408,13 +413,17 @@ impl Lexer {
|
||||
let token = match string {
|
||||
"Infinity" => Token::Float("Infinity"),
|
||||
"NaN" => Token::Float("NaN"),
|
||||
"bool" => Token::Bool,
|
||||
"else" => Token::Else,
|
||||
"false" => Token::Boolean("false"),
|
||||
"float" => Token::FloatKeyword,
|
||||
"if" => Token::If,
|
||||
"int" => Token::Int,
|
||||
"is_even" => Token::IsEven,
|
||||
"is_odd" => Token::IsOdd,
|
||||
"length" => Token::Length,
|
||||
"read_line" => Token::ReadLine,
|
||||
"struct" => Token::Struct,
|
||||
"to_string" => Token::ToString,
|
||||
"true" => Token::Boolean("true"),
|
||||
"while" => Token::While,
|
||||
|
@ -604,6 +604,8 @@ impl<'src> Parser<'src> {
|
||||
};
|
||||
|
||||
if let Token::Dot = &self.current.0 {
|
||||
let operator_position = self.current.1;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let right = self.parse_statement(operator_precedence)?;
|
||||
@ -658,7 +660,11 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::PropertyAccess(Box::new(left), Box::new(right)),
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left),
|
||||
operator: Node::new(BinaryOperator::FieldAccess, operator_position),
|
||||
right: Box::new(right),
|
||||
},
|
||||
(left_start, right_end),
|
||||
));
|
||||
}
|
||||
@ -852,47 +858,48 @@ mod tests {
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node {
|
||||
inner: Statement::PropertyAccess(
|
||||
Box::new(Node {
|
||||
inner: Statement::Map(vec![(
|
||||
Node {
|
||||
inner: Statement::Identifier(Identifier::new("x")),
|
||||
position: (2, 3)
|
||||
},
|
||||
Node {
|
||||
inner: Statement::Map(vec![(
|
||||
Node {
|
||||
inner: Statement::Identifier(Identifier::new("y")),
|
||||
position: (8, 9)
|
||||
},
|
||||
Node {
|
||||
inner: Statement::Constant(Value::integer(42)),
|
||||
position: (12, 14)
|
||||
}
|
||||
)]),
|
||||
position: (6, 16)
|
||||
}
|
||||
)]),
|
||||
position: (0, 18)
|
||||
}),
|
||||
Box::new(Node {
|
||||
inner: Statement::PropertyAccess(
|
||||
Box::new(Node {
|
||||
inner: Statement::Identifier(Identifier::new("x")),
|
||||
position: (19, 20)
|
||||
}),
|
||||
Box::new(Node {
|
||||
inner: Statement::Identifier(Identifier::new("y")),
|
||||
|
||||
position: (21, 22)
|
||||
})
|
||||
nodes: [Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Map(vec![(
|
||||
Node::new(
|
||||
Statement::Identifier(Identifier::new("x")),
|
||||
(2, 3)
|
||||
),
|
||||
position: (19, 22)
|
||||
})
|
||||
Node::new(
|
||||
Statement::Map(vec![(
|
||||
Node::new(
|
||||
Statement::Identifier(Identifier::new("y")),
|
||||
(8, 9)
|
||||
),
|
||||
position: (0, 22)
|
||||
}]
|
||||
Node::new(
|
||||
Statement::Constant(Value::integer(42)),
|
||||
(12, 14)
|
||||
)
|
||||
)]),
|
||||
(6, 16)
|
||||
)
|
||||
)]),
|
||||
(0, 18)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::FieldAccess, (18, 19)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("x")),
|
||||
(19, 20)
|
||||
))
|
||||
},
|
||||
(0, 20)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::FieldAccess, (20, 21)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("y")),
|
||||
(21, 22)
|
||||
))
|
||||
},
|
||||
(0, 22)
|
||||
)]
|
||||
.into()
|
||||
})
|
||||
)
|
||||
@ -1444,7 +1451,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_with_two_properties() {
|
||||
fn map_with_two_fields() {
|
||||
let input = "{ x = 42, y = 'foobar' }";
|
||||
|
||||
assert_eq!(
|
||||
@ -1469,8 +1476,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_with_one_property() {
|
||||
let input = "{ x = 42, }";
|
||||
fn map_with_one_field() {
|
||||
let input = "{ x = 42 }";
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
@ -1480,7 +1487,7 @@ mod tests {
|
||||
Node::new(Statement::Identifier(Identifier::new("x")), (2, 3)),
|
||||
Node::new(Statement::Constant(Value::integer(42)), (6, 8))
|
||||
)]),
|
||||
(0, 11)
|
||||
(0, 10)
|
||||
)]
|
||||
.into()
|
||||
})
|
||||
@ -1732,8 +1739,8 @@ mod tests {
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::PropertyAccess(
|
||||
Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::List(vec![
|
||||
Node::new(Statement::Constant(Value::integer(1)), (1, 2)),
|
||||
Node::new(Statement::Constant(Value::integer(2)), (4, 5)),
|
||||
@ -1741,8 +1748,12 @@ mod tests {
|
||||
]),
|
||||
(0, 9)
|
||||
)),
|
||||
Box::new(Node::new(Statement::Constant(Value::integer(0)), (10, 11))),
|
||||
),
|
||||
operator: Node::new(BinaryOperator::FieldAccess, (9, 10)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::integer(0)),
|
||||
(10, 11)
|
||||
)),
|
||||
},
|
||||
(0, 11),
|
||||
)]
|
||||
.into()
|
||||
@ -1758,16 +1769,17 @@ mod tests {
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::PropertyAccess(
|
||||
Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("a")),
|
||||
(0, 1)
|
||||
)),
|
||||
Box::new(Node::new(
|
||||
operator: Node::new(BinaryOperator::FieldAccess, (1, 2)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("b")),
|
||||
(2, 3)
|
||||
)),
|
||||
),
|
||||
},
|
||||
(0, 3),
|
||||
)]
|
||||
.into()
|
||||
|
@ -17,18 +17,24 @@ pub enum Token<'src> {
|
||||
String(&'src str),
|
||||
|
||||
// Keywords
|
||||
Bool,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
If,
|
||||
Int,
|
||||
IsEven,
|
||||
IsOdd,
|
||||
Length,
|
||||
ReadLine,
|
||||
Str,
|
||||
Struct,
|
||||
ToString,
|
||||
While,
|
||||
WriteLine,
|
||||
|
||||
// Symbols
|
||||
Bang,
|
||||
Colon,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
@ -59,7 +65,9 @@ impl<'src> Token<'src> {
|
||||
pub fn to_owned(&self) -> TokenOwned {
|
||||
match self {
|
||||
Token::Bang => TokenOwned::Bang,
|
||||
Token::Bool => TokenOwned::Bool,
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
||||
Token::Colon => TokenOwned::Colon,
|
||||
Token::Comma => TokenOwned::Comma,
|
||||
Token::Dot => TokenOwned::Dot,
|
||||
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
|
||||
@ -70,10 +78,12 @@ impl<'src> Token<'src> {
|
||||
Token::Eof => TokenOwned::Eof,
|
||||
Token::Equal => TokenOwned::Equal,
|
||||
Token::Float(float) => TokenOwned::Float(float.to_string()),
|
||||
Token::FloatKeyword => TokenOwned::FloatKeyword,
|
||||
Token::Greater => TokenOwned::Greater,
|
||||
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
|
||||
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
|
||||
Token::If => TokenOwned::If,
|
||||
Token::Int => TokenOwned::Int,
|
||||
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
|
||||
Token::IsEven => TokenOwned::IsEven,
|
||||
Token::IsOdd => TokenOwned::IsOdd,
|
||||
@ -95,6 +105,8 @@ impl<'src> Token<'src> {
|
||||
Token::Star => TokenOwned::Star,
|
||||
Token::Slash => TokenOwned::Slash,
|
||||
Token::String(text) => TokenOwned::String(text.to_string()),
|
||||
Token::Str => TokenOwned::Str,
|
||||
Token::Struct => TokenOwned::Struct,
|
||||
Token::ToString => TokenOwned::ToString,
|
||||
Token::While => TokenOwned::While,
|
||||
Token::WriteLine => TokenOwned::WriteLine,
|
||||
@ -104,11 +116,14 @@ impl<'src> Token<'src> {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Token::Boolean(boolean_text) => boolean_text,
|
||||
Token::Float(float_text) => float_text,
|
||||
Token::Identifier(text) => text,
|
||||
Token::Integer(integer_text) => integer_text,
|
||||
Token::String(text) => text,
|
||||
|
||||
Token::Bang => "!",
|
||||
Token::Bool => "bool",
|
||||
Token::Colon => ":",
|
||||
Token::Comma => ",",
|
||||
Token::Dot => ".",
|
||||
Token::DoubleAmpersand => "&&",
|
||||
@ -118,10 +133,11 @@ impl<'src> Token<'src> {
|
||||
Token::Else => "else",
|
||||
Token::Eof => "EOF",
|
||||
Token::Equal => "=",
|
||||
Token::Float(_) => "float",
|
||||
Token::FloatKeyword => "float",
|
||||
Token::Greater => ">",
|
||||
Token::GreaterEqual => ">=",
|
||||
Token::If => "if",
|
||||
Token::Int => "int",
|
||||
Token::IsEven => "is_even",
|
||||
Token::IsOdd => "is_odd",
|
||||
Token::LeftCurlyBrace => "{",
|
||||
@ -141,6 +157,8 @@ impl<'src> Token<'src> {
|
||||
Token::Semicolon => ";",
|
||||
Token::Star => "*",
|
||||
Token::Slash => "/",
|
||||
Token::Str => "str",
|
||||
Token::Struct => "struct",
|
||||
Token::ToString => "to_string",
|
||||
Token::While => "while",
|
||||
Token::WriteLine => "write_line",
|
||||
@ -150,7 +168,9 @@ impl<'src> Token<'src> {
|
||||
pub fn kind(&self) -> TokenKind {
|
||||
match self {
|
||||
Token::Bang => TokenKind::Bang,
|
||||
Token::Bool => TokenKind::Bool,
|
||||
Token::Boolean(_) => TokenKind::Boolean,
|
||||
Token::Colon => TokenKind::Colon,
|
||||
Token::Comma => TokenKind::Comma,
|
||||
Token::Dot => TokenKind::Dot,
|
||||
Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
|
||||
@ -161,10 +181,12 @@ impl<'src> Token<'src> {
|
||||
Token::Eof => TokenKind::Eof,
|
||||
Token::Equal => TokenKind::Equal,
|
||||
Token::Float(_) => TokenKind::Float,
|
||||
Token::FloatKeyword => TokenKind::FloatKeyword,
|
||||
Token::Greater => TokenKind::Greater,
|
||||
Token::GreaterEqual => TokenKind::GreaterOrEqual,
|
||||
Token::Identifier(_) => TokenKind::Identifier,
|
||||
Token::If => TokenKind::If,
|
||||
Token::Int => TokenKind::Int,
|
||||
Token::Integer(_) => TokenKind::Integer,
|
||||
Token::IsEven => TokenKind::IsEven,
|
||||
Token::IsOdd => TokenKind::IsOdd,
|
||||
@ -185,7 +207,9 @@ impl<'src> Token<'src> {
|
||||
Token::Semicolon => TokenKind::Semicolon,
|
||||
Token::Star => TokenKind::Star,
|
||||
Token::Slash => TokenKind::Slash,
|
||||
Token::Str => TokenKind::Str,
|
||||
Token::String(_) => TokenKind::String,
|
||||
Token::Struct => TokenKind::Struct,
|
||||
Token::ToString => TokenKind::ToString,
|
||||
Token::While => TokenKind::While,
|
||||
Token::WriteLine => TokenKind::WriteLine,
|
||||
@ -301,18 +325,23 @@ pub enum TokenOwned {
|
||||
String(String),
|
||||
|
||||
// Keywords
|
||||
Bool,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
If,
|
||||
Int,
|
||||
IsEven,
|
||||
IsOdd,
|
||||
Length,
|
||||
ReadLine,
|
||||
Str,
|
||||
ToString,
|
||||
While,
|
||||
WriteLine,
|
||||
|
||||
// Symbols
|
||||
Bang,
|
||||
Colon,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
@ -336,6 +365,7 @@ pub enum TokenOwned {
|
||||
RightSquareBrace,
|
||||
Semicolon,
|
||||
Star,
|
||||
Struct,
|
||||
Slash,
|
||||
}
|
||||
|
||||
@ -343,7 +373,9 @@ impl Display for TokenOwned {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenOwned::Bang => Token::Bang.fmt(f),
|
||||
TokenOwned::Bool => write!(f, "bool"),
|
||||
TokenOwned::Boolean(boolean) => write!(f, "{boolean}"),
|
||||
TokenOwned::Colon => Token::Colon.fmt(f),
|
||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
||||
TokenOwned::Dot => Token::Dot.fmt(f),
|
||||
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
||||
@ -354,10 +386,12 @@ impl Display for TokenOwned {
|
||||
TokenOwned::Eof => Token::Eof.fmt(f),
|
||||
TokenOwned::Equal => Token::Equal.fmt(f),
|
||||
TokenOwned::Float(float) => write!(f, "{float}"),
|
||||
TokenOwned::FloatKeyword => write!(f, "float"),
|
||||
TokenOwned::Greater => Token::Greater.fmt(f),
|
||||
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenOwned::Identifier(text) => write!(f, "{text}"),
|
||||
TokenOwned::If => Token::If.fmt(f),
|
||||
TokenOwned::Int => write!(f, "int"),
|
||||
TokenOwned::Integer(integer) => write!(f, "{integer}"),
|
||||
TokenOwned::IsEven => Token::IsEven.fmt(f),
|
||||
TokenOwned::IsOdd => Token::IsOdd.fmt(f),
|
||||
@ -378,7 +412,9 @@ impl Display for TokenOwned {
|
||||
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
|
||||
TokenOwned::Star => Token::Star.fmt(f),
|
||||
TokenOwned::Slash => Token::Slash.fmt(f),
|
||||
TokenOwned::Str => write!(f, "str"),
|
||||
TokenOwned::String(string) => write!(f, "{string}"),
|
||||
TokenOwned::Struct => Token::Struct.fmt(f),
|
||||
TokenOwned::ToString => Token::ToString.fmt(f),
|
||||
TokenOwned::While => Token::While.fmt(f),
|
||||
TokenOwned::WriteLine => Token::WriteLine.fmt(f),
|
||||
@ -400,18 +436,23 @@ pub enum TokenKind {
|
||||
String,
|
||||
|
||||
// Keywords
|
||||
Bool,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
If,
|
||||
Int,
|
||||
IsEven,
|
||||
IsOdd,
|
||||
Length,
|
||||
ReadLine,
|
||||
Str,
|
||||
ToString,
|
||||
While,
|
||||
WriteLine,
|
||||
|
||||
// Symbols
|
||||
Bang,
|
||||
Colon,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
@ -435,6 +476,7 @@ pub enum TokenKind {
|
||||
RightSquareBrace,
|
||||
Semicolon,
|
||||
Star,
|
||||
Struct,
|
||||
Slash,
|
||||
}
|
||||
|
||||
@ -442,7 +484,9 @@ impl Display for TokenKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenKind::Bang => Token::Bang.fmt(f),
|
||||
TokenKind::Boolean => write!(f, "boolean"),
|
||||
TokenKind::Bool => Token::Bool.fmt(f),
|
||||
TokenKind::Boolean => write!(f, "boolean value"),
|
||||
TokenKind::Colon => Token::Colon.fmt(f),
|
||||
TokenKind::Comma => Token::Comma.fmt(f),
|
||||
TokenKind::Dot => Token::Dot.fmt(f),
|
||||
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
||||
@ -452,12 +496,14 @@ impl Display for TokenKind {
|
||||
TokenKind::Else => Token::Else.fmt(f),
|
||||
TokenKind::Eof => Token::Eof.fmt(f),
|
||||
TokenKind::Equal => Token::Equal.fmt(f),
|
||||
TokenKind::Float => write!(f, "float"),
|
||||
TokenKind::Float => write!(f, "float value"),
|
||||
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
|
||||
TokenKind::Greater => Token::Greater.fmt(f),
|
||||
TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenKind::Identifier => write!(f, "identifier"),
|
||||
TokenKind::If => Token::If.fmt(f),
|
||||
TokenKind::Integer => write!(f, "integer"),
|
||||
TokenKind::Int => Token::Int.fmt(f),
|
||||
TokenKind::Integer => write!(f, "integer value"),
|
||||
TokenKind::IsEven => Token::IsEven.fmt(f),
|
||||
TokenKind::IsOdd => Token::IsOdd.fmt(f),
|
||||
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
|
||||
@ -476,11 +522,85 @@ impl Display for TokenKind {
|
||||
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
||||
TokenKind::Semicolon => Token::Semicolon.fmt(f),
|
||||
TokenKind::Star => Token::Star.fmt(f),
|
||||
TokenKind::Str => write!(f, "str"),
|
||||
TokenKind::Slash => Token::Slash.fmt(f),
|
||||
TokenKind::String => write!(f, "string"),
|
||||
TokenKind::String => write!(f, "string value"),
|
||||
TokenKind::Struct => Token::Struct.fmt(f),
|
||||
TokenKind::ToString => Token::ToString.fmt(f),
|
||||
TokenKind::While => Token::While.fmt(f),
|
||||
TokenKind::WriteLine => Token::WriteLine.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn all_tokens<'src>() -> [Token<'src>; 42] {
|
||||
[
|
||||
Token::Bang,
|
||||
Token::Boolean("true"),
|
||||
Token::Colon,
|
||||
Token::Comma,
|
||||
Token::Dot,
|
||||
Token::DoubleAmpersand,
|
||||
Token::DoubleDot,
|
||||
Token::DoubleEqual,
|
||||
Token::DoublePipe,
|
||||
Token::Else,
|
||||
Token::Eof,
|
||||
Token::Equal,
|
||||
Token::Float("42.0"),
|
||||
Token::Greater,
|
||||
Token::GreaterEqual,
|
||||
Token::Identifier("foobar"),
|
||||
Token::If,
|
||||
Token::Integer("42"),
|
||||
Token::IsEven,
|
||||
Token::IsOdd,
|
||||
Token::LeftCurlyBrace,
|
||||
Token::LeftParenthesis,
|
||||
Token::LeftSquareBrace,
|
||||
Token::Length,
|
||||
Token::Less,
|
||||
Token::LessEqual,
|
||||
Token::Minus,
|
||||
Token::Percent,
|
||||
Token::Plus,
|
||||
Token::PlusEqual,
|
||||
Token::ReadLine,
|
||||
Token::RightCurlyBrace,
|
||||
Token::RightParenthesis,
|
||||
Token::RightSquareBrace,
|
||||
Token::Semicolon,
|
||||
Token::Star,
|
||||
Token::Slash,
|
||||
Token::String("foobar"),
|
||||
Token::Struct,
|
||||
Token::ToString,
|
||||
Token::While,
|
||||
Token::WriteLine,
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_displays() {
|
||||
for token in all_tokens().iter() {
|
||||
let display = token.to_string();
|
||||
|
||||
assert_eq!(display, token.to_owned().to_string());
|
||||
|
||||
if let Token::Boolean(_)
|
||||
| Token::Float(_)
|
||||
| Token::Identifier(_)
|
||||
| Token::Integer(_)
|
||||
| Token::String(_) = token
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
assert_eq!(display, token.kind().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ pub struct TypeConflict {
|
||||
pub enum Type {
|
||||
Any,
|
||||
Boolean,
|
||||
Defined(Identifier),
|
||||
Enum {
|
||||
name: Identifier,
|
||||
type_parameters: Option<Vec<Type>>,
|
||||
@ -216,6 +217,7 @@ impl Display for Type {
|
||||
match self {
|
||||
Type::Any => write!(f, "any"),
|
||||
Type::Boolean => write!(f, "bool"),
|
||||
Type::Defined(identifier) => write!(f, "{identifier}"),
|
||||
Type::Enum { variants, .. } => {
|
||||
write!(f, "enum ")?;
|
||||
|
||||
|
@ -348,6 +348,7 @@ impl Display for Value {
|
||||
}
|
||||
ValueInner::Range(range) => write!(f, "{}..{}", range.start, range.end),
|
||||
ValueInner::String(string) => write!(f, "{string}"),
|
||||
ValueInner::Struct(r#struct) => write!(f, "{struct}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -403,6 +404,7 @@ impl Serialize for Value {
|
||||
tuple_ser.end()
|
||||
}
|
||||
ValueInner::String(string) => serializer.serialize_str(string),
|
||||
ValueInner::Struct(r#struct) => r#struct.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -659,6 +661,7 @@ pub enum ValueInner {
|
||||
Map(BTreeMap<Identifier, Value>),
|
||||
Range(Range<i64>),
|
||||
String(String),
|
||||
Struct(Struct),
|
||||
}
|
||||
|
||||
impl ValueInner {
|
||||
@ -692,6 +695,7 @@ impl ValueInner {
|
||||
}
|
||||
ValueInner::Range(_) => Type::Range,
|
||||
ValueInner::String(_) => Type::String,
|
||||
ValueInner::Struct(r#struct) => Type::Defined(r#struct.name().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -747,6 +751,8 @@ impl Ord for ValueInner {
|
||||
(Range(_), _) => Ordering::Greater,
|
||||
(String(left), String(right)) => left.cmp(right),
|
||||
(String(_), _) => Ordering::Greater,
|
||||
(Struct(left), Struct(right)) => left.cmp(right),
|
||||
(Struct(_), _) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -832,6 +838,65 @@ impl Display for Function {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Struct {
|
||||
Unit {
|
||||
name: Identifier,
|
||||
},
|
||||
Tuple {
|
||||
name: Identifier,
|
||||
fields: Vec<Value>,
|
||||
},
|
||||
Fields {
|
||||
name: Identifier,
|
||||
fields: Vec<(Identifier, Value)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
pub fn name(&self) -> &Identifier {
|
||||
match self {
|
||||
Struct::Unit { name } => name,
|
||||
Struct::Tuple { name, .. } => name,
|
||||
Struct::Fields { name, .. } => name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Struct {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Struct::Unit { name } => write!(f, "{}", name),
|
||||
Struct::Tuple { name, fields } => {
|
||||
write!(f, "{}(", name)?;
|
||||
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{}", field)?;
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
Struct::Fields { name, fields } => {
|
||||
write!(f, "{} {{", name)?;
|
||||
|
||||
for (index, (identifier, r#type)) in fields.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{}: {}", identifier, r#type)?;
|
||||
}
|
||||
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ValueError {
|
||||
CannotAdd(Value, Value),
|
||||
|
@ -137,6 +137,58 @@ impl Vm {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let BinaryOperator::FieldAccess = operator.inner {
|
||||
let left_span = left.position;
|
||||
let left_value = if let Some(value) = self.run_statement(*left)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue {
|
||||
position: left_span,
|
||||
});
|
||||
};
|
||||
let right_span = right.position;
|
||||
|
||||
if let (Some(list), Statement::Constant(value)) =
|
||||
(left_value.as_list(), &right.inner)
|
||||
{
|
||||
if let Some(index) = value.as_integer() {
|
||||
let value = list.get(index as usize).cloned();
|
||||
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
if let Some(range) = value.as_range() {
|
||||
let range = range.start as usize..range.end as usize;
|
||||
|
||||
if let Some(items) = list.get(range) {
|
||||
return Ok(Some(Value::list(items.to_vec())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(map) = left_value.as_map() {
|
||||
if let Statement::Identifier(identifier) = right.inner {
|
||||
let value = map.get(&identifier).cloned();
|
||||
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.run_statement(*right)? {
|
||||
if let Some(string) = value.as_string() {
|
||||
let identifier = Identifier::new(string);
|
||||
|
||||
let value = map.get(&identifier).cloned();
|
||||
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(VmError::ExpectedIdentifierIntegerOrRange {
|
||||
position: right_span,
|
||||
});
|
||||
}
|
||||
|
||||
let left_position = left.position;
|
||||
let left_value = if let Some(value) = self.run_statement(*left)? {
|
||||
value
|
||||
@ -466,57 +518,6 @@ impl Vm {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
Statement::PropertyAccess(left, right) => {
|
||||
let left_span = left.position;
|
||||
let left_value = if let Some(value) = self.run_statement(*left)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue {
|
||||
position: left_span,
|
||||
});
|
||||
};
|
||||
let right_span = right.position;
|
||||
|
||||
if let (Some(list), Statement::Constant(value)) =
|
||||
(left_value.as_list(), &right.inner)
|
||||
{
|
||||
if let Some(index) = value.as_integer() {
|
||||
let value = list.get(index as usize).cloned();
|
||||
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
if let Some(range) = value.as_range() {
|
||||
let range = range.start as usize..range.end as usize;
|
||||
|
||||
if let Some(items) = list.get(range) {
|
||||
return Ok(Some(Value::list(items.to_vec())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(map) = left_value.as_map() {
|
||||
if let Statement::Identifier(identifier) = right.inner {
|
||||
let value = map.get(&identifier).cloned();
|
||||
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.run_statement(*right)? {
|
||||
if let Some(string) = value.as_string() {
|
||||
let identifier = Identifier::new(string);
|
||||
|
||||
let value = map.get(&identifier).cloned();
|
||||
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(VmError::ExpectedIdentifierIntegerOrRange {
|
||||
position: right_span,
|
||||
})
|
||||
}
|
||||
Statement::UnaryOperation { operator, operand } => {
|
||||
let position = operand.position;
|
||||
let value = if let Some(value) = self.run_statement(*operand)? {
|
||||
|
Loading…
Reference in New Issue
Block a user