Skip to content

Commit

Permalink
updating rest-spread (rebased) (#213)
Browse files Browse the repository at this point in the history
* Adding support for rest/spread
  • Loading branch information
jasonwilliams authored Dec 2, 2019
1 parent cf516f6 commit 80a9e6a
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 34 deletions.
6 changes: 3 additions & 3 deletions src/lib/builtins/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
10 changes: 9 additions & 1 deletion src/lib/builtins/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
},
}
Expand Down
133 changes: 120 additions & 13 deletions src/lib/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?);
}

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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!(),
})
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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#"
Expand Down
23 changes: 15 additions & 8 deletions src/lib/syntax/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 3 additions & 0 deletions src/lib/syntax/ast/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -80,6 +82,7 @@ impl Display for UnaryOp {
UnaryOp::Minus => "-",
UnaryOp::Not => "!",
UnaryOp::Tilde => "~",
UnaryOp::Spread => "...",
}
)
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/syntax/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ."));
}
Expand Down
Loading

0 comments on commit 80a9e6a

Please sign in to comment.