Skip to content

Commit

Permalink
Implement optional chains
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Oct 28, 2022
1 parent f446c09 commit 8284751
Show file tree
Hide file tree
Showing 17 changed files with 843 additions and 113 deletions.
220 changes: 174 additions & 46 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
unary::UnaryOp,
},
Call, Identifier, New,
Call, Identifier, New, Optional, OptionalItemKind,
},
function::{
ArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameterList,
Expand Down Expand Up @@ -430,6 +430,14 @@ impl<'b> ByteCompiler<'b> {
Label { index }
}

#[inline]
fn jump_if_null_or_undefined(&mut self) -> Label {
let index = self.next_opcode_location();
self.emit(Opcode::JumpIfNullOrUndefined, &[Self::DUMMY_ADDRESS]);

Label { index }
}

/// Emit an opcode with a dummy operand.
/// Return the `Label` of the operand.
#[inline]
Expand Down Expand Up @@ -1109,6 +1117,7 @@ impl<'b> ByteCompiler<'b> {
}
},
PropertyDefinition::MethodDefinition(name, kind) => match kind {
// TODO: set function name for getter and setters
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
Expand All @@ -1123,6 +1132,7 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::SetPropertyGetterByValue);
}
},
// TODO: set function name for getter and setters
MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
Expand Down Expand Up @@ -1443,12 +1453,164 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::PushNewTarget);
}
}
Expression::Optional(opt) => {
self.compile_optional_preserve_this(opt)?;

self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::Pop);

if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
// TODO: try to remove this variant somehow
Expression::FormalParameterList(_) => unreachable!(),
}
Ok(())
}

fn compile_access_preserve_this(&mut self, access: &PropertyAccess) -> JsResult<()> {
match access {
PropertyAccess::Simple(access) => {
self.compile_expr(access.target(), true)?;
self.emit_opcode(Opcode::Dup);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyAccessField::Expr(field) => {
self.compile_expr(field, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}
PropertyAccess::Private(access) => {
self.compile_expr(access.target(), true)?;
self.emit_opcode(Opcode::Dup);
let index = self.get_or_insert_name(access.field().into());
self.emit(Opcode::GetPrivateField, &[index]);
}
PropertyAccess::Super(access) => {
self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Super);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}
}
Ok(())
}

fn compile_optional_preserve_this(&mut self, optional: &Optional) -> JsResult<()> {
let mut jumps = Vec::with_capacity(optional.chain().len());

match optional.target() {
Expression::PropertyAccess(access) => {
self.compile_access_preserve_this(access)?;
}
Expression::Optional(opt) => self.compile_optional_preserve_this(opt)?,
expr => {
self.emit(Opcode::PushUndefined, &[]);
self.compile_expr(expr, true)?;
}
}
jumps.push(self.jump_if_null_or_undefined());

let (first, rest) = optional
.chain()
.split_first()
.expect("chain must have at least one element");
assert!(first.shorted());

self.compile_optional_item_kind(first.kind())?;

for item in rest {
if item.shorted() {
jumps.push(self.jump_if_null_or_undefined());
}
self.compile_optional_item_kind(item.kind())?;
}
let skip_undef = self.jump();

for label in jumps {
self.patch_jump(label);
}

self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::PushUndefined);

self.patch_jump(skip_undef);

Ok(())
}

fn compile_optional_item_kind(&mut self, kind: &OptionalItemKind) -> JsResult<()> {
match kind {
OptionalItemKind::SimplePropertyAccess { field } => {
self.emit_opcode(Opcode::Dup);
match field {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.emit(Opcode::Swap, &[]);
self.emit(Opcode::GetPropertyByValue, &[]);
}
}
self.emit_opcode(Opcode::RotateLeft);
self.emit_u8(3);
self.emit_opcode(Opcode::Pop);
}
OptionalItemKind::PrivatePropertyAccess { field } => {
self.emit_opcode(Opcode::Dup);
let index = self.get_or_insert_name((*field).into());
self.emit(Opcode::GetPrivateField, &[index]);
self.emit_opcode(Opcode::RotateLeft);
self.emit_u8(3);
self.emit_opcode(Opcode::Pop);
}
OptionalItemKind::Call { args } => {
let args = &**args;
let contains_spread = args.iter().any(|arg| matches!(arg, Expression::Spread(_)));

if contains_spread {
self.emit_opcode(Opcode::PushNewArray);
for arg in args {
self.compile_expr(arg, true)?;
if let Expression::Spread(_) = arg {
self.emit_opcode(Opcode::InitIterator);
self.emit_opcode(Opcode::PushIteratorToArray);
} else {
self.emit_opcode(Opcode::PushValueToArray);
}
}
self.emit_opcode(Opcode::CallSpread);
} else {
for arg in args {
self.compile_expr(arg, true)?;
}
self.emit(Opcode::Call, &[args.len() as u32]);
}

self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
}
}
Ok(())
}

