Add loop and break

This commit is contained in:
Jeff 2024-08-20 14:45:43 -04:00
parent 169c1a9e3f
commit f9480ddc24
9 changed files with 197 additions and 57 deletions

View File

@ -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();

View File

@ -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() {

View File

@ -12,7 +12,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{ContextError, Type};
use crate::ContextError;
pub type Span = (usize, usize);

View File

@ -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),

View File

@ -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};

View File

@ -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,

View File

@ -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()";

View File

@ -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,
]

View File

@ -95,32 +95,40 @@ 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)?;
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]";