diff options
| author | Mel <einebeere@gmail.com> | 2021-11-14 22:31:34 +0100 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2021-11-14 22:31:34 +0100 |
| commit | 2a3ab5c161ac98cb3c6326173e5ed78089a9ed68 (patch) | |
| tree | 2a25e0017f6b90f4296e1d9f5d10980a345e1373 /src/interpret/value.rs | |
| parent | 89fcc9a395f2aa9744d507d79d29aa117b89fa7e (diff) | |
| download | rabbithole-2a3ab5c161ac98cb3c6326173e5ed78089a9ed68.tar.zst rabbithole-2a3ab5c161ac98cb3c6326173e5ed78089a9ed68.zip | |
Function calling and the ensuing scope shenanigans
Diffstat (limited to 'src/interpret/value.rs')
| -rw-r--r-- | src/interpret/value.rs | 66 |
1 files changed, 64 insertions, 2 deletions
diff --git a/src/interpret/value.rs b/src/interpret/value.rs index f923742..dd8ad54 100644 --- a/src/interpret/value.rs +++ b/src/interpret/value.rs @@ -1,7 +1,12 @@ -use crate::parse::ast::nodes::FnNode; +use crate::parse::ast::{expression::Expression, nodes::FnNode}; use std::{cell::RefCell, fmt::Display, rc::Rc}; use thiserror::Error; +use super::{ + scope::ScopeChain, + walker::{Walker, WalkerError}, +}; + type ReferenceOnCopy<T> = Rc<RefCell<T>>; #[derive(Clone, Debug)] @@ -11,7 +16,7 @@ pub enum Value { Int(i64), Bool(bool), Array(ReferenceOnCopy<Vec<Value>>), - Fn(ReferenceOnCopy<FnNode>), + Fn(ReferenceOnCopy<FnValue>), Void, } @@ -214,6 +219,53 @@ impl Value { x => Err(OperationError::ArrayType(x.clone())), } } + + pub fn call(&self, arguments: Vec<Value>) -> Result<Value, WalkerError> { + let called = match self { + Value::Fn(i) => i, + i => { + return Err(WalkerError::OperationError(OperationError::CallableType( + i.clone(), + ))) + } + } + .borrow(); + + // FIXME: Currently closures are able to re-assign values from the upper scopes. + // This is good behaviour, until a closure re-assigns a value that was declared after + // the closure even existed. + // Minimal reproducible example: + // ```rh + // closure = fn { y = 10; }; + // y = 1; + // closure(); + // print y; + // ``` + // Expected: 1 + // Actual: 10 + let mut scope = called.scope.clone(); + scope.nest(); + + let parameters = &called.node.header.parameters; + + if parameters.len() != arguments.len() { + return Err(WalkerError::OperationError( + OperationError::WrongArgumentCount(parameters.len(), arguments.len()), + )); + } + + for (argument, parameter) in arguments.into_iter().zip(parameters.iter()) { + scope.set_var_shadowed(¶meter.identifier, argument); + } + + // Yes, we create a new walker for every function call, + // it's *way* easier that way. + let mut walker = Walker::new_with_scope(scope); + let result = + walker.walk_expression(&Expression::Block(Box::new(called.node.body.clone())))?; + + Ok(result) + } } impl Value { @@ -254,6 +306,12 @@ impl Display for Value { } } +#[derive(Clone, Debug)] +pub struct FnValue { + pub node: FnNode, + pub scope: ScopeChain, +} + #[derive(Error, Debug)] pub enum OperationError { #[error("Can't add value '{0}' of type '{}' to value '{1}' of type '{}'.", .0.type_name(), .1.type_name())] @@ -276,4 +334,8 @@ pub enum OperationError { ArrayType(Value), #[error("Array index '{index}' out of range for array of length '{length}'.")] ArrayIndexOutOfRange { index: i64, length: usize }, + #[error("Can't call value '{0}' of type '{}'.", .0.type_name())] + CallableType(Value), + #[error("Function expects {0} arguments, but {1} were given.")] + WrongArgumentCount(usize, usize), } |