pub fn compile_var_decl(&mut self, decl: &VarDeclaration) -> JsResult<()> {
for variable in decl.0.as_ref() {
match variable.binding() {
Expand Down Expand Up @@ -2292,7 +2454,6 @@ impl<'b> ByteCompiler<'b> {
function.is_async(),
function.is_arrow(),
);

let FunctionSpec {
name,
parameters,
Expand Down Expand Up @@ -2357,50 +2518,13 @@ impl<'b> ByteCompiler<'b> {
};

match call.function() {
Expression::PropertyAccess(access) => match access {
PropertyAccess::Simple(access) => {
self.compile_expr(access.target(), true)?;
if kind == CallKind::Call {
self.emit(Opcode::Dup, &[]);
}
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyAccessField::Expr(field) => {
self.compile_expr(field, true)?;
self.emit(Opcode::Swap, &[]);
self.emit(Opcode::GetPropertyByValue, &[]);
}
}
}
PropertyAccess::Private(access) => {
self.compile_expr(access.target(), true)?;
if kind == CallKind::Call {
self.emit(Opcode::Dup, &[]);
}
let index = self.get_or_insert_name(access.field().into());
self.emit(Opcode::GetPrivateField, &[index]);
}
PropertyAccess::Super(access) => {
if kind == CallKind::Call {
self.emit_opcode(Opcode::This);
}
self.emit_opcode(Opcode::Super);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}
},
Expression::PropertyAccess(access) if kind == CallKind::Call => {
self.compile_access_preserve_this(access)?;
}

Expression::Optional(opt) if kind == CallKind::Call => {
self.compile_optional_preserve_this(opt)?;
}
expr => {
self.compile_expr(expr, true)?;
if kind == CallKind::Call || kind == CallKind::CallEval {
Expand Down Expand Up @@ -2958,6 +3082,7 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::SetClassPrototype);
self.emit_opcode(Opcode::Swap);

// TODO: set function name for getter and setters
for element in class.elements() {
match element {
ClassElement::StaticMethodDefinition(name, method_definition) => {
Expand Down Expand Up @@ -3049,6 +3174,7 @@ impl<'b> ByteCompiler<'b> {
},
}
}
// TODO: set names for private methods
ClassElement::PrivateStaticMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
Expand Down Expand Up @@ -3218,6 +3344,7 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::Call, &[0]);
self.emit_opcode(Opcode::Pop);
}
// TODO: set names for private methods
ClassElement::PrivateMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
Expand Down Expand Up @@ -3263,6 +3390,7 @@ impl<'b> ByteCompiler<'b> {
match element {
ClassElement::MethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
// TODO: set names for getters and setters
match method_definition {
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
Expand Down
10 changes: 8 additions & 2 deletions boa_engine/src/syntax/ast/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ mod r#await;
mod call;
mod identifier;
mod new;
mod optional;
mod spread;
mod tagged_template;
mod r#yield;

pub use call::{Call, SuperCall};
pub use identifier::Identifier;
pub use new::New;
pub use optional::{Optional, OptionalItem, OptionalItemKind};
pub use r#await::Await;
pub use r#yield::Yield;
pub use spread::Spread;
Expand Down Expand Up @@ -110,10 +112,11 @@ pub enum Expression {
/// See [`Call`].
Call(Call),

/// See [`SuperCall`]
/// See [`SuperCall`].
SuperCall(SuperCall),

// TODO: Optional chains
/// See [`Optional`].
Optional(Optional),

// TODO: Import calls
/// See [`TaggedTemplate`].
Expand Down Expand Up @@ -173,6 +176,7 @@ impl Expression {
Self::New(new) => new.to_interned_string(interner),
Self::Call(call) => call.to_interned_string(interner),
Self::SuperCall(supc) => supc.to_interned_string(interner),
Self::Optional(opt) => opt.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(),
Self::TaggedTemplate(tag) => tag.to_interned_string(interner),
Self::Assign(assign) => assign.to_interned_string(interner),
Expand Down Expand Up @@ -213,6 +217,7 @@ impl Expression {
Expression::New(new) => new.contains_arguments(),
Expression::Call(call) => call.contains_arguments(),
Expression::SuperCall(call) => call.contains_arguments(),
Expression::Optional(opt) => opt.contains_arguments(),
Expression::TaggedTemplate(tag) => tag.contains_arguments(),
Expression::Assign(assign) => assign.contains_arguments(),
Expression::Unary(unary) => unary.contains_arguments(),
Expand Down Expand Up @@ -252,6 +257,7 @@ impl Expression {
Expression::Call(call) => call.contains(symbol),
Expression::SuperCall(_) if symbol == ContainsSymbol::SuperCall => true,
Expression::SuperCall(expr) => expr.contains(symbol),
Expression::Optional(opt) => opt.contains(symbol),
Expression::TaggedTemplate(temp) => temp.contains(symbol),
Expression::NewTarget => symbol == ContainsSymbol::NewTarget,
Expression::Assign(assign) => assign.contains(symbol),
Expand Down
Loading

0 comments on commit 8284751

Please sign in to comment.