From 2a3ab5c161ac98cb3c6326173e5ed78089a9ed68 Mon Sep 17 00:00:00 2001 From: Mel Date: Sun, 14 Nov 2021 22:31:34 +0100 Subject: Function calling and the ensuing scope shenanigans --- src/interpret/scope.rs | 44 ++++++++++++++++++++++++++++----- src/interpret/value.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++-- src/interpret/walker.rs | 49 +++++++++++++++++++++++------------- src/parse/parser.rs | 11 ++++----- 4 files changed, 139 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/interpret/scope.rs b/src/interpret/scope.rs index 478b24d..0db34c2 100644 --- a/src/interpret/scope.rs +++ b/src/interpret/scope.rs @@ -3,17 +3,37 @@ use crate::parse::ast::nodes::Identifier; use std::{cell::RefCell, collections::HashMap, rc::Rc}; use thiserror::Error; -pub struct Scope { - scopes: Vec>, -} +type InnerScope = HashMap; + +#[derive(Debug, Clone)] +pub struct Scope(Rc>); impl Scope { + fn new() -> Self { + Scope(Rc::new(RefCell::new(HashMap::new()))) + } + + fn get(&self, ident: &str) -> Option { + self.0.borrow().get(ident).cloned() + } + + fn insert(&self, ident: Identifier, value: AssignedValue) { + self.0.borrow_mut().insert(ident, value); + } +} + +#[derive(Debug, Clone)] +pub struct ScopeChain { + scopes: Vec, +} + +impl ScopeChain { pub fn new() -> Self { - Scope { scopes: Vec::new() } + ScopeChain { scopes: Vec::new() } } pub fn nest(&mut self) { - self.scopes.push(HashMap::new()); + self.scopes.push(Scope::new()); } pub fn unnest(&mut self) { @@ -70,9 +90,21 @@ impl Scope { } Err(ScopeError::UnknownIdentifier(ident.to_string())) } + + // In contrast to set_var sets the var in the most inner scope so that it's always found first. + // This is used when setting parameters. + pub fn set_var_shadowed(&mut self, ident: &str, value: Value) { + let inner_scope = self + .scopes + .last_mut() + .expect("Tried accessing scope after last frame is gone."); + + // Can shadowed values be constant? No idea! + inner_scope.insert(ident.to_string(), AssignedValue::Mutable(value)); + } } -#[derive(Debug)] +#[derive(Debug, Clone)] enum AssignedValue { Constant(Value), Mutable(Value), 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 = Rc>; #[derive(Clone, Debug)] @@ -11,7 +16,7 @@ pub enum Value { Int(i64), Bool(bool), Array(ReferenceOnCopy>), - Fn(ReferenceOnCopy), + Fn(ReferenceOnCopy), Void, } @@ -214,6 +219,53 @@ impl Value { x => Err(OperationError::ArrayType(x.clone())), } } + + pub fn call(&self, arguments: Vec) -> Result { + 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), } diff --git a/src/interpret/walker.rs b/src/interpret/walker.rs index 68dc388..3feed6f 100644 --- a/src/interpret/walker.rs +++ b/src/interpret/walker.rs @@ -1,29 +1,38 @@ use std::{cell::RefCell, rc::Rc}; -use crate::parse::ast::{ - expression::Expression, - nodes::{BinaryOperator as BinOp, BlockNode, SimpleLiteral, StrPart, UnaryOperator as UnOp}, - statement::Statement, - Program, +use crate::{ + interpret::value::FnValue, + parse::ast::{ + expression::Expression, + nodes::{ + BinaryOperator as BinOp, BlockNode, SimpleLiteral, StrPart, UnaryOperator as UnOp, + }, + statement::Statement, + Program, + }, }; use thiserror::Error; use super::{ - scope::{Scope, ScopeError}, + scope::{ScopeChain, ScopeError}, value::{OperationError, Value}, }; pub struct Walker { - scope: Scope, + scope: ScopeChain, } impl Walker { pub fn new() -> Self { Walker { - scope: Scope::new(), + scope: ScopeChain::new(), } } + pub fn new_with_scope(scope: ScopeChain) -> Self { + Walker { scope } + } + pub fn walk(&mut self, program: &Program) { self.scope.nest(); for statement in program.statements.iter() { @@ -118,14 +127,20 @@ impl Walker { } .map_err(WalkerError::OperationError) } - Expression::Call(_) => todo!("Calls not implemented yet."), + Expression::Call(node) => { + let called = self.walk_expression(&node.called)?; + + let mut argument_values = Vec::new(); + for argument_node in node.arguments.iter() { + argument_values.push(self.walk_expression(argument_node)?); + } + + called.call(argument_values) + } Expression::ArrayAccess(node) => { let array = self.walk_expression(&node.array)?; let index = self.walk_expression(&node.index)?; - array - .subscript(index) - .map(|v| v.clone()) - .map_err(WalkerError::OperationError) + array.subscript(index).map_err(WalkerError::OperationError) } Expression::MemberAccess(_) => todo!("Structures not implemented yet."), Expression::Group(node) => self.walk_expression(node), @@ -160,10 +175,10 @@ impl Walker { Ok(Value::Str(buffer)) } - Expression::FnLiteral(fn_node) => { - let node = fn_node.as_ref().clone(); - Ok(Value::Fn(Rc::new(RefCell::new(node)))) - } + Expression::FnLiteral(node) => Ok(Value::Fn(Rc::new(RefCell::new(FnValue { + node: node.as_ref().clone(), + scope: self.scope.clone(), + })))), Expression::If(if_node) => { for conditional in &if_node.conditionals { if let Value::Bool(bool) = self.walk_expression(&conditional.condition)? { diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 5a09a8f..ee83293 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -217,10 +217,10 @@ impl> Parser { while let Some(token) = consume_if!(self, GroupOpen | ArrayOpen | Dot) { match token.variant { - GroupOpen => loop { + GroupOpen => { let mut arguments = Vec::new(); - loop { + while consume_if!(self, GroupClose).is_none() { arguments.push(self.expression()?); if consume_if!(self, Comma).is_none() { @@ -232,8 +232,8 @@ impl> Parser { left = Expression::Call(Box::new(CallNode { called: left, arguments, - })) - }, + })); + } ArrayOpen => { let index = self.expression()?; consume!(self, ArrayClose)?; @@ -335,10 +335,9 @@ impl> Parser { fn function(&mut self) -> Result { consume!(self, KeywordFn)?; - let token = self.tokens.next().expect("Expected function header."); let header = { - let has_self_receiver = if let KeywordSelf = token.variant { + let has_self_receiver = if consume_if!(self, KeywordSelf).is_some() { consume_if!(self, Comma); true } else { -- cgit 1.4.1