Browse Source

Implement `for await...of` loops (#2286)

This Pull Request changes the following:

- Implement `for await...of` loop parsing
- Implement `for await...of` execution
pull/2292/head
raskad 2 years ago
parent
commit
d28633925c
  1. 5
      boa_engine/src/builtins/iterable/mod.rs
  2. 14
      boa_engine/src/bytecompiler/mod.rs
  3. 9
      boa_engine/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  4. 32
      boa_engine/src/syntax/parser/statement/iteration/for_statement.rs
  5. 2
      boa_engine/src/vm/code_block.rs
  6. 42
      boa_engine/src/vm/mod.rs
  7. 18
      boa_engine/src/vm/opcode.rs
  8. 4
      boa_engine/src/vm/tests.rs

5
boa_engine/src/builtins/iterable/mod.rs

@ -233,6 +233,11 @@ pub struct IteratorResult {
}
impl IteratorResult {
/// Create a new `IteratorResult`.
pub(crate) fn new(object: JsObject) -> Self {
Self { object }
}
/// `IteratorComplete ( iterResult )`
///
/// The abstract operation `IteratorComplete` takes argument `iterResult` (an `Object`) and

14
boa_engine/src/bytecompiler/mod.rs

@ -1578,7 +1578,11 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::PopEnvironment);
}
if for_of_loop.r#await() {
self.emit_opcode(Opcode::InitIteratorAsync);
} else {
self.emit_opcode(Opcode::InitIterator);
}
self.emit_opcode(Opcode::LoopStart);
let start_address = self.next_opcode_location();
@ -1588,7 +1592,15 @@ impl<'b> ByteCompiler<'b> {
self.context.push_compile_time_environment(false);
let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext);
let exit = if for_of_loop.r#await() {
self.emit_opcode(Opcode::ForAwaitOfLoopIterate);
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
self.emit_opcode_with_operand(Opcode::ForAwaitOfLoopNext)
} else {
self.emit_opcode_with_operand(Opcode::ForInLoopNext)
};
match for_of_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => {

9
boa_engine/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -11,11 +11,12 @@ pub struct ForOfLoop {
iterable: Box<Node>,
body: Box<Node>,
label: Option<Sym>,
r#await: bool,
}
impl ForOfLoop {
/// Creates a new "for of" loop AST node.
pub fn new<I, B>(init: IterableLoopInitializer, iterable: I, body: B) -> Self
pub fn new<I, B>(init: IterableLoopInitializer, iterable: I, body: B, r#await: bool) -> Self
where
I: Into<Node>,
B: Into<Node>,
@ -25,6 +26,7 @@ impl ForOfLoop {
iterable: Box::new(iterable.into()),
body: Box::new(body.into()),
label: None,
r#await,
}
}
@ -48,6 +50,11 @@ impl ForOfLoop {
self.label = Some(label);
}
/// Returns true if this "for...of" loop is an "for await...of" loop.
pub(crate) fn r#await(&self) -> bool {
self.r#await
}
/// Converts the "for of" loop to a string with the given indentation.
pub(in crate::syntax::ast::node) fn to_indented_string(
&self,

32
boa_engine/src/syntax/parser/statement/iteration/for_statement.rs

@ -79,10 +79,34 @@ where
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("ForStatement", "Parsing");
cursor.expect((Keyword::For, false), "for statement", interner)?;
let init_position = cursor
.expect(Punctuator::OpenParen, "for statement", interner)?
let mut r#await = false;
let next = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
let init_position = match next.kind() {
TokenKind::Punctuator(Punctuator::OpenParen) => next.span().end(),
TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => {
return Err(ParseError::unexpected(
next.to_string(interner),
next.span(),
"for await...of is only valid in async functions or async generators",
));
}
TokenKind::Keyword((Keyword::Await, _)) => {
r#await = true;
cursor
.expect(Punctuator::OpenParen, "for await...of", interner)?
.span()
.end();
.end()
}
_ => {
return Err(ParseError::unexpected(
next.to_string(interner),
next.span(),
"for statement",
));
}
};
let init = match cursor
.peek(0, interner)?
@ -233,7 +257,7 @@ where
}
}
return Ok(ForOfLoop::new(init, iterable, body).into());
return Ok(ForOfLoop::new(init, iterable, body, r#await).into());
}
(Some(Node::ConstDeclList(list)), _) => {
// Reject const declarations without initializers inside for loops

2
boa_engine/src/vm/code_block.rs

@ -204,6 +204,7 @@ impl CodeBlock {
| Opcode::SuperCall
| Opcode::ForInLoopInitIterator
| Opcode::ForInLoopNext
| Opcode::ForAwaitOfLoopNext
| Opcode::ConcatToString
| Opcode::GeneratorNextDelegate => {
let result = self.read::<u32>(*pc).to_string();
@ -374,6 +375,7 @@ impl CodeBlock {
| Opcode::CallSpread
| Opcode::NewSpread
| Opcode::SuperCallSpread
| Opcode::ForAwaitOfLoopIterate
| Opcode::Nop => String::new(),
}
}

42
boa_engine/src/vm/mod.rs

@ -6,7 +6,7 @@ use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
function::{ConstructorKind, Function},
iterable::{IteratorHint, IteratorRecord},
iterable::{IteratorHint, IteratorRecord, IteratorResult},
Array, ForInIterator, JsArgs, Number, Promise,
},
environments::EnvironmentSlots,
@ -2129,6 +2129,46 @@ impl Context {
self.vm.push(done);
}
}
Opcode::ForAwaitOfLoopIterate => {
let _done = self
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = self.vm.pop();
let next_method_object = if let Some(object) = next_method.as_callable() {
object
} else {
return self.throw_type_error("iterable next method not a function");
};
let iterator = self.vm.pop();
let next_result = next_method_object.call(&iterator, &[], self)?;
self.vm.push(iterator);
self.vm.push(next_method);
self.vm.push(next_result);
}
Opcode::ForAwaitOfLoopNext => {
let address = self.vm.read::<u32>();
let next_result = self.vm.pop();
let next_result = if let Some(next_result) = next_result.as_object() {
IteratorResult::new(next_result.clone())
} else {
return self.throw_type_error("next value should be an object");
};
if next_result.complete(self)? {
self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().loop_env_stack_dec();
self.vm.frame_mut().try_env_stack_dec();
self.realm.environments.pop();
self.vm.push(true);
} else {
self.vm.push(false);
let value = next_result.value(self)?;
self.vm.push(value);
}
}
Opcode::ConcatToString => {
let value_count = self.vm.read::<u32>();
let mut strings = Vec::with_capacity(value_count as usize);

18
boa_engine/src/vm/opcode.rs

@ -1085,6 +1085,20 @@ pub enum Opcode {
/// Stack: iterator, next_method, done **=>** iterator, next_method, done, next_result
ForInLoopNext,
/// Move to the next value in a for await..of loop.
///
/// Operands:
///
/// Stack: iterator, next_method, done **=>** iterator, next_method, next_result
ForAwaitOfLoopIterate,
/// Get the value from a for await..of loop next result.
///
/// Operands: address: `u32`
///
/// Stack: next_result **=>** done, value
ForAwaitOfLoopNext,
/// Concat multiple stack objects into a string.
///
/// Operands: value_count: `u32`
@ -1337,6 +1351,8 @@ impl Opcode {
Self::IteratorClose => "IteratorClose",
Self::IteratorToArray => "IteratorToArray",
Self::ForInLoopNext => "ForInLoopNext",
Self::ForAwaitOfLoopNext => "ForAwaitOfLoopNext",
Self::ForAwaitOfLoopIterate => "ForAwaitOfLoopIterate",
Self::ConcatToString => "ConcatToString",
Self::RequireObjectCoercible => "RequireObjectCoercible",
Self::ValueNotNullOrUndefined => "ValueNotNullOrUndefined",
@ -1481,6 +1497,8 @@ impl Opcode {
Self::IteratorClose => "INST - IteratorClose",
Self::IteratorToArray => "INST - IteratorToArray",
Self::ForInLoopNext => "INST - ForInLoopNext",
Self::ForAwaitOfLoopIterate => "INST - ForAwaitOfLoopIterate",
Self::ForAwaitOfLoopNext => "INST - ForAwaitOfLoopNext",
Self::ConcatToString => "INST - ConcatToString",
Self::RequireObjectCoercible => "INST - RequireObjectCoercible",
Self::ValueNotNullOrUndefined => "INST - ValueNotNullOrUndefined",

4
boa_engine/src/vm/tests.rs

@ -154,7 +154,7 @@ fn run_super_method_in_object() {
assert_eq!(
Context::default().eval(source.as_bytes()),
Ok(JsValue::from("super"))
)
);
}
#[test]
@ -180,5 +180,5 @@ fn get_reference_by_super() {
assert_eq!(
Context::default().eval(source.as_bytes()),
Ok(JsValue::from("ab"))
)
);
}

Loading…
Cancel
Save