1
0

Compare commits

..

2 Commits

Author SHA1 Message Date
37e3e1116d Add while loops 2024-08-10 05:23:43 -04:00
1687fd7fe3 Clean up; Add an analyzer test 2024-08-10 04:45:30 -04:00
8 changed files with 274 additions and 28 deletions

View File

@ -57,8 +57,15 @@ pub enum Statement {
}, },
// Property access expression // Property access expression
// TODO: This should be a binary operation
PropertyAccess(Box<Node<Statement>>, Box<Node<Statement>>), PropertyAccess(Box<Node<Statement>>, Box<Node<Statement>>),
// Loops
While {
condition: Box<Node<Statement>>,
body: Box<Node<Statement>>,
},
// Identifier expression // Identifier expression
Identifier(Identifier), Identifier(Identifier),
@ -78,7 +85,27 @@ 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, .. } => left.inner.expected_type(context), Statement::BinaryOperation {
left,
operator,
right,
} => match operator.inner {
BinaryOperator::Add
| BinaryOperator::Divide
| BinaryOperator::Modulo
| BinaryOperator::Multiply
| BinaryOperator::Subtract => Some(left.inner.expected_type(context)?),
BinaryOperator::Equal
| BinaryOperator::Greater
| BinaryOperator::GreaterOrEqual
| BinaryOperator::Less
| BinaryOperator::LessOrEqual
| BinaryOperator::And
| BinaryOperator::Or => Some(Type::Boolean),
BinaryOperator::Assign | BinaryOperator::AddAssign => 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)),
Statement::FunctionCall { function, .. } => function.inner.expected_type(context), Statement::FunctionCall { function, .. } => function.inner.expected_type(context),
@ -102,8 +129,9 @@ impl Statement {
Some(Type::Map(types)) Some(Type::Map(types))
} }
Statement::PropertyAccess(_, _) => None,
Statement::Nil(_) => None, Statement::Nil(_) => None,
Statement::PropertyAccess(_, _) => None,
Statement::While { .. } => None,
} }
} }
} }
@ -231,6 +259,9 @@ impl Display for Statement {
} }
Statement::Nil(node) => write!(f, "{node};"), Statement::Nil(node) => write!(f, "{node};"),
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"), Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
Statement::While { condition, body } => {
write!(f, "while {condition} {body}")
}
} }
} }
} }

View File

