Compare commits
2 Commits
a46e5dd365
...
37e3e1116d
Author | SHA1 | Date | |
---|---|---|---|
37e3e1116d | |||
1687fd7fe3 |
@ -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}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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";
|
||||||
|
@ -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";
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
use std.io
|
|
||||||
|
|
||||||
count = 1
|
count = 1
|
||||||
|
|
||||||
while count <= 15 {
|
while count <= 15 {
|
||||||
divides_by_3 = count % 3 == 0
|
divides_by_3 = count % 3 == 0
|
||||||
divides_by_5 = count % 5 == 0
|
divides_by_5 = count % 5 == 0
|
||||||
|
|
||||||
output = if divides_by_3 && divides_by_5 {
|
output = if divides_by_3 && divides_by_5 {
|
||||||
'fizzbuzz'
|
'fizzbuzz'
|
||||||
} else if divides_by_3 {
|
} else if divides_by_3 {
|
||||||
'fizz'
|
'fizz'
|
||||||
} else if divides_by_5 {
|
} else if divides_by_5 {
|
||||||
'buzz'
|
'buzz'
|
||||||
@ -16,8 +14,7 @@ while count <= 15 {
|
|||||||
count as str
|
count as str
|
||||||
}
|
}
|
||||||
|
|
||||||
io.write_line(output)
|
write_line(output)
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user