From 4ad80ceb47f64628fe82c6c7a51d7d573c02c1a0 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Mon, 5 Feb 2024 14:37:14 -0600 Subject: [PATCH] Possibility to bubble returns to parent caller --- runtime/src/controlflow.rs | 29 +++++++++ runtime/src/eval.rs | 121 ++++++++++++++++++++++++------------- runtime/src/fiber.rs | 5 +- 3 files changed, 111 insertions(+), 44 deletions(-) diff --git a/runtime/src/controlflow.rs b/runtime/src/controlflow.rs index 8a23755..a4d7dbe 100644 --- a/runtime/src/controlflow.rs +++ b/runtime/src/controlflow.rs @@ -6,6 +6,11 @@ use crate::{Exception, Value}; /// Control flow is handled in the interpreter entirely using return values. +/// +/// When an expression is evaluated normally, it returns `Continue` with the +/// result of the expression. When normal control flow is interrupted with another +/// action, it returns `Break` with the action that took place, either an early +/// return or an exception thrown. pub(crate) type ControlFlow = std::ops::ControlFlow; /// When performing an early exit of normal control flow, this is the action being @@ -20,10 +25,34 @@ pub(crate) enum BreakAction { Throw(Exception), } +pub(crate) trait Resolve { + fn resolve(self) -> Result; +} + +impl Resolve for ControlFlow { + fn resolve(self) -> Result { + match self { + ControlFlow::Continue(value) => Ok(value), + ControlFlow::Break(BreakAction::Return(value)) => Ok(value), + ControlFlow::Break(BreakAction::Throw(exception)) => Err(exception), + } + } +} + macro_rules! throw_cf { ($($arg:tt)*) => { return ::std::ops::ControlFlow::Break(BreakAction::Throw($crate::Exception::from(format!($($arg)*)))) }; } +macro_rules! break_return { + ($value:expr) => { + return ::std::ops::ControlFlow::Break(BreakAction::Return($value)) + }; + () => { + return ::std::ops::ControlFlow::Break(BreakAction::Return(Default::default())) + }; +} + +pub(crate) use break_return; pub(crate) use throw_cf; diff --git a/runtime/src/eval.rs b/runtime/src/eval.rs index 3fca36d..292a528 100644 --- a/runtime/src/eval.rs +++ b/runtime/src/eval.rs @@ -2,7 +2,7 @@ use crate::{ closure::Closure, - controlflow::{throw_cf, BreakAction, ControlFlow}, + controlflow::{break_return, throw_cf, BreakAction, ControlFlow}, exceptions::Exception, fiber::Fiber, foreign::ForeignFn, @@ -15,15 +15,14 @@ use crate::{ }; use futures::future::try_join_all; use gc::Gc; -use riptide_syntax::{ - parse, - ast::*, - source::*, -}; +use riptide_syntax::{ast::*, parse, source::*}; use std::ops::ControlFlow::Continue; /// Compile the given source code as a closure. -pub(crate) fn compile(fiber: &mut Fiber, file: impl Into) -> Result { +pub(crate) fn compile( + fiber: &mut Fiber, + file: impl Into, +) -> Result { let file = file.into(); let file_name = file.name().to_string(); @@ -47,16 +46,27 @@ fn compile_block(fiber: &mut Fiber, block: Block) -> Closure { } /// Invoke the given value as a function with the given arguments. -pub(crate) async fn invoke(fiber: &mut Fiber, value: &Value, args: Vec) -> Result { +pub(crate) async fn invoke( + fiber: &mut Fiber, + value: &Value, + args: Vec, +) -> ControlFlow { match value { - Value::Block(closure) => invoke_closure(fiber, closure, args, table!(), table!()).await, + Value::Block(closure) => invoke_closure(fiber, closure, args, table!(), table!(), false).await, Value::ForeignFn(function) => invoke_native(fiber, function, args).await, - value => throw!("cannot invoke '{:?}' as a function", value), + value => throw_cf!("cannot invoke '{:?}' as a function", value), } } /// Invoke a block with an array of arguments. -pub(crate) async fn invoke_closure(fiber: &mut Fiber, closure: &Closure, args: Vec, bindings: Table, cvars: Table) -> Result { +pub(crate) async fn invoke_closure( + fiber: &mut Fiber, + closure: &Closure, + args: Vec, + bindings: Table, + cvars: Table, + bubble_up_return: bool, +) -> ControlFlow { let scope = Scope { name: format!("", closure.block.span.as_ref().unwrap()), bindings, @@ -73,7 +83,7 @@ pub(crate) async fn invoke_closure(fiber: &mut Fiber, closure: &Closure, args: V // Bind arguments to any named params. if let Some(named_params) = closure.block.named_params.as_ref() { for named_param in named_params.iter() { - scope.set(named_param.as_bytes(), args.next().unwrap_or(Value::Nil)); + scope.set(named_param.as_bytes(), args.next().unwrap_or_default()); } } @@ -100,7 +110,11 @@ pub(crate) async fn invoke_closure(fiber: &mut Fiber, closure: &Closure, args: V Continue(return_value) => last_return_value = return_value, // Stop block execution and return the given value. - ControlFlow::Break(BreakAction::Return(value)) => return Ok(value), + ControlFlow::Break(BreakAction::Return(value)) => if bubble_up_return { + break_return!(value); + } else { + return Continue(value); + }, // Exception thrown; our scope guard from earlier will ensure that // the stack is unwound. @@ -109,16 +123,20 @@ pub(crate) async fn invoke_closure(fiber: &mut Fiber, closure: &Closure, args: V exception.backtrace = fiber.backtrace().cloned().collect(); } - return Err(exception); - }, + return ControlFlow::Break(BreakAction::Throw(exception)); + } } } - Ok(last_return_value) + Continue(last_return_value) } /// Invoke a native function. -async fn invoke_native(fiber: &mut Fiber, function: &ForeignFn, args: Vec) -> Result { +async fn invoke_native( + fiber: &mut Fiber, + function: &ForeignFn, + args: Vec, +) -> ControlFlow { // Push the scope onto the stack. fiber.stack.push(Gc::new(Scope { name: String::from(""), @@ -134,12 +152,12 @@ async fn invoke_native(fiber: &mut Fiber, function: &ForeignFn, args: Vec fiber.stack.pop(); }); - function.call(*fiber, args).await.map_err(|mut e| { + result_to_control_flow(function.call(*fiber, args).await.map_err(|mut e| { if e.backtrace.is_empty() { e.backtrace = fiber.backtrace().cloned().collect(); } e - }) + })) } #[async_recursion::async_recursion(?Send)] @@ -148,14 +166,14 @@ async fn evaluate_statement(fiber: &mut Fiber, statement: Statement) -> ControlF Statement::Import(statement) => { evaluate_import_statement(fiber, statement).await?; Continue(Default::default()) - }, - Statement::Return(None) => ControlFlow::Break(BreakAction::Return(Value::Nil)), + } + Statement::Return(None) => break_return!(), Statement::Return(Some(expr)) => { let value = evaluate_expr(fiber, expr).await?; - ControlFlow::Break(BreakAction::Return(value)) - }, + break_return!(value) + } Statement::Pipeline(pipeline) => evaluate_pipeline(fiber, pipeline).await, - Statement::Assignment(AssignmentStatement {target, value}) => { + Statement::Assignment(AssignmentStatement { target, value }) => { match target { AssignmentTarget::MemberAccess(member_access) => { if let Some(table) = evaluate_expr(fiber, *member_access.0).await?.as_table() { @@ -180,11 +198,14 @@ async fn evaluate_statement(fiber: &mut Fiber, statement: Statement) -> ControlF } Continue(Value::Nil) - }, + } } } -async fn evaluate_import_statement(fiber: &mut Fiber, statement: ImportStatement) -> ControlFlow<()> { +async fn evaluate_import_statement( + fiber: &mut Fiber, + statement: ImportStatement, +) -> ControlFlow<()> { let module_contents = result_to_control_flow(fiber.load_module(statement.path.as_str()).await)?; match statement.clause { @@ -246,8 +267,8 @@ async fn evaluate_pipeline(fiber: &mut Fiber, pipeline: Pipeline) -> ControlFlow #[async_recursion::async_recursion(?Send)] async fn evaluate_call(fiber: &mut Fiber, call: Call) -> ControlFlow { - result_to_control_flow(match call { - Call::Named {function, args} => { + match call { + Call::Named { function, args } => { let name = function; let function = fiber.get(&name); let arg_values = evaluate_call_args(fiber, args).await?; @@ -255,16 +276,16 @@ async fn evaluate_call(fiber: &mut Fiber, call: Call) -> ControlFlow { if !function.is_nil() { invoke(fiber, &function, arg_values).await } else { - crate::io::process::command(fiber, &name, &arg_values).await + result_to_control_flow(crate::io::process::command(fiber, &name, &arg_values).await) } - }, - Call::Unnamed {function, args} => { + } + Call::Unnamed { function, args } => { let function = evaluate_expr(fiber, *function).await?; let arg_values = evaluate_call_args(fiber, args).await?; invoke(fiber, &function, arg_values).await - }, - }) + } + } } async fn evaluate_call_args(fiber: &mut Fiber, args: Vec) -> ControlFlow> { @@ -281,7 +302,10 @@ async fn evaluate_call_args(fiber: &mut Fiber, args: Vec) -> ControlFlo arg_values.push(item.clone()); } } else if !splat_items.is_nil() { - throw_cf!("cannot expand a {} value as function arguments", splat_items.type_name()); + throw_cf!( + "cannot expand a {} value as function arguments", + splat_items.type_name() + ); } } } @@ -302,7 +326,9 @@ async fn evaluate_expr(fiber: &mut Fiber, expr: Expr) -> ControlFlow { Expr::Table(literal) => evaluate_table_literal(fiber, literal).await, Expr::List(list) => evaluate_list_literal(fiber, list).await, Expr::InterpolatedString(string) => evaluate_interpolated_string(fiber, string).await, - Expr::MemberAccess(MemberAccess(lhs, rhs)) => evaluate_member_access(fiber, *lhs, rhs).await, + Expr::MemberAccess(MemberAccess(lhs, rhs)) => { + evaluate_member_access(fiber, *lhs, rhs).await + } Expr::Block(block) => evaluate_block(fiber, block), Expr::Subroutine(subroutine) => evaluate_subroutine(fiber, subroutine), Expr::Pipeline(pipeline) => evaluate_pipeline(fiber, pipeline).await, @@ -338,10 +364,13 @@ async fn evaluate_cvar_scope(fiber: &mut Fiber, cvar_scope: CvarScope) -> Contro cvar_scope.name.0 => evaluate_expr(fiber, *cvar_scope.value).await?, }; - result_to_control_flow(invoke_closure(fiber, &closure, vec![], cvars, table!()).await) + invoke_closure(fiber, &closure, vec![], cvars, table!(), false).await } -async fn evaluate_substitution(fiber: &mut Fiber, substitution: Substitution) -> ControlFlow { +async fn evaluate_substitution( + fiber: &mut Fiber, + substitution: Substitution, +) -> ControlFlow { match substitution { Substitution::Variable(name) => Continue(fiber.get(name)), Substitution::Pipeline(pipeline) => evaluate_pipeline(fiber, pipeline).await, @@ -372,14 +401,22 @@ async fn evaluate_list_literal(fiber: &mut Fiber, list: ListLiteral) -> ControlF Continue(Value::List(values)) } -async fn evaluate_interpolated_string(fiber: &mut Fiber, string: InterpolatedString) -> ControlFlow { +async fn evaluate_interpolated_string( + fiber: &mut Fiber, + string: InterpolatedString, +) -> ControlFlow { let mut rendered = String::new(); for part in string.0.into_iter() { - rendered.push_str(match part { - InterpolatedStringPart::String(part) => part, - InterpolatedStringPart::Substitution(sub) => evaluate_substitution(fiber, sub).await?.to_string(), - }.as_str()); + rendered.push_str( + match part { + InterpolatedStringPart::String(part) => part, + InterpolatedStringPart::Substitution(sub) => { + evaluate_substitution(fiber, sub).await?.to_string() + } + } + .as_str(), + ); } Continue(Value::from(rendered)) diff --git a/runtime/src/fiber.rs b/runtime/src/fiber.rs index 048aabf..7398c07 100644 --- a/runtime/src/fiber.rs +++ b/runtime/src/fiber.rs @@ -1,4 +1,5 @@ use crate::{ + controlflow::Resolve, eval, exceptions::Exception, io::{IoContext, Input, Output}, @@ -168,12 +169,12 @@ impl Fiber { ) -> Result { let closure = eval::compile(self, file)?; - eval::invoke_closure(self, &closure, vec![], scope, Default::default()).await + eval::invoke_closure(self, &closure, vec![], scope, Default::default(), false).await.resolve() } /// Invoke the given value as a function with the given arguments. pub async fn invoke(&mut self, value: &Value, args: &[Value]) -> Result { - eval::invoke(self, value, args.to_vec()).await + eval::invoke(self, value, args.to_vec()).await.resolve() } /// Lookup a normal variable name in the current scope.