@ -160,7 +160,9 @@ impl<'a> Analyzer<'a> {
} }
} }
Statement::Identifier(identifier) => { Statement::Identifier(identifier) => {
if !self.context.contains(identifier) { let exists = self.context.add_allowed_use(identifier);
if !exists {
return Err(AnalyzerError::UndefinedVariable { return Err(AnalyzerError::UndefinedVariable {
identifier: node.clone(), identifier: node.clone(),
}); });
@ -176,6 +178,9 @@ impl<'a> Analyzer<'a> {
self.analyze_node(value_node)?; self.analyze_node(value_node)?;
} }
} }
Statement::Nil(node) => {
self.analyze_node(node)?;
}
Statement::PropertyAccess(left, right) => { Statement::PropertyAccess(left, right) => {
if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) = if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) =
&left.inner &left.inner
@ -202,8 +207,17 @@ impl<'a> Analyzer<'a> {
self.analyze_node(right)?; self.analyze_node(right)?;
} }
Statement::Nil(node) => { Statement::While { condition, body } => {
self.analyze_node(node)?; self.analyze_node(condition)?;
self.analyze_node(body)?;
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
} else {
return Err(AnalyzerError::ExpectedBoolean {
actual: condition.as_ref().clone(),
position: condition.position,
});
}
} }
} }
@ -241,11 +255,6 @@ pub enum AnalyzerError {
actual: Node<Statement>, actual: Node<Statement>,
position: Span, position: Span,
}, },
ExpectedSameType {
left: Node<Statement>,
right: Node<Statement>,
position: Span,
},
ExpectedString { ExpectedString {
actual: Node<Statement>, actual: Node<Statement>,
position: (usize, usize), position: (usize, usize),
@ -278,7 +287,6 @@ impl AnalyzerError {
AnalyzerError::ExpectedInteger { position, .. } => *position, AnalyzerError::ExpectedInteger { position, .. } => *position,
AnalyzerError::ExpectedIntegerOrFloat { position, .. } => *position, AnalyzerError::ExpectedIntegerOrFloat { position, .. } => *position,
AnalyzerError::ExpectedIntegerFloatOrString { position, .. } => *position, AnalyzerError::ExpectedIntegerFloatOrString { position, .. } => *position,
AnalyzerError::ExpectedSameType { position, .. } => *position,
AnalyzerError::ExpectedString { position, .. } => *position, AnalyzerError::ExpectedString { position, .. } => *position,
AnalyzerError::UndefinedVariable { identifier } => identifier.position, AnalyzerError::UndefinedVariable { identifier } => identifier.position,
AnalyzerError::UnexpectedIdentifier { position, .. } => *position, AnalyzerError::UnexpectedIdentifier { position, .. } => *position,
@ -313,9 +321,6 @@ impl Display for AnalyzerError {
AnalyzerError::ExpectedIntegerFloatOrString { actual, .. } => { AnalyzerError::ExpectedIntegerFloatOrString { actual, .. } => {
write!(f, "Expected integer, float, or string, found {}", actual) write!(f, "Expected integer, float, or string, found {}", actual)
} }
AnalyzerError::ExpectedSameType { left, right, .. } => {
write!(f, "Expected same type, found {} and {}", left, right)
}
AnalyzerError::ExpectedString { actual, .. } => { AnalyzerError::ExpectedString { actual, .. } => {
write!(f, "Expected string, found {}", actual) write!(f, "Expected string, found {}", actual)
} }
@ -341,6 +346,31 @@ mod tests {
use super::*; use super::*;
#[test]
fn math_requires_same_types() {
let abstract_tree = AbstractSyntaxTree {
nodes: [Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(Statement::Constant(Value::integer(1)), (0, 1))),
operator: Node::new(BinaryOperator::Add, (1, 2)),
right: Box::new(Node::new(Statement::Constant(Value::float(1.0)), (3, 4))),
},
(0, 2),
)]
.into(),
};
let mut context = Context::new();
let mut analyzer = Analyzer::new(&abstract_tree, &mut context);
assert_eq!(
analyzer.analyze(),
Err(AnalyzerError::ExpectedInteger {
actual: Node::new(Statement::Constant(Value::float(1.0)), (3, 4)),
position: (3, 4)
})
)
}
#[test] #[test]
fn float_plus_integer() { fn float_plus_integer() {
let abstract_tree = AbstractSyntaxTree { let abstract_tree = AbstractSyntaxTree {

View File

@ -31,9 +31,13 @@ impl Context {
} }
} }
pub fn get_value(&self, identifier: &Identifier) -> Option<&Value> { pub fn use_value(&mut self, identifier: &Identifier) -> Option<&Value> {
match self.variables.get(identifier) { match self.variables.get_mut(identifier) {
Some((VariableData::Value(value), _)) => Some(value), Some((VariableData::Value(value), usage_data)) => {
usage_data.used += 1;
Some(value)
}
_ => None, _ => None,
} }
} }
@ -63,6 +67,16 @@ impl Context {
self.variables self.variables
.retain(|_, (_, usage_data)| usage_data.used < usage_data.allowed_uses); .retain(|_, (_, usage_data)| usage_data.used < usage_data.allowed_uses);
} }
pub fn add_allowed_use(&mut self, identifier: &Identifier) -> bool {
if let Some((_, usage_data)) = self.variables.get_mut(identifier) {
usage_data.allowed_uses += 1;
true
} else {
false
}
}
} }
impl Default for Context { impl Default for Context {

View File

@ -403,6 +403,7 @@ impl Lexer {
"length" => Token::Length, "length" => Token::Length,
"NaN" => Token::Float("NaN"), "NaN" => Token::Float("NaN"),
"read_line" => Token::ReadLine, "read_line" => Token::ReadLine,
"while" => Token::While,
"write_line" => Token::WriteLine, "write_line" => Token::WriteLine,
_ => Token::Identifier(string), _ => Token::Identifier(string),
}; };
@ -475,6 +476,27 @@ impl Display for LexError {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn while_loop() {
let input = "while x < 10 { x += 1 }";
assert_eq!(
lex(input),
Ok(vec![
(Token::While, (0, 5)),
(Token::Identifier("x"), (6, 7)),
(Token::Less, (8, 9)),
(Token::Integer("10"), (10, 12)),
(Token::LeftCurlyBrace, (13, 14)),
(Token::Identifier("x"), (15, 16)),
(Token::PlusEqual, (17, 19)),
(Token::Integer("1"), (20, 21)),
(Token::RightCurlyBrace, (22, 23)),
(Token::Eof, (23, 23)),
])
)
}
#[test] #[test]
fn add_assign() { fn add_assign() {
let input = "x += 42"; let input = "x += 42";

View File

@ -416,6 +416,31 @@ impl<'src> Parser<'src> {
left_position, left_position,
)) ))
} }
(Token::While, left_position) => {
self.next_token()?;
let condition = self.parse_statement(0)?;
if let Token::LeftCurlyBrace = self.current.0 {
} else {
return Err(ParseError::ExpectedToken {
expected: TokenOwned::LeftCurlyBrace,
actual: self.current.0.to_owned(),
position: self.current.1,
});
}
let body = self.parse_block()?;
let body_end = body.position.1;
Ok(Node::new(
Statement::While {
condition: Box::new(condition),
body: Box::new(body),
},
(left_position.0, body_end),
))
}
_ => Err(ParseError::UnexpectedToken { _ => Err(ParseError::UnexpectedToken {
actual: self.current.0.to_owned(), actual: self.current.0.to_owned(),
position: self.current.1, position: self.current.1,
@ -498,6 +523,39 @@ impl<'src> Parser<'src> {
Ok(left) Ok(left)
} }
} }
fn parse_block(&mut self) -> Result<Node<Statement>, ParseError> {
let left_start = self.current.1 .0;
if let Token::LeftCurlyBrace = self.current.0 {
self.next_token()?;
} else {
return Err(ParseError::ExpectedToken {
expected: TokenOwned::LeftCurlyBrace,
actual: self.current.0.to_owned(),
position: self.current.1,
});
}
let mut statements = Vec::new();
loop {
if let Token::RightCurlyBrace = self.current.0 {
let right_end = self.current.1 .1;
self.next_token()?;
return Ok(Node::new(
Statement::Block(statements),
(left_start, right_end),
));
}
let statement = self.parse_statement(0)?;
statements.push(statement);
}
}
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -583,6 +641,54 @@ mod tests {
use super::*; use super::*;
#[test]
fn while_loop() {
let input = "while x < 10 { x += 1 }";
assert_eq!(
parse(input),
Ok(AbstractSyntaxTree {
nodes: [Node::new(
Statement::While {
condition: Box::new(Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(
Statement::Identifier(Identifier::new("x")),
(6, 7)
)),
operator: Node::new(BinaryOperator::Less, (8, 9)),
right: Box::new(Node::new(
Statement::Constant(Value::integer(10)),
(10, 12)
)),
},
(6, 12)
)),
body: Box::new(Node::new(
Statement::Block(vec![Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(
Statement::Identifier(Identifier::new("x")),
(15, 16)
)),
operator: Node::new(BinaryOperator::AddAssign, (17, 19)),
right: Box::new(Node::new(
Statement::Constant(Value::integer(1)),
(20, 21)
)),
},
(15, 21)
)]),
(13, 23)
)),
},
(0, 23)
)]
.into()
})
);
}
#[test] #[test]
fn add_assign() { fn add_assign() {
let input = "a += 1"; let input = "a += 1";

View File

@ -21,6 +21,7 @@ pub enum Token<'src> {
IsOdd, IsOdd,
Length, Length,
ReadLine, ReadLine,
While,
WriteLine, WriteLine,
// Symbols // Symbols
@ -85,6 +86,7 @@ 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::While => TokenOwned::While,
Token::WriteLine => TokenOwned::WriteLine, Token::WriteLine => TokenOwned::WriteLine,
} }
} }
@ -124,6 +126,7 @@ impl<'src> Token<'src> {
Token::Star => "*", Token::Star => "*",
Token::String(_) => "string", Token::String(_) => "string",
Token::Slash => "/", Token::Slash => "/",
Token::While => "while",
Token::WriteLine => "write_line", Token::WriteLine => "write_line",
} }
} }
@ -205,6 +208,7 @@ impl<'src> PartialEq for Token<'src> {
(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,
(Token::While, Token::While) => true,
(Token::WriteLine, Token::WriteLine) => true, (Token::WriteLine, Token::WriteLine) => true,
_ => false, _ => false,
} }
@ -231,6 +235,7 @@ pub enum TokenOwned {
IsOdd, IsOdd,
Length, Length,
ReadLine, ReadLine,
While,
WriteLine, WriteLine,
// Symbols // Symbols
@ -295,6 +300,7 @@ impl Display for TokenOwned {
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}"),
TokenOwned::While => Token::While.fmt(f),
TokenOwned::WriteLine => Token::WriteLine.fmt(f), TokenOwned::WriteLine => Token::WriteLine.fmt(f),
} }
} }

