Browse Source

updating rest-spread (rebased) (#213)

* Adding support for rest/spread
pull/215/head
Jason Williams 5 years ago committed by GitHub
parent
commit
80a9e6a971
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/lib/builtins/function.rs
  2. 10
      src/lib/builtins/value.rs
  3. 133
      src/lib/exec.rs
  4. 23
      src/lib/syntax/ast/expr.rs
  5. 3
      src/lib/syntax/ast/op.rs
  6. 1
      src/lib/syntax/lexer.rs
  7. 136
      src/lib/syntax/parser.rs
  8. 4
      tests/js/test.js

6
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<String>,
/// The argument declarations of the function
pub args: Vec<Expr>,
}
impl RegularFunction {
/// Make a new regular function
#[allow(clippy::cast_possible_wrap)]
pub fn new(expr: Expr, args: Vec<String>) -> Self {
pub fn new(expr: Expr, args: Vec<Expr>) -> Self {
let mut object = Object::default();
object.properties.insert(
"arguments".to_string(),

10
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)
}
},
}

133
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<Vec<_>, _> = arr.iter().map(|val| self.run(val)).collect();
array::add_to_array_object(&array, &elements?)?;
let mut elements: Vec<Value> = 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<Vec<Gc<ValueData>>, ()> {
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<Gc<ValueData>> = (0..length)
.map(|idx| value.get_field_slice(&idx.to_string()))
.collect::<Vec<Value>>();
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#"

23
src/lib/syntax/ast/expr.rs

@ -42,7 +42,7 @@ pub enum ExprDef {
Construct(Box<Expr>, Vec<Expr>),
/// Run several expressions from top-to-bottom
Block(Vec<Expr>),
/// 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<Expr>, String),
@ -61,9 +61,9 @@ pub enum ExprDef {
/// Create an array with items inside
ArrayDecl(Vec<Expr>),
/// Create a function with the given name, arguments, and expression
FunctionDecl(Option<String>, Vec<String>, Box<Expr>),
FunctionDecl(Option<String>, Vec<Expr>, Box<Expr>),
/// Create an arrow function with the given arguments and expression
ArrowFunctionDecl(Vec<String>, Box<Expr>),
ArrowFunctionDecl(Vec<Expr>, Box<Expr>),
/// Return the expression from a function
Return(Option<Box<Expr>>),
/// 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),

3
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 => "...",
}
)
}

1
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 ."));
}

136
src/lib/syntax/parser.rs

@ -54,13 +54,17 @@ impl Parser {
}
}
fn parse_function_parameters(&mut self) -> Result<Vec<String>, ParseError> {
fn parse_function_parameters(&mut self) -> Result<Vec<Expr>, 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"))),
))),
))]))),
))],
);
}
}

4
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);

Loading…
Cancel
Save