From 80a9e6a971095157381a32506d67bd9754a05eea Mon Sep 17 00:00:00 2001 From: Jason Williams <936006+jasonwilliams@users.noreply.github.com> Date: Mon, 2 Dec 2019 21:13:31 +0000 Subject: [PATCH] updating rest-spread (rebased) (#213) * Adding support for rest/spread --- src/lib/builtins/function.rs | 6 +- src/lib/builtins/value.rs | 10 ++- src/lib/exec.rs | 133 ++++++++++++++++++++++++++++++---- src/lib/syntax/ast/expr.rs | 23 +++--- src/lib/syntax/ast/op.rs | 3 + src/lib/syntax/lexer.rs | 1 + src/lib/syntax/parser.rs | 136 ++++++++++++++++++++++++++++++++--- tests/js/test.js | 4 ++ 8 files changed, 282 insertions(+), 34 deletions(-) diff --git a/src/lib/builtins/function.rs b/src/lib/builtins/function.rs index 326f3791b0..0cfa8efd64 100644 --- a/src/lib/builtins/function.rs +++ b/src/lib/builtins/function.rs @@ -35,14 +35,14 @@ pub struct RegularFunction { pub object: Object, /// This function's expression pub expr: Expr, - /// The argument names of the function - pub args: Vec, + /// The argument declarations of the function + pub args: Vec, } impl RegularFunction { /// Make a new regular function #[allow(clippy::cast_possible_wrap)] - pub fn new(expr: Expr, args: Vec) -> Self { + pub fn new(expr: Expr, args: Vec) -> Self { let mut object = Object::default(); object.properties.insert( "arguments".to_string(), diff --git a/src/lib/builtins/value.rs b/src/lib/builtins/value.rs index 5b9af77502..69503d79d8 100644 --- a/src/lib/builtins/value.rs +++ b/src/lib/builtins/value.rs @@ -849,7 +849,15 @@ impl Display for ValueData { ValueData::Function(ref v) => match *v.borrow() { Function::NativeFunc(_) => write!(f, "function() {{ [native code] }}"), Function::RegularFunc(ref rf) => { - write!(f, "function({}){}", rf.args.join(", "), rf.expr) + write!(f, "function(")?; + let last_index = rf.args.len() - 1; + for (index, arg) in rf.args.iter().enumerate() { + write!(f, "{}", arg)?; + if index != last_index { + write!(f, ", ")?; + } + } + write!(f, "){}", rf.expr) } }, } diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 76d4ea4714..735e570356 100644 --- a/src/lib/exec.rs +++ b/src/lib/exec.rs @@ -135,6 +135,12 @@ impl Executor for Interpreter { }; let mut v_args = Vec::with_capacity(args.len()); for arg in args.iter() { + if let ExprDef::UnaryOp(UnaryOp::Spread, ref x) = arg.def { + let val = self.run(x)?; + let mut vals = self.extract_array_properties(&val).unwrap(); + v_args.append(&mut vals); + break; // after spread we don't accept any new arguments + } v_args.push(self.run(arg)?); } @@ -207,8 +213,17 @@ impl Executor for Interpreter { } ExprDef::ArrayDecl(ref arr) => { let array = array::new_array(self)?; - let elements: Result, _> = arr.iter().map(|val| self.run(val)).collect(); - array::add_to_array_object(&array, &elements?)?; + let mut elements: Vec = vec![]; + for elem in arr.iter() { + if let ExprDef::UnaryOp(UnaryOp::Spread, ref x) = elem.def { + let val = self.run(x)?; + let mut vals = self.extract_array_properties(&val).unwrap(); + elements.append(&mut vals); + break; // after spread we don't accept any new arguments + } + elements.push(self.run(elem)?); + } + array::add_to_array_object(&array, &elements)?; Ok(array) } ExprDef::FunctionDecl(ref name, ref args, ref expr) => { @@ -265,6 +280,7 @@ impl Executor for Interpreter { !(num_v_a as i32) }) } + UnaryOp::Spread => Gc::new(v_a), // for now we can do nothing but return the value as-is _ => unreachable!(), }) } @@ -366,7 +382,13 @@ impl Executor for Interpreter { )); for i in 0..data.args.len() { - let name = data.args.get(i).expect("Could not get data argument"); + let arg_expr = + data.args.get(i).expect("Could not get data argument"); + let name = match arg_expr.def { + ExprDef::Local(ref n) => Some(n), + _ => None, + } + .expect("Could not get argument"); let expr = v_args.get(i).expect("Could not get argument"); env.create_mutable_binding( name.clone(), @@ -520,16 +542,37 @@ impl Interpreter { Some(env.get_current_environment_ref().clone()), )); for i in 0..data.args.len() { - let name = data.args.get(i).expect("Could not get data argument"); - let expr: &Value = arguments_list.get(i).expect("Could not get argument"); - self.realm.environment.create_mutable_binding( - name.clone(), - false, - VariableScope::Function, - ); - self.realm - .environment - .initialize_binding(name, expr.clone()); + let arg_expr = data.args.get(i).expect("Could not get data argument"); + match arg_expr.def { + ExprDef::Local(ref name) => { + let expr: &Value = + arguments_list.get(i).expect("Could not get argument"); + self.realm.environment.create_mutable_binding( + name.clone(), + false, + VariableScope::Function, + ); + self.realm + .environment + .initialize_binding(name, expr.clone()); + } + ExprDef::UnaryOp(UnaryOp::Spread, ref expr) => { + if let ExprDef::Local(ref name) = expr.def { + let array = array::new_array(self)?; + array::add_to_array_object(&array, &arguments_list[i..])?; + + self.realm.environment.create_mutable_binding( + name.clone(), + false, + VariableScope::Function, + ); + self.realm.environment.initialize_binding(name, array); + } else { + panic!("Unsupported function argument declaration") + } + } + _ => panic!("Unsupported function argument declaration"), + } } // Add arguments object @@ -714,11 +757,34 @@ impl Interpreter { } } } + + /// `extract_array_properties` converts an array object into a rust vector of Values. + /// This is useful for the spread operator, for any other object an `Err` is returned + fn extract_array_properties(&mut self, value: &Value) -> Result>, ()> { + if let ValueData::Object(ref x) = *value.deref().borrow() { + // Check if object is array + if x.deref().borrow().kind == ObjectKind::Array { + let length: i32 = + self.value_to_rust_number(&value.get_field_slice("length")) as i32; + let values: Vec> = (0..length) + .map(|idx| value.get_field_slice(&idx.to_string())) + .collect::>(); + return Ok(values); + } + + return Err(()); + } + + Err(()) + } } #[cfg(test)] mod tests { use crate::exec; + use crate::exec::Executor; + use crate::forward; + use crate::realm::Realm; #[test] fn empty_let_decl_undefined() { @@ -754,6 +820,47 @@ mod tests { assert_eq!(exec(scenario), String::from("22")); } + #[test] + fn spread_with_arguments() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let scenario = r#" + const a = [1, "test", 3, 4]; + function foo(...a) { + return arguments; + } + + var result = foo(...a); + "#; + forward(&mut engine, scenario); + let one = forward(&mut engine, "result[0]"); + assert_eq!(one, String::from("1")); + + let two = forward(&mut engine, "result[1]"); + assert_eq!(two, String::from("test")); + + let three = forward(&mut engine, "result[2]"); + assert_eq!(three, String::from("3")); + + let four = forward(&mut engine, "result[3]"); + assert_eq!(four, String::from("4")); + } + + #[test] + fn array_rest_with_arguments() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let scenario = r#" + var b = [4, 5, 6] + var a = [1, 2, 3, ...b]; + "#; + forward(&mut engine, scenario); + let one = forward(&mut engine, "a"); + assert_eq!(one, String::from("[ 1, 2, 3, 4, 5, 6 ]")); + } + #[test] fn array_field_set() { let element_changes = r#" diff --git a/src/lib/syntax/ast/expr.rs b/src/lib/syntax/ast/expr.rs index cfc094718d..77ef8e6a53 100644 --- a/src/lib/syntax/ast/expr.rs +++ b/src/lib/syntax/ast/expr.rs @@ -42,7 +42,7 @@ pub enum ExprDef { Construct(Box, Vec), /// Run several expressions from top-to-bottom Block(Vec), - /// Load a reference to a value + /// Load a reference to a value, or a function argument Local(String), /// Gets the constant field of a value GetConstField(Box, String), @@ -61,9 +61,9 @@ pub enum ExprDef { /// Create an array with items inside ArrayDecl(Vec), /// Create a function with the given name, arguments, and expression - FunctionDecl(Option, Vec, Box), + FunctionDecl(Option, Vec, Box), /// Create an arrow function with the given arguments and expression - ArrowFunctionDecl(Vec, Box), + ArrowFunctionDecl(Vec, Box), /// Return the expression from a function Return(Option>), /// Throw a value @@ -181,12 +181,19 @@ impl Display for ExprDef { join_expr(f, arr)?; f.write_str("]") } - ExprDef::FunctionDecl(ref name, ref args, ref expr) => match name { - Some(val) => write!(f, "function {}({}){}", val, args.join(", "), expr), - None => write!(f, "function ({}){}", args.join(", "), expr), - }, + ExprDef::FunctionDecl(ref name, ref args, ref expr) => { + write!(f, "function ")?; + if let Some(func_name) = name { + f.write_fmt(format_args!("{}", func_name))?; + } + write!(f, "{{")?; + join_expr(f, args)?; + write!(f, "}} {}", expr) + } ExprDef::ArrowFunctionDecl(ref args, ref expr) => { - write!(f, "({}) => {}", args.join(", "), expr) + write!(f, "(")?; + join_expr(f, args)?; + write!(f, ") => {}", expr) } ExprDef::BinOp(ref op, ref a, ref b) => write!(f, "{} {} {}", a, op, b), ExprDef::UnaryOp(ref op, ref a) => write!(f, "{}{}", op, a), diff --git a/src/lib/syntax/ast/op.rs b/src/lib/syntax/ast/op.rs index 4c9c0f8449..29dc422357 100644 --- a/src/lib/syntax/ast/op.rs +++ b/src/lib/syntax/ast/op.rs @@ -66,6 +66,8 @@ pub enum UnaryOp { Not, /// `~a` - bitwise-not of the value Tilde, + /// `...a` - spread an iterable value + Spread, } impl Display for UnaryOp { @@ -80,6 +82,7 @@ impl Display for UnaryOp { UnaryOp::Minus => "-", UnaryOp::Not => "!", UnaryOp::Tilde => "~", + UnaryOp::Spread => "...", } ) } diff --git a/src/lib/syntax/lexer.rs b/src/lib/syntax/lexer.rs index dd05e80915..696510a4bb 100644 --- a/src/lib/syntax/lexer.rs +++ b/src/lib/syntax/lexer.rs @@ -434,6 +434,7 @@ impl<'a> Lexer<'a> { if self.next_is('.') { if self.next_is('.') { self.push_punc(Punctuator::Spread); + self.column_number += 2; } else { return Err(LexerError::new("Expecting Token .")); } diff --git a/src/lib/syntax/parser.rs b/src/lib/syntax/parser.rs index 207085b9fb..8c06794f3a 100644 --- a/src/lib/syntax/parser.rs +++ b/src/lib/syntax/parser.rs @@ -54,13 +54,17 @@ impl Parser { } } - fn parse_function_parameters(&mut self) -> Result, ParseError> { + fn parse_function_parameters(&mut self) -> Result, ParseError> { self.expect_punc(Punctuator::OpenParen, "function parameters ( expected")?; let mut args = Vec::new(); let mut tk = self.get_token(self.pos)?; while tk.data != TokenData::Punctuator(Punctuator::CloseParen) { match tk.data { - TokenData::Identifier(ref id) => args.push(id.clone()), + TokenData::Identifier(ref id) => args.push(Expr::new(ExprDef::Local(id.clone()))), + TokenData::Punctuator(Punctuator::Spread) => { + args.push(self.parse()?); + self.pos -= 1; // roll back so we're sitting on the closeParen ')' + } _ => { return Err(ParseError::Expected( vec![TokenData::Identifier("identifier".to_string())], @@ -374,14 +378,27 @@ impl Parser { TokenData::Punctuator(Punctuator::CloseParen) => next, TokenData::Punctuator(Punctuator::Comma) => { // at this point it's probably gonna be an arrow function + // if first param captured all arguments, we should expect a close paren + if let ExprDef::UnaryOp(UnaryOp::Spread, _) = next.def { + return Err(ParseError::Expected( + vec![TokenData::Punctuator(Punctuator::CloseParen)], + next_tok.clone(), + "arrow function", + )); + } + let mut args = vec![ match next.def { - ExprDef::Local(ref name) => (*name).clone(), - _ => "".to_string(), + ExprDef::Local(ref name) => { + Expr::new(ExprDef::Local((*name).clone())) + } + _ => Expr::new(ExprDef::Local("".to_string())), }, match self.get_token(self.pos)?.data { - TokenData::Identifier(ref id) => id.clone(), - _ => "".to_string(), + TokenData::Identifier(ref id) => { + Expr::new(ExprDef::Local(id.clone())) + } + _ => Expr::new(ExprDef::Local("".to_string())), }, ]; let mut expect_ident = true; @@ -390,12 +407,29 @@ impl Parser { let curr_tk = self.get_token(self.pos)?; match curr_tk.data { TokenData::Identifier(ref id) if expect_ident => { - args.push(id.clone()); + args.push(Expr::new(ExprDef::Local(id.clone()))); expect_ident = false; } TokenData::Punctuator(Punctuator::Comma) => { expect_ident = true; } + TokenData::Punctuator(Punctuator::Spread) => { + let ident_token = self.get_token(self.pos + 1)?; + if let TokenData::Identifier(ref _id) = ident_token.data + { + args.push(self.parse()?); + self.pos -= 1; + expect_ident = false; + } else { + return Err(ParseError::Expected( + vec![TokenData::Identifier( + "identifier".to_string(), + )], + ident_token.clone(), + "arrow function", + )); + } + } TokenData::Punctuator(Punctuator::CloseParen) => { self.pos += 1; break; @@ -414,6 +448,7 @@ impl Parser { vec![ TokenData::Punctuator(Punctuator::Comma), TokenData::Punctuator(Punctuator::CloseParen), + TokenData::Punctuator(Punctuator::Spread), ], curr_tk, "arrow function", @@ -578,6 +613,9 @@ impl Parser { UnaryOp::DecrementPre, Box::new(self.parse()?), )), + TokenData::Punctuator(Punctuator::Spread) => { + Expr::new(ExprDef::UnaryOp(UnaryOp::Spread, Box::new(self.parse()?))) + } _ => return Err(ParseError::Expected(Vec::new(), token.clone(), "script")), }; if self.pos >= self.tokens.len() { @@ -708,7 +746,10 @@ impl Parser { self.pos += 1; let mut args = Vec::with_capacity(1); match result.def { - ExprDef::Local(ref name) => args.push((*name).clone()), + ExprDef::Local(ref name) => { + args.push(Expr::new(ExprDef::Local((*name).clone()))) + } + ExprDef::UnaryOp(UnaryOp::Spread, _) => args.push(result), _ => return Err(ParseError::ExpectedExpr("identifier", result)), } let next = self.parse()?; @@ -844,11 +885,16 @@ impl Parser { #[cfg(test)] mod tests { use super::*; + use crate::syntax::ast::{constant::Const, op::BinOp}; use crate::syntax::{ ast::expr::{Expr, ExprDef}, lexer::Lexer, }; + fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr { + Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2))) + } + #[allow(clippy::result_unwrap_used)] fn check_parser(js: &str, expr: &[Expr]) { let mut lexer = Lexer::new(js); @@ -924,7 +970,7 @@ mod tests { String::from("b"), Expr::new(ExprDef::FunctionDecl( None, - vec![String::from("test")], + vec![Expr::new(ExprDef::Local(String::from("test")))], Box::new(Expr::new(ExprDef::Block(vec![]))), )), ); @@ -1455,4 +1501,76 @@ mod tests { )], ); } + + #[test] + fn check_function_declarations() { + check_parser( + "function foo(a) { return a; }", + &[Expr::new(ExprDef::FunctionDecl( + Some(String::from("foo")), + vec![Expr::new(ExprDef::Local(String::from("a")))], + Box::new(Expr::new(ExprDef::Block(vec![Expr::new(ExprDef::Return( + Some(Box::new(Expr::new(ExprDef::Local(String::from("a"))))), + ))]))), + ))], + ); + + check_parser( + "function (a, ...b) {}", + &[Expr::new(ExprDef::FunctionDecl( + None, + vec![ + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::UnaryOp( + UnaryOp::Spread, + Box::new(Expr::new(ExprDef::Local(String::from("b")))), + )), + ], + Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), + ))], + ); + + check_parser( + "(...a) => {}", + &[Expr::new(ExprDef::ArrowFunctionDecl( + vec![Expr::new(ExprDef::UnaryOp( + UnaryOp::Spread, + Box::new(Expr::new(ExprDef::Local(String::from("a")))), + ))], + Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), + ))], + ); + + check_parser( + "(a, b, ...c) => {}", + &[Expr::new(ExprDef::ArrowFunctionDecl( + vec![ + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + Expr::new(ExprDef::UnaryOp( + UnaryOp::Spread, + Box::new(Expr::new(ExprDef::Local(String::from("c")))), + )), + ], + Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), + ))], + ); + + check_parser( + "(a, b) => { return a + b; }", + &[Expr::new(ExprDef::ArrowFunctionDecl( + vec![ + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + ], + Box::new(Expr::new(ExprDef::Block(vec![Expr::new(ExprDef::Return( + Some(Box::new(create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + ))), + ))]))), + ))], + ); + } } diff --git a/tests/js/test.js b/tests/js/test.js index d0559ac44d..449b58df54 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1 +1,5 @@ // Test your JS here +var b = [4, 5, 6] +var a = [1, 2, 3, ...b]; + +console.log(a);