View File

@ -6,8 +6,9 @@ use std::{
}; };
use crate::{ use crate::{
abstract_tree::BinaryOperator, parse, AbstractSyntaxTree, Analyzer, AnalyzerError, abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer,
BuiltInFunctionError, Context, Node, ParseError, Span, Statement, Value, ValueError, AnalyzerError, BuiltInFunctionError, Context, Node, ParseError, Span, Statement, Value,
ValueError,
}; };
pub fn run(input: &str, context: &mut Context) -> Result<Option<Value>, VmError> { pub fn run(input: &str, context: &mut Context) -> Result<Option<Value>, VmError> {
@ -90,7 +91,7 @@ impl Vm {
position: right_position, position: right_position,
}); });
}; };
let left_value = context.get_value(&identifier).ok_or_else(|| { let left_value = context.use_value(&identifier).ok_or_else(|| {
VmError::UndefinedVariable { VmError::UndefinedVariable {
identifier: Node::new( identifier: Node::new(
Statement::Identifier(identifier.clone()), Statement::Identifier(identifier.clone()),
@ -236,7 +237,7 @@ impl Vm {
Ok(function.clone().call(None, value_parameters, context)?) Ok(function.clone().call(None, value_parameters, context)?)
} }
Statement::Identifier(identifier) => { Statement::Identifier(identifier) => {
if let Some(value) = context.get_value(&identifier) { if let Some(value) = context.use_value(&identifier) {
Ok(Some(value.clone())) Ok(Some(value.clone()))
} else { } else {
Err(VmError::UndefinedVariable { Err(VmError::UndefinedVariable {
@ -348,6 +349,31 @@ impl Vm {
position: right_span, position: right_span,
}) })
} }
Statement::While { condition, body } => {
let mut return_value = None;
let condition_position = condition.position;
while let Some(condition_value) = self.run_node(*condition.clone(), context)? {
if let ValueInner::Boolean(condition_value) = condition_value.inner().as_ref() {
if !condition_value {
break;
}
} else {
return Err(VmError::ExpectedBoolean {
position: condition_position,
});
}
return_value = self.run_node(*body.clone(), context)?;
if return_value.is_some() {
break;
}
}
Ok(return_value)
}
} }
} }
} }
@ -367,6 +393,9 @@ pub enum VmError {
error: BuiltInFunctionError, error: BuiltInFunctionError,
position: Span, position: Span,
}, },
ExpectedBoolean {
position: Span,
},
ExpectedIdentifier { ExpectedIdentifier {
position: Span, position: Span,
}, },
@ -398,6 +427,7 @@ impl VmError {
Self::ParseError(parse_error) => parse_error.position(), Self::ParseError(parse_error) => parse_error.position(),
Self::ValueError { position, .. } => *position, Self::ValueError { position, .. } => *position,
Self::BuiltInFunctionError { position, .. } => *position, Self::BuiltInFunctionError { position, .. } => *position,
Self::ExpectedBoolean { position } => *position,
Self::ExpectedIdentifier { position } => *position, Self::ExpectedIdentifier { position } => *position,
Self::ExpectedIdentifierOrInteger { position } => *position, Self::ExpectedIdentifierOrInteger { position } => *position,
Self::ExpectedInteger { position } => *position, Self::ExpectedInteger { position } => *position,
@ -442,6 +472,9 @@ impl Display for VmError {
Self::BuiltInFunctionError { error, .. } => { Self::BuiltInFunctionError { error, .. } => {
write!(f, "{}", error) write!(f, "{}", error)
} }
Self::ExpectedBoolean { position } => {
write!(f, "Expected a boolean at position: {:?}", position)
}
Self::ExpectedFunction { actual, position } => { Self::ExpectedFunction { actual, position } => {
write!( write!(
f, f,
@ -479,6 +512,13 @@ impl Display for VmError {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn while_loop() {
let input = "x = 0; while x < 5 { x += 1; } x";
assert_eq!(run(input, &mut Context::new()), Ok(Some(Value::integer(5))));
}
#[test] #[test]
fn add_assign() { fn add_assign() {
let input = "x = 1; x += 1; x"; let input = "x = 1; x += 1; x";

View File

@ -1,5 +1,3 @@
use std.io
count = 1 count = 1
while count <= 15 { while count <= 15 {
@ -16,8 +14,7 @@ while count <= 15 {
count as str count as str
} }
io.write_line(output) write_line(output)
count += 1 count += 1
} }