Fix dot notations precedence; Add some miscellaneous expansions

This commit is contained in:
Jeff 2024-08-12 15:02:04 -04:00
parent a61c1756f2
commit 0fb0b63a97
8 changed files with 384 additions and 152 deletions

View File

@ -62,10 +62,6 @@ pub enum Statement {
value_arguments: Option<Vec<Node<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 // Loops
While { While {
condition: Box<Node<Statement>>, condition: Box<Node<Statement>>,
@ -113,7 +109,11 @@ impl Statement {
pub fn expected_type(&self, context: &Context) -> Option<Type> { pub fn expected_type(&self, context: &Context) -> Option<Type> {
match self { match self {
Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context), 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::Add
| BinaryOperator::Divide | BinaryOperator::Divide
| BinaryOperator::Modulo | BinaryOperator::Modulo
@ -129,6 +129,21 @@ impl Statement {
| BinaryOperator::Or => Some(Type::Boolean), | BinaryOperator::Or => Some(Type::Boolean),
BinaryOperator::Assign | BinaryOperator::AddAssign => None, 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::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
Statement::Constant(value) => Some(value.r#type(context)), Statement::Constant(value) => Some(value.r#type(context)),
@ -157,7 +172,6 @@ impl Statement {
Some(Type::Map(types)) Some(Type::Map(types))
} }
Statement::Nil(_) => None, Statement::Nil(_) => None,
Statement::PropertyAccess(_, _) => None,
Statement::UnaryOperation { operator, operand } => match operator.inner { Statement::UnaryOperation { operator, operand } => match operator.inner {
UnaryOperator::Negate => Some(operand.inner.expected_type(context)?), UnaryOperator::Negate => Some(operand.inner.expected_type(context)?),
UnaryOperator::Not => Some(Type::Boolean), UnaryOperator::Not => Some(Type::Boolean),
@ -202,8 +216,12 @@ impl Display for Statement {
operator, operator,
right, right,
} => { } => {
if let BinaryOperator::FieldAccess = operator.inner {
write!(f, "{left}{operator}{right}")
} else {
write!(f, "{left} {operator} {right}") write!(f, "{left} {operator} {right}")
} }
}
Statement::BuiltInFunctionCall { Statement::BuiltInFunctionCall {
function, function,
type_arguments: type_parameters, type_arguments: type_parameters,
@ -340,7 +358,6 @@ impl Display for Statement {
write!(f, "}}") write!(f, "}}")
} }
Statement::Nil(node) => write!(f, "{node};"), Statement::Nil(node) => write!(f, "{node};"),
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
Statement::UnaryOperation { operator, operand } => { Statement::UnaryOperation { operator, operand } => {
write!(f, "{operator}{operand}") write!(f, "{operator}{operand}")
} }
@ -353,6 +370,8 @@ impl Display for Statement {
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BinaryOperator { pub enum BinaryOperator {
FieldAccess,
// Math // Math
Add, Add,
Divide, Divide,
@ -385,6 +404,7 @@ impl Display for BinaryOperator {
BinaryOperator::And => write!(f, "&&"), BinaryOperator::And => write!(f, "&&"),
BinaryOperator::Divide => write!(f, "/"), BinaryOperator::Divide => write!(f, "/"),
BinaryOperator::Equal => write!(f, "=="), BinaryOperator::Equal => write!(f, "=="),
BinaryOperator::FieldAccess => 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, "<"),

View File

@ -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(left)?;
self.analyze_statement(right)?; self.analyze_statement(right)?;
@ -324,41 +362,6 @@ impl<'a> Analyzer<'a> {
Statement::Nil(node) => { Statement::Nil(node) => {
self.analyze_statement(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 } => { Statement::UnaryOperation { operator, operand } => {
self.analyze_statement(operand)?; self.analyze_statement(operand)?;

View File

@ -264,6 +264,11 @@ impl Lexer {
(Token::Bang, (self.position - 1, self.position)) (Token::Bang, (self.position - 1, self.position))
} }
':' => {
self.position += 1;
(Token::Colon, (self.position - 1, self.position))
}
_ => { _ => {
self.position += 1; self.position += 1;
@ -408,13 +413,17 @@ impl Lexer {
let token = match string { let token = match string {
"Infinity" => Token::Float("Infinity"), "Infinity" => Token::Float("Infinity"),
"NaN" => Token::Float("NaN"), "NaN" => Token::Float("NaN"),
"bool" => Token::Bool,
"else" => Token::Else, "else" => Token::Else,
"false" => Token::Boolean("false"), "false" => Token::Boolean("false"),
"float" => Token::FloatKeyword,
"if" => Token::If, "if" => Token::If,
"int" => Token::Int,
"is_even" => Token::IsEven, "is_even" => Token::IsEven,
"is_odd" => Token::IsOdd, "is_odd" => Token::IsOdd,
"length" => Token::Length, "length" => Token::Length,
"read_line" => Token::ReadLine, "read_line" => Token::ReadLine,
"struct" => Token::Struct,
"to_string" => Token::ToString, "to_string" => Token::ToString,
"true" => Token::Boolean("true"), "true" => Token::Boolean("true"),
"while" => Token::While, "while" => Token::While,

View File

@ -604,6 +604,8 @@ impl<'src> Parser<'src> {
}; };
if let Token::Dot = &self.current.0 { if let Token::Dot = &self.current.0 {
let operator_position = self.current.1;
self.next_token()?; self.next_token()?;
let right = self.parse_statement(operator_precedence)?; let right = self.parse_statement(operator_precedence)?;
@ -658,7 +660,11 @@ impl<'src> Parser<'src> {
} }
return Ok(Node::new( 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), (left_start, right_end),
)); ));
} }
@ -852,47 +858,48 @@ mod tests {
assert_eq!( assert_eq!(
parse(input), parse(input),
Ok(AbstractSyntaxTree { Ok(AbstractSyntaxTree {
nodes: [Node { nodes: [Node::new(
inner: Statement::PropertyAccess( Statement::BinaryOperation {
Box::new(Node { left: Box::new(Node::new(
inner: Statement::Map(vec![( Statement::BinaryOperation {
Node { left: Box::new(Node::new(
inner: Statement::Identifier(Identifier::new("x")), Statement::Map(vec![(
position: (2, 3) Node::new(
}, Statement::Identifier(Identifier::new("x")),
Node { (2, 3)
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)
})
), ),
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() .into()
}) })
) )
@ -1444,7 +1451,7 @@ mod tests {
} }
#[test] #[test]
fn map_with_two_properties() { fn map_with_two_fields() {
let input = "{ x = 42, y = 'foobar' }"; let input = "{ x = 42, y = 'foobar' }";
assert_eq!( assert_eq!(
@ -1469,8 +1476,8 @@ mod tests {
} }
#[test] #[test]
fn map_with_one_property() { fn map_with_one_field() {
let input = "{ x = 42, }"; let input = "{ x = 42 }";
assert_eq!( assert_eq!(
parse(input), parse(input),
@ -1480,7 +1487,7 @@ mod tests {
Node::new(Statement::Identifier(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(Statement::Constant(Value::integer(42)), (6, 8))
)]), )]),
(0, 11) (0, 10)
)] )]
.into() .into()
}) })
@ -1732,8 +1739,8 @@ mod tests {
parse(input), parse(input),
Ok(AbstractSyntaxTree { Ok(AbstractSyntaxTree {
nodes: [Node::new( nodes: [Node::new(
Statement::PropertyAccess( Statement::BinaryOperation {
Box::new(Node::new( left: Box::new(Node::new(
Statement::List(vec![ Statement::List(vec![
Node::new(Statement::Constant(Value::integer(1)), (1, 2)), Node::new(Statement::Constant(Value::integer(1)), (1, 2)),
Node::new(Statement::Constant(Value::integer(2)), (4, 5)), Node::new(Statement::Constant(Value::integer(2)), (4, 5)),
@ -1741,8 +1748,12 @@ mod tests {
]), ]),
(0, 9) (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), (0, 11),
)] )]
.into() .into()
@ -1758,16 +1769,17 @@ mod tests {
parse(input), parse(input),
Ok(AbstractSyntaxTree { Ok(AbstractSyntaxTree {
nodes: [Node::new( nodes: [Node::new(
Statement::PropertyAccess( Statement::BinaryOperation {
Box::new(Node::new( left: Box::new(Node::new(
Statement::Identifier(Identifier::new("a")), Statement::Identifier(Identifier::new("a")),
(0, 1) (0, 1)
)), )),
Box::new(Node::new( operator: Node::new(BinaryOperator::FieldAccess, (1, 2)),
right: Box::new(Node::new(
Statement::Identifier(Identifier::new("b")), Statement::Identifier(Identifier::new("b")),
(2, 3) (2, 3)
)), )),
), },
(0, 3), (0, 3),
)] )]
.into() .into()

View File

@ -17,18 +17,24 @@ pub enum Token<'src> {
String(&'src str), String(&'src str),
// Keywords // Keywords
Bool,
Else, Else,
FloatKeyword,
If, If,
Int,
IsEven, IsEven,
IsOdd, IsOdd,
Length, Length,
ReadLine, ReadLine,
Str,
Struct,
ToString, ToString,
While, While,
WriteLine, WriteLine,
// Symbols // Symbols
Bang, Bang,
Colon,
Comma, Comma,
Dot, Dot,
DoubleAmpersand, DoubleAmpersand,
@ -59,7 +65,9 @@ impl<'src> Token<'src> {
pub fn to_owned(&self) -> TokenOwned { pub fn to_owned(&self) -> TokenOwned {
match self { match self {
Token::Bang => TokenOwned::Bang, Token::Bang => TokenOwned::Bang,
Token::Bool => TokenOwned::Bool,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Colon => TokenOwned::Colon,
Token::Comma => TokenOwned::Comma, Token::Comma => TokenOwned::Comma,
Token::Dot => TokenOwned::Dot, Token::Dot => TokenOwned::Dot,
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand, Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
@ -70,10 +78,12 @@ impl<'src> Token<'src> {
Token::Eof => TokenOwned::Eof, Token::Eof => TokenOwned::Eof,
Token::Equal => TokenOwned::Equal, Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()), Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::FloatKeyword => TokenOwned::FloatKeyword,
Token::Greater => TokenOwned::Greater, Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual, Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
Token::If => TokenOwned::If, Token::If => TokenOwned::If,
Token::Int => TokenOwned::Int,
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()), Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
Token::IsEven => TokenOwned::IsEven, Token::IsEven => TokenOwned::IsEven,
Token::IsOdd => TokenOwned::IsOdd, Token::IsOdd => TokenOwned::IsOdd,
@ -95,6 +105,8 @@ impl<'src> Token<'src> {
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()),
Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
Token::ToString => TokenOwned::ToString, Token::ToString => TokenOwned::ToString,
Token::While => TokenOwned::While, Token::While => TokenOwned::While,
Token::WriteLine => TokenOwned::WriteLine, Token::WriteLine => TokenOwned::WriteLine,
@ -104,11 +116,14 @@ impl<'src> Token<'src> {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
Token::Boolean(boolean_text) => boolean_text, Token::Boolean(boolean_text) => boolean_text,
Token::Float(float_text) => float_text,
Token::Identifier(text) => text, Token::Identifier(text) => text,
Token::Integer(integer_text) => integer_text, Token::Integer(integer_text) => integer_text,
Token::String(text) => text, Token::String(text) => text,
Token::Bang => "!", Token::Bang => "!",
Token::Bool => "bool",
Token::Colon => ":",
Token::Comma => ",", Token::Comma => ",",
Token::Dot => ".", Token::Dot => ".",
Token::DoubleAmpersand => "&&", Token::DoubleAmpersand => "&&",
@ -118,10 +133,11 @@ impl<'src> Token<'src> {
Token::Else => "else", Token::Else => "else",
Token::Eof => "EOF", Token::Eof => "EOF",
Token::Equal => "=", Token::Equal => "=",
Token::Float(_) => "float", Token::FloatKeyword => "float",
Token::Greater => ">", Token::Greater => ">",
Token::GreaterEqual => ">=", Token::GreaterEqual => ">=",
Token::If => "if", Token::If => "if",
Token::Int => "int",
Token::IsEven => "is_even", Token::IsEven => "is_even",
Token::IsOdd => "is_odd", Token::IsOdd => "is_odd",
Token::LeftCurlyBrace => "{", Token::LeftCurlyBrace => "{",
@ -141,6 +157,8 @@ impl<'src> Token<'src> {
Token::Semicolon => ";", Token::Semicolon => ";",
Token::Star => "*", Token::Star => "*",
Token::Slash => "/", Token::Slash => "/",
Token::Str => "str",
Token::Struct => "struct",
Token::ToString => "to_string", Token::ToString => "to_string",
Token::While => "while", Token::While => "while",
Token::WriteLine => "write_line", Token::WriteLine => "write_line",
@ -150,7 +168,9 @@ impl<'src> Token<'src> {
pub fn kind(&self) -> TokenKind { pub fn kind(&self) -> TokenKind {
match self { match self {
Token::Bang => TokenKind::Bang, Token::Bang => TokenKind::Bang,
Token::Bool => TokenKind::Bool,
Token::Boolean(_) => TokenKind::Boolean, Token::Boolean(_) => TokenKind::Boolean,
Token::Colon => TokenKind::Colon,
Token::Comma => TokenKind::Comma, Token::Comma => TokenKind::Comma,
Token::Dot => TokenKind::Dot, Token::Dot => TokenKind::Dot,
Token::DoubleAmpersand => TokenKind::DoubleAmpersand, Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
@ -161,10 +181,12 @@ impl<'src> Token<'src> {
Token::Eof => TokenKind::Eof, Token::Eof => TokenKind::Eof,
Token::Equal => TokenKind::Equal, Token::Equal => TokenKind::Equal,
Token::Float(_) => TokenKind::Float, Token::Float(_) => TokenKind::Float,
Token::FloatKeyword => TokenKind::FloatKeyword,
Token::Greater => TokenKind::Greater, Token::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterOrEqual, Token::GreaterEqual => TokenKind::GreaterOrEqual,
Token::Identifier(_) => TokenKind::Identifier, Token::Identifier(_) => TokenKind::Identifier,
Token::If => TokenKind::If, Token::If => TokenKind::If,
Token::Int => TokenKind::Int,
Token::Integer(_) => TokenKind::Integer, Token::Integer(_) => TokenKind::Integer,
Token::IsEven => TokenKind::IsEven, Token::IsEven => TokenKind::IsEven,
Token::IsOdd => TokenKind::IsOdd, Token::IsOdd => TokenKind::IsOdd,
@ -185,7 +207,9 @@ impl<'src> Token<'src> {
Token::Semicolon => TokenKind::Semicolon, Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star, Token::Star => TokenKind::Star,
Token::Slash => TokenKind::Slash, Token::Slash => TokenKind::Slash,
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String, Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
Token::ToString => TokenKind::ToString, Token::ToString => TokenKind::ToString,
Token::While => TokenKind::While, Token::While => TokenKind::While,
Token::WriteLine => TokenKind::WriteLine, Token::WriteLine => TokenKind::WriteLine,
@ -301,18 +325,23 @@ pub enum TokenOwned {
String(String), String(String),
// Keywords // Keywords
Bool,
Else, Else,
FloatKeyword,
If, If,
Int,
IsEven, IsEven,
IsOdd, IsOdd,
Length, Length,
ReadLine, ReadLine,
Str,
ToString, ToString,
While, While,
WriteLine, WriteLine,
// Symbols // Symbols
Bang, Bang,
Colon,
Comma, Comma,
Dot, Dot,
DoubleAmpersand, DoubleAmpersand,
@ -336,6 +365,7 @@ pub enum TokenOwned {
RightSquareBrace, RightSquareBrace,
Semicolon, Semicolon,
Star, Star,
Struct,
Slash, Slash,
} }
@ -343,7 +373,9 @@ impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenOwned::Bang => Token::Bang.fmt(f), TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::Bool => write!(f, "bool"),
TokenOwned::Boolean(boolean) => write!(f, "{boolean}"), TokenOwned::Boolean(boolean) => write!(f, "{boolean}"),
TokenOwned::Colon => Token::Colon.fmt(f),
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),
@ -354,10 +386,12 @@ impl Display for TokenOwned {
TokenOwned::Eof => Token::Eof.fmt(f), TokenOwned::Eof => Token::Eof.fmt(f),
TokenOwned::Equal => Token::Equal.fmt(f), TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => write!(f, "{float}"), TokenOwned::Float(float) => write!(f, "{float}"),
TokenOwned::FloatKeyword => write!(f, "float"),
TokenOwned::Greater => Token::Greater.fmt(f), TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => write!(f, "{text}"), TokenOwned::Identifier(text) => write!(f, "{text}"),
TokenOwned::If => Token::If.fmt(f), TokenOwned::If => Token::If.fmt(f),
TokenOwned::Int => write!(f, "int"),
TokenOwned::Integer(integer) => write!(f, "{integer}"), TokenOwned::Integer(integer) => write!(f, "{integer}"),
TokenOwned::IsEven => Token::IsEven.fmt(f), TokenOwned::IsEven => Token::IsEven.fmt(f),
TokenOwned::IsOdd => Token::IsOdd.fmt(f), TokenOwned::IsOdd => Token::IsOdd.fmt(f),
@ -378,7 +412,9 @@ impl Display for TokenOwned {
TokenOwned::Semicolon => Token::Semicolon.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::Str => write!(f, "str"),
TokenOwned::String(string) => write!(f, "{string}"), TokenOwned::String(string) => write!(f, "{string}"),
TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::ToString => Token::ToString.fmt(f), TokenOwned::ToString => Token::ToString.fmt(f),
TokenOwned::While => Token::While.fmt(f), TokenOwned::While => Token::While.fmt(f),
TokenOwned::WriteLine => Token::WriteLine.fmt(f), TokenOwned::WriteLine => Token::WriteLine.fmt(f),
@ -400,18 +436,23 @@ pub enum TokenKind {
String, String,
// Keywords // Keywords
Bool,
Else, Else,
FloatKeyword,
If, If,
Int,
IsEven, IsEven,
IsOdd, IsOdd,
Length, Length,
ReadLine, ReadLine,
Str,
ToString, ToString,
While, While,
WriteLine, WriteLine,
// Symbols // Symbols
Bang, Bang,
Colon,
Comma, Comma,
Dot, Dot,
DoubleAmpersand, DoubleAmpersand,
@ -435,6 +476,7 @@ pub enum TokenKind {
RightSquareBrace, RightSquareBrace,
Semicolon, Semicolon,
Star, Star,
Struct,
Slash, Slash,
} }
@ -442,7 +484,9 @@ impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenKind::Bang => Token::Bang.fmt(f), 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::Comma => Token::Comma.fmt(f),
TokenKind::Dot => Token::Dot.fmt(f), TokenKind::Dot => Token::Dot.fmt(f),
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f), TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
@ -452,12 +496,14 @@ impl Display for TokenKind {
TokenKind::Else => Token::Else.fmt(f), TokenKind::Else => Token::Else.fmt(f),
TokenKind::Eof => Token::Eof.fmt(f), TokenKind::Eof => Token::Eof.fmt(f),
TokenKind::Equal => Token::Equal.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::Greater => Token::Greater.fmt(f),
TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenKind::Identifier => write!(f, "identifier"), TokenKind::Identifier => write!(f, "identifier"),
TokenKind::If => Token::If.fmt(f), 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::IsEven => Token::IsEven.fmt(f),
TokenKind::IsOdd => Token::IsOdd.fmt(f), TokenKind::IsOdd => Token::IsOdd.fmt(f),
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f), TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
@ -476,11 +522,85 @@ impl Display for TokenKind {
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f), TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f), TokenKind::Star => Token::Star.fmt(f),
TokenKind::Str => write!(f, "str"),
TokenKind::Slash => Token::Slash.fmt(f), 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::ToString => Token::ToString.fmt(f),
TokenKind::While => Token::While.fmt(f), TokenKind::While => Token::While.fmt(f),
TokenKind::WriteLine => Token::WriteLine.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());
}
}
}
}

View File

@ -31,6 +31,7 @@ pub struct TypeConflict {
pub enum Type { pub enum Type {
Any, Any,
Boolean, Boolean,
Defined(Identifier),
Enum { Enum {
name: Identifier, name: Identifier,
type_parameters: Option<Vec<Type>>, type_parameters: Option<Vec<Type>>,
@ -216,6 +217,7 @@ impl Display for Type {
match self { match self {
Type::Any => write!(f, "any"), Type::Any => write!(f, "any"),
Type::Boolean => write!(f, "bool"), Type::Boolean => write!(f, "bool"),
Type::Defined(identifier) => write!(f, "{identifier}"),
Type::Enum { variants, .. } => { Type::Enum { variants, .. } => {
write!(f, "enum ")?; write!(f, "enum ")?;

View File

@ -348,6 +348,7 @@ impl Display for Value {
} }
ValueInner::Range(range) => write!(f, "{}..{}", range.start, range.end), ValueInner::Range(range) => write!(f, "{}..{}", range.start, range.end),
ValueInner::String(string) => write!(f, "{string}"), ValueInner::String(string) => write!(f, "{string}"),
ValueInner::Struct(r#struct) => write!(f, "{struct}"),
} }
} }
} }
@ -403,6 +404,7 @@ impl Serialize for Value {
tuple_ser.end() tuple_ser.end()
} }
ValueInner::String(string) => serializer.serialize_str(string), 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>), Map(BTreeMap<Identifier, Value>),
Range(Range<i64>), Range(Range<i64>),
String(String), String(String),
Struct(Struct),
} }
impl ValueInner { impl ValueInner {
@ -692,6 +695,7 @@ impl ValueInner {
} }
ValueInner::Range(_) => Type::Range, ValueInner::Range(_) => Type::Range,
ValueInner::String(_) => Type::String, 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, (Range(_), _) => Ordering::Greater,
(String(left), String(right)) => left.cmp(right), (String(left), String(right)) => left.cmp(right),
(String(_), _) => Ordering::Greater, (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)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ValueError { pub enum ValueError {
CannotAdd(Value, Value), CannotAdd(Value, Value),

View File

@ -137,6 +137,58 @@ impl Vm {
return Ok(None); 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_position = left.position;
let left_value = if let Some(value) = self.run_statement(*left)? { let left_value = if let Some(value) = self.run_statement(*left)? {
value value
@ -466,57 +518,6 @@ impl Vm {
Ok(None) 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 } => { Statement::UnaryOperation { operator, operand } => {
let position = operand.position; let position = operand.position;
let value = if let Some(value) = self.run_statement(*operand)? { let value = if let Some(value) = self.run_statement(*operand)? {