Add loop and break
This commit is contained in:
parent
169c1a9e3f
commit
f9480ddc24
@ -153,6 +153,11 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
fn analyze_expression(&self, expression: &Expression) -> Result<(), AnalysisError> {
|
||||
match expression {
|
||||
Expression::Block(block_expression) => self.analyze_block(&block_expression.inner)?,
|
||||
Expression::Break(break_node) => {
|
||||
if let Some(expression) = &break_node.inner {
|
||||
self.analyze_expression(expression)?;
|
||||
}
|
||||
}
|
||||
Expression::Call(call_expression) => {
|
||||
let CallExpression { invoker, arguments } = call_expression.inner.as_ref();
|
||||
|
||||
|
@ -2,20 +2,19 @@ use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
fmt::{self, Display, Formatter},
|
||||
rc::Weak,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
BuiltInFunction, Context, ContextError, FunctionType, Identifier, RangeableType, StructType,
|
||||
Type,
|
||||
};
|
||||
use crate::{BuiltInFunction, Context, FunctionType, Identifier, RangeableType, StructType, Type};
|
||||
|
||||
use super::{AstError, Node, Span, Statement};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Expression {
|
||||
Block(Node<Box<BlockExpression>>),
|
||||
Break(Node<Option<Box<Expression>>>),
|
||||
Call(Node<Box<CallExpression>>),
|
||||
FieldAccess(Node<Box<FieldAccessExpression>>),
|
||||
Grouped(Node<Box<Expression>>),
|
||||
@ -33,6 +32,10 @@ pub enum Expression {
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn r#break(expression: Option<Expression>, position: Span) -> Self {
|
||||
Self::Break(Node::new(expression.map(Box::new), position))
|
||||
}
|
||||
|
||||
pub fn map<T: Into<Vec<(Node<Identifier>, Expression)>>>(pairs: T, position: Span) -> Self {
|
||||
Self::Map(Node::new(
|
||||
Box::new(MapExpression {
|
||||
@ -266,13 +269,15 @@ impl Expression {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_type<'recovered>(
|
||||
&self,
|
||||
context: &'recovered Context,
|
||||
) -> Result<Option<Type>, AstError> {
|
||||
pub fn return_type(&self, context: &Context) -> Result<Option<Type>, AstError> {
|
||||
let return_type = match self {
|
||||
Expression::Block(block_expression) => {
|
||||
return block_expression.inner.return_type(context)
|
||||
Expression::Block(block_expression) => block_expression.inner.return_type(context)?,
|
||||
Expression::Break(expression_node) => {
|
||||
if let Some(expression) = expression_node.inner.as_ref() {
|
||||
expression.return_type(context)?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Expression::Call(call_expression) => {
|
||||
let CallExpression { invoker, .. } = call_expression.inner.as_ref();
|
||||
@ -479,6 +484,7 @@ impl Expression {
|
||||
pub fn position(&self) -> Span {
|
||||
match self {
|
||||
Expression::Block(block) => block.position,
|
||||
Expression::Break(expression_node) => expression_node.position,
|
||||
Expression::Call(call) => call.position,
|
||||
Expression::FieldAccess(field_access) => field_access.position,
|
||||
Expression::Grouped(grouped) => grouped.position,
|
||||
@ -501,6 +507,13 @@ impl Display for Expression {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Expression::Block(block) => write!(f, "{}", block.inner),
|
||||
Expression::Break(break_node) => {
|
||||
if let Some(expression_node) = &break_node.inner {
|
||||
write!(f, "break {};", expression_node)
|
||||
} else {
|
||||
write!(f, "break;")
|
||||
}
|
||||
}
|
||||
Expression::Call(call) => write!(f, "{}", call.inner),
|
||||
Expression::FieldAccess(field_access) => write!(f, "{}", field_access.inner),
|
||||
Expression::Grouped(grouped) => write!(f, "({})", grouped.inner),
|
||||
@ -1027,10 +1040,7 @@ pub enum BlockExpression {
|
||||
}
|
||||
|
||||
impl BlockExpression {
|
||||
fn return_type<'recovered>(
|
||||
&self,
|
||||
context: &'recovered Context,
|
||||
) -> Result<Option<Type>, AstError> {
|
||||
fn return_type(&self, context: &Context) -> Result<Option<Type>, AstError> {
|
||||
match self {
|
||||
BlockExpression::Async(statements) | BlockExpression::Sync(statements) => {
|
||||
if let Some(statement) = statements.last() {
|
||||
|
@ -12,7 +12,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ContextError, Type};
|
||||
use crate::ContextError;
|
||||
|
||||
pub type Span = (usize, usize);
|
||||
|
||||
|
@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Context, ContextError, Identifier, Type};
|
||||
use crate::{Context, Identifier, Type};
|
||||
|
||||
use super::{AstError, Expression, Node, Span};
|
||||
|
||||
@ -28,10 +28,7 @@ impl Statement {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_type<'recovered>(
|
||||
&self,
|
||||
context: &'recovered Context,
|
||||
) -> Result<Option<Type>, AstError> {
|
||||
pub fn return_type(&self, context: &Context) -> Result<Option<Type>, AstError> {
|
||||
match self {
|
||||
Statement::Expression(expression) => expression.return_type(context),
|
||||
Statement::ExpressionNullified(_) => Ok(None),
|
||||
|
@ -1,5 +1,5 @@
|
||||
//! Top-level error handling for the Dust language.
|
||||
use annotate_snippets::{Level, Message, Renderer, Snippet};
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{ast::Span, AnalysisError, LexError, ParseError, RuntimeError};
|
||||
|
@ -424,12 +424,14 @@ impl<'src> Lexer<'src> {
|
||||
"NaN" => Token::Float("NaN"),
|
||||
"async" => Token::Async,
|
||||
"bool" => Token::Bool,
|
||||
"break" => Token::Break,
|
||||
"else" => Token::Else,
|
||||
"false" => Token::Boolean("false"),
|
||||
"float" => Token::FloatKeyword,
|
||||
"if" => Token::If,
|
||||
"int" => Token::Int,
|
||||
"let" => Token::Let,
|
||||
"loop" => Token::Loop,
|
||||
"map" => Token::Map,
|
||||
"mut" => Token::Mut,
|
||||
"struct" => Token::Struct,
|
||||
|
@ -413,9 +413,33 @@ impl<'src> Parser<'src> {
|
||||
error,
|
||||
position: start_position,
|
||||
})?;
|
||||
let statement = Expression::literal(boolean, start_position);
|
||||
let expression = Expression::literal(boolean, start_position);
|
||||
|
||||
Ok(statement)
|
||||
Ok(expression)
|
||||
}
|
||||
Token::Break => {
|
||||
let break_end = self.current_position.1;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let (expression_option, end) = if let Token::Semicolon = self.current_token {
|
||||
// Do not consume the semicolon, allowing it to nullify the expression
|
||||
|
||||
(None, break_end)
|
||||
} else if let Ok(expression) = self.parse_expression(0) {
|
||||
let end = expression.position().1;
|
||||
|
||||
(Some(expression), end)
|
||||
} else {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenKind::Semicolon,
|
||||
actual: self.current_token.to_owned(),
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
let position = (start_position.0, end);
|
||||
|
||||
Ok(Expression::r#break(expression_option, position))
|
||||
}
|
||||
Token::Float(text) => {
|
||||
self.next_token()?;
|
||||
@ -586,6 +610,14 @@ impl<'src> Parser<'src> {
|
||||
expressions.push(expression);
|
||||
}
|
||||
}
|
||||
Token::Loop => {
|
||||
self.next_token()?;
|
||||
|
||||
let block = self.parse_block()?;
|
||||
let position = (start_position.0, block.position.1);
|
||||
|
||||
Ok(Expression::infinite_loop(block, position))
|
||||
}
|
||||
Token::Map => {
|
||||
self.next_token()?;
|
||||
|
||||
@ -1145,6 +1177,27 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn break_loop() {
|
||||
let source = "loop { break; }";
|
||||
|
||||
assert_eq!(
|
||||
parse(source),
|
||||
Ok(AbstractSyntaxTree::with_statements([
|
||||
Statement::Expression(Expression::infinite_loop(
|
||||
Node::new(
|
||||
BlockExpression::Sync(vec![Statement::ExpressionNullified(Node::new(
|
||||
Expression::r#break(None, (7, 12)),
|
||||
(7, 13)
|
||||
))]),
|
||||
(5, 15)
|
||||
),
|
||||
(0, 15)
|
||||
))
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn built_in_function() {
|
||||
let source = "42.to_string()";
|
||||
|
@ -3,10 +3,6 @@ use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct Raw<'src> {
|
||||
data: &'src str,
|
||||
}
|
||||
|
||||
/// Source code token.
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Token<'src> {
|
||||
@ -23,11 +19,13 @@ pub enum Token<'src> {
|
||||
// Keywords
|
||||
Async,
|
||||
Bool,
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Mut,
|
||||
Str,
|
||||
@ -73,6 +71,7 @@ impl<'src> Token<'src> {
|
||||
Token::Bang => TokenOwned::Bang,
|
||||
Token::Bool => TokenOwned::Bool,
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
||||
Token::Break => TokenOwned::Break,
|
||||
Token::Colon => TokenOwned::Colon,
|
||||
Token::Comma => TokenOwned::Comma,
|
||||
Token::Dot => TokenOwned::Dot,
|
||||
@ -97,6 +96,7 @@ impl<'src> Token<'src> {
|
||||
Token::Let => TokenOwned::Let,
|
||||
Token::Less => TokenOwned::Less,
|
||||
Token::LessEqual => TokenOwned::LessOrEqual,
|
||||
Token::Loop => TokenOwned::Loop,
|
||||
Token::Map => TokenOwned::Map,
|
||||
Token::Minus => TokenOwned::Minus,
|
||||
Token::MinusEqual => TokenOwned::MinusEqual,
|
||||
@ -129,6 +129,7 @@ impl<'src> Token<'src> {
|
||||
Token::BangEqual => "!=",
|
||||
Token::Bang => "!",
|
||||
Token::Bool => "bool",
|
||||
Token::Break => "break",
|
||||
Token::Colon => ":",
|
||||
Token::Comma => ",",
|
||||
Token::Dot => ".",
|
||||
@ -150,6 +151,7 @@ impl<'src> Token<'src> {
|
||||
Token::Let => "let",
|
||||
Token::Less => "<",
|
||||
Token::LessEqual => "<=",
|
||||
Token::Loop => "loop",
|
||||
Token::Map => "map",
|
||||
Token::Minus => "-",
|
||||
Token::MinusEqual => "-=",
|
||||
@ -176,6 +178,7 @@ impl<'src> Token<'src> {
|
||||
Token::Bang => TokenKind::Bang,
|
||||
Token::Bool => TokenKind::Bool,
|
||||
Token::Boolean(_) => TokenKind::Boolean,
|
||||
Token::Break => TokenKind::Break,
|
||||
Token::Colon => TokenKind::Colon,
|
||||
Token::Comma => TokenKind::Comma,
|
||||
Token::Dot => TokenKind::Dot,
|
||||
@ -200,6 +203,7 @@ impl<'src> Token<'src> {
|
||||
Token::Let => TokenKind::Let,
|
||||
Token::Less => TokenKind::Less,
|
||||
Token::LessEqual => TokenKind::LessOrEqual,
|
||||
Token::Loop => TokenKind::Loop,
|
||||
Token::Map => TokenKind::Map,
|
||||
Token::Minus => TokenKind::Minus,
|
||||
Token::MinusEqual => TokenKind::MinusEqual,
|
||||
@ -296,11 +300,13 @@ pub enum TokenOwned {
|
||||
|
||||
// Keywords
|
||||
Bool,
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Mut,
|
||||
Str,
|
||||
@ -347,6 +353,7 @@ impl Display for TokenOwned {
|
||||
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
|
||||
TokenOwned::Bool => Token::Bool.fmt(f),
|
||||
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
|
||||
TokenOwned::Break => Token::Break.fmt(f),
|
||||
TokenOwned::Colon => Token::Colon.fmt(f),
|
||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
||||
TokenOwned::Dot => Token::Dot.fmt(f),
|
||||
@ -371,6 +378,7 @@ impl Display for TokenOwned {
|
||||
TokenOwned::Let => Token::Let.fmt(f),
|
||||
TokenOwned::Less => Token::Less.fmt(f),
|
||||
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
|
||||
TokenOwned::Loop => Token::Loop.fmt(f),
|
||||
TokenOwned::Map => Token::Map.fmt(f),
|
||||
TokenOwned::Minus => Token::Minus.fmt(f),
|
||||
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
|
||||
@ -385,7 +393,7 @@ impl Display for TokenOwned {
|
||||
TokenOwned::Star => Token::Star.fmt(f),
|
||||
TokenOwned::Slash => Token::Slash.fmt(f),
|
||||
TokenOwned::Str => Token::Str.fmt(f),
|
||||
TokenOwned::String(string) => write!(f, "{string}"),
|
||||
TokenOwned::String(string) => Token::String(string).fmt(f),
|
||||
TokenOwned::Struct => Token::Struct.fmt(f),
|
||||
TokenOwned::While => Token::While.fmt(f),
|
||||
}
|
||||
@ -408,11 +416,13 @@ pub enum TokenKind {
|
||||
// Keywords
|
||||
Async,
|
||||
Bool,
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Str,
|
||||
While,
|
||||
@ -458,6 +468,7 @@ impl Display for TokenKind {
|
||||
TokenKind::BangEqual => Token::BangEqual.fmt(f),
|
||||
TokenKind::Bool => Token::Bool.fmt(f),
|
||||
TokenKind::Boolean => write!(f, "boolean value"),
|
||||
TokenKind::Break => Token::Break.fmt(f),
|
||||
TokenKind::Colon => Token::Colon.fmt(f),
|
||||
TokenKind::Comma => Token::Comma.fmt(f),
|
||||
TokenKind::Dot => Token::Dot.fmt(f),
|
||||
@ -482,6 +493,7 @@ impl Display for TokenKind {
|
||||
TokenKind::Let => Token::Let.fmt(f),
|
||||
TokenKind::Less => Token::Less.fmt(f),
|
||||
TokenKind::LessOrEqual => Token::LessEqual.fmt(f),
|
||||
TokenKind::Loop => Token::Loop.fmt(f),
|
||||
TokenKind::Map => Token::Map.fmt(f),
|
||||
TokenKind::Minus => Token::Minus.fmt(f),
|
||||
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
|
||||
@ -494,7 +506,7 @@ 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::Str => Token::Str.fmt(f),
|
||||
TokenKind::Slash => Token::Slash.fmt(f),
|
||||
TokenKind::String => write!(f, "string value"),
|
||||
TokenKind::Struct => Token::Struct.fmt(f),
|
||||
@ -507,17 +519,13 @@ impl Display for TokenKind {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
pub fn all_tokens<'src>() -> [Token<'src>; 46] {
|
||||
pub fn all_tokens<'src>() -> [Token<'src>; 47] {
|
||||
[
|
||||
Token::Identifier("foobar"),
|
||||
Token::Boolean("true"),
|
||||
Token::Float("1.0"),
|
||||
Token::Integer("1"),
|
||||
Token::String("string"),
|
||||
Token::Async,
|
||||
Token::Bang,
|
||||
Token::BangEqual,
|
||||
Token::Bool,
|
||||
Token::Break,
|
||||
Token::Colon,
|
||||
Token::Comma,
|
||||
Token::Dot,
|
||||
@ -536,9 +544,9 @@ pub(crate) mod tests {
|
||||
Token::LeftCurlyBrace,
|
||||
Token::LeftParenthesis,
|
||||
Token::LeftSquareBrace,
|
||||
Token::Let,
|
||||
Token::Less,
|
||||
Token::LessEqual,
|
||||
Token::Let,
|
||||
Token::Map,
|
||||
Token::Minus,
|
||||
Token::MinusEqual,
|
||||
@ -550,9 +558,14 @@ pub(crate) mod tests {
|
||||
Token::RightParenthesis,
|
||||
Token::RightSquareBrace,
|
||||
Token::Semicolon,
|
||||
Token::Slash,
|
||||
Token::Star,
|
||||
Token::Str,
|
||||
Token::Slash,
|
||||
Token::Boolean("true"),
|
||||
Token::Float("0.0"),
|
||||
Token::Integer("0"),
|
||||
Token::String("string"),
|
||||
Token::Identifier("foobar"),
|
||||
Token::Struct,
|
||||
Token::While,
|
||||
]
|
||||
|
@ -95,31 +95,39 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Option<Value>, RuntimeError> {
|
||||
let mut previous_value = None;
|
||||
let mut previous_evaluation = None;
|
||||
|
||||
while let Some(statement) = self.abstract_tree.statements.pop_front() {
|
||||
previous_value = self.run_statement(statement, true)?;
|
||||
previous_evaluation = self.run_statement(statement, true)?;
|
||||
}
|
||||
|
||||
Ok(previous_value)
|
||||
match previous_evaluation {
|
||||
Some(Evaluation::Break(value_option)) => Ok(value_option),
|
||||
Some(Evaluation::Return(value_option)) => Ok(value_option),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_statement(
|
||||
&self,
|
||||
statement: Statement,
|
||||
collect_garbage: bool,
|
||||
) -> Result<Option<Value>, RuntimeError> {
|
||||
) -> Result<Option<Evaluation>, RuntimeError> {
|
||||
log::debug!("Running statement: {}", statement);
|
||||
|
||||
let position = statement.position();
|
||||
let result = match statement {
|
||||
Statement::Expression(expression) => self
|
||||
.run_expression(expression, collect_garbage)
|
||||
.map(|evaluation| evaluation.value()),
|
||||
Statement::Expression(expression) => {
|
||||
Ok(Some(self.run_expression(expression, collect_garbage)?))
|
||||
}
|
||||
Statement::ExpressionNullified(expression) => {
|
||||
self.run_expression(expression.inner, collect_garbage)?;
|
||||
let evaluation = self.run_expression(expression.inner, collect_garbage)?;
|
||||
|
||||
Ok(None)
|
||||
if let Evaluation::Break(_) = evaluation {
|
||||
Ok(Some(evaluation))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Statement::Let(let_statement) => {
|
||||
self.run_let_statement(let_statement.inner, collect_garbage)?;
|
||||
@ -218,6 +226,23 @@ impl Vm {
|
||||
let position = expression.position();
|
||||
let evaluation_result = match expression {
|
||||
Expression::Block(Node { inner, .. }) => self.run_block(*inner, collect_garbage),
|
||||
Expression::Break(Node { inner, .. }) => {
|
||||
let break_expression = if let Some(expression) = inner {
|
||||
*expression
|
||||
} else {
|
||||
return Ok(Evaluation::Break(None));
|
||||
};
|
||||
let run_break = self.run_expression(break_expression, collect_garbage)?;
|
||||
let evaluation = match run_break {
|
||||
Evaluation::Break(value_option) => Evaluation::Break(value_option),
|
||||
Evaluation::Return(value_option) => Evaluation::Break(value_option),
|
||||
Evaluation::Constructor(_) => {
|
||||
return Err(RuntimeError::ExpectedValue { position })
|
||||
}
|
||||
};
|
||||
|
||||
Ok(evaluation)
|
||||
}
|
||||
Expression::Call(call) => self.run_call(*call.inner, collect_garbage),
|
||||
Expression::FieldAccess(field_access) => {
|
||||
self.run_field_access(*field_access.inner, collect_garbage)
|
||||
@ -585,8 +610,19 @@ impl Vm {
|
||||
|
||||
fn run_loop(&self, loop_expression: LoopExpression) -> Result<Evaluation, RuntimeError> {
|
||||
match loop_expression {
|
||||
LoopExpression::Infinite { block } => loop {
|
||||
self.run_block(block.inner.clone(), false)?;
|
||||
LoopExpression::Infinite {
|
||||
block: Node { inner, .. },
|
||||
} => match inner {
|
||||
BlockExpression::Sync(statements) => 'outer: loop {
|
||||
for statement in statements.clone() {
|
||||
let evaluation = self.run_statement(statement, false)?;
|
||||
|
||||
if let Some(Evaluation::Break(value_option)) = evaluation {
|
||||
break 'outer Ok(Evaluation::Return(value_option));
|
||||
}
|
||||
}
|
||||
},
|
||||
BlockExpression::Async(_) => todo!(),
|
||||
},
|
||||
LoopExpression::While { condition, block } => {
|
||||
while self
|
||||
@ -849,11 +885,11 @@ impl Vm {
|
||||
let evaluation_result = self.run_statement(statement, false);
|
||||
|
||||
match evaluation_result {
|
||||
Ok(evaluation) => {
|
||||
Ok(evaluation_option) => {
|
||||
if i == statements_length - 1 {
|
||||
let mut final_result = final_result.lock().unwrap();
|
||||
|
||||
*final_result = evaluation;
|
||||
*final_result = evaluation_option;
|
||||
}
|
||||
|
||||
None
|
||||
@ -864,18 +900,27 @@ impl Vm {
|
||||
|
||||
if let Some(error) = error_option {
|
||||
Err(error)
|
||||
} else if let Some(evaluation) = final_result.lock().unwrap().take() {
|
||||
Ok(evaluation)
|
||||
} else {
|
||||
Ok(Evaluation::Return(final_result.lock().unwrap().clone()))
|
||||
Ok(Evaluation::Return(None))
|
||||
}
|
||||
}
|
||||
BlockExpression::Sync(statements) => {
|
||||
let mut previous_value = None;
|
||||
let mut previous_evaluation = None;
|
||||
|
||||
for statement in statements {
|
||||
previous_value = self.run_statement(statement, collect_garbage)?;
|
||||
previous_evaluation = self.run_statement(statement, collect_garbage)?;
|
||||
|
||||
if let Some(Evaluation::Break(value_option)) = previous_evaluation {
|
||||
return Ok(Evaluation::Break(value_option));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Evaluation::Return(previous_value))
|
||||
match previous_evaluation {
|
||||
Some(evaluation) => Ok(evaluation),
|
||||
None => Ok(Evaluation::Return(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -898,7 +943,11 @@ impl Vm {
|
||||
.ok_or(RuntimeError::ExpectedBoolean { position })?;
|
||||
|
||||
if boolean {
|
||||
self.run_block(if_block.inner, collect_garbage)?;
|
||||
let evaluation = self.run_block(if_block.inner, collect_garbage)?;
|
||||
|
||||
if let Evaluation::Break(_) = evaluation {
|
||||
return Ok(evaluation);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Evaluation::Return(None))
|
||||
@ -916,7 +965,11 @@ impl Vm {
|
||||
.ok_or(RuntimeError::ExpectedBoolean { position })?;
|
||||
|
||||
if boolean {
|
||||
self.run_block(if_block.inner, collect_garbage)?;
|
||||
let evaluation = self.run_block(if_block.inner, collect_garbage)?;
|
||||
|
||||
if let Evaluation::Break(_) = evaluation {
|
||||
return Ok(evaluation);
|
||||
}
|
||||
}
|
||||
|
||||
match r#else {
|
||||
@ -931,7 +984,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
enum Evaluation {
|
||||
Break,
|
||||
Break(Option<Value>),
|
||||
Constructor(Constructor),
|
||||
Return(Option<Value>),
|
||||
}
|
||||
@ -1262,6 +1315,13 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn break_loop() {
|
||||
let input = "let mut x = 0; loop { x += 1; if x == 10 { break; } } x";
|
||||
|
||||
assert_eq!(run(input), Ok(Some(Value::mutable(Value::Integer(10)))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_index() {
|
||||
let input = "'foo'[0]";
|
||||
|
Loading…
Reference in New Issue
Block a user