diff --git a/crates/dash_compiler/src/for_each_loop.rs b/crates/dash_compiler/src/for_each_loop.rs new file mode 100644 index 00000000..a4ed7966 --- /dev/null +++ b/crates/dash_compiler/src/for_each_loop.rs @@ -0,0 +1,192 @@ +use std::mem; + +use dash_middle::compiler::instruction::AssignKind; +use dash_middle::interner::{sym, Symbol}; +use dash_middle::lexer::token::TokenType; +use dash_middle::parser::error::Error; +use dash_middle::parser::expr::{Expr, ExprKind}; +use dash_middle::parser::statement::{ + BlockStatement, ScopeId, Statement, StatementKind, VariableBinding, VariableDeclaration, VariableDeclarations, + WhileLoop, +}; +use dash_middle::sourcemap::Span; +use dash_middle::visitor::Visitor; + +use crate::builder::InstructionBuilder; +use crate::instruction::compile_local_load; + +pub enum ForEachLoopKind { + ForOf, + ForIn, +} + +/// Helper for desugaring for each-like loops that iterate through an iterator +/// (for..in and for..of), as well as `yield*` +pub struct ForEachDesugarCtxt<'a, 'cx, 'interner> { + /// The local that stores the iterator + iterator_local: u16, + /// The local that stores the intermediate next() call result + gen_step_local: u16, + ib: &'a mut InstructionBuilder<'cx, 'interner>, +} + +impl<'a, 'cx, 'interner> ForEachDesugarCtxt<'a, 'cx, 'interner> { + pub fn new(ib: &'a mut InstructionBuilder<'cx, 'interner>, at: Span) -> Result { + let iterator_local = ib + .add_unnameable_local(sym::for_of_iter) + .map_err(|_| Error::LocalLimitExceeded(at))?; + + let gen_step_local = ib + .add_unnameable_local(sym::for_of_gen_step) + .map_err(|_| Error::LocalLimitExceeded(at))?; + + Ok(Self { + ib, + iterator_local, + gen_step_local, + }) + } + + /// Assigns the iterator expression to the iterator local + pub fn init_iterator(&mut self, kind: ForEachLoopKind, iterable: Expr) -> Result<(), Error> { + self.ib.accept_expr(iterable)?; + match kind { + ForEachLoopKind::ForOf => self.ib.build_symbol_iterator(), + ForEachLoopKind::ForIn => self.ib.build_for_in_iterator(), + } + self.ib + .build_local_store(AssignKind::Assignment, self.iterator_local, false); + self.ib.build_pop(); + Ok(()) + } + + /// Creates an expression that loads the step value + pub fn step_value_expr(&self) -> Expr { + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::property_access( + false, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::compiled(compile_local_load(self.gen_step_local, false)), + }, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::value), + }, + ), + } + } + + /// Prepends a variable definition with the iterator step's value at the "beginning" of a statement by wrapping it in a new block with the given scope. + pub fn prepend_variable_defn(&self, binding: VariableBinding, body: &mut Statement, scope: ScopeId) { + // Assign iterator value to binding at the very start of the for loop body + let next_value = Statement { + span: Span::COMPILER_GENERATED, + kind: StatementKind::Variable(VariableDeclarations(vec![VariableDeclaration::new( + binding, + Some(self.step_value_expr()), + )])), + }; + + // Create a new block for the variable declaration + let old_body = mem::replace(body, Statement::dummy_empty()); + *body = Statement { + span: Span::COMPILER_GENERATED, + kind: StatementKind::Block(BlockStatement(vec![next_value, old_body], scope)), + }; + } + + /// Emits a loop, assuming that `iterator_local` has been initialized with the iterator + pub fn compile_loop(&mut self, label: Option, body: Box) -> Result<(), Error> { + self.ib.visit_while_loop( + Span::COMPILER_GENERATED, + label, + WhileLoop { + condition: Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::unary( + TokenType::LogicalNot, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::property_access( + false, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::assignment_local_space( + self.gen_step_local, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::function_call( + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::property_access( + false, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::compiled(compile_local_load( + self.iterator_local, + false, + )), + }, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::next), + }, + ), + }, + Vec::new(), + false, + ), + }, + TokenType::Assignment, + ), + }, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::done), + }, + ), + }, + ), + }, + body, + }, + )?; + + Ok(()) + } + + /// Convenience function for fully desugaring a for..of/for..in loop given an iterable + pub fn desugar_for_each_kinded_loop( + &mut self, + kind: ForEachLoopKind, + binding: VariableBinding, + iterable: Expr, + mut loop_body: Box, + loop_scope: ScopeId, + label: Option, + ) -> Result<(), Error> { + // For-Of Loop Desugaring: + // + // for (const x of [1,2]) console.log(x) + // + // becomes + // + // let __forOfIter = [1,2][Symbol.iterator](); + // let __forOfGenStep; + // let x; + // while (!(__forOfGenStep = __forOfIter.next()).done) { + // x = __forOfGenStep.value; + // console.log(x) + // } + // + // For-In loops are desugared almost equivalently, except an iterator over the object keys is used + + self.init_iterator(kind, iterable)?; + + self.prepend_variable_defn(binding, &mut loop_body, loop_scope); + + self.compile_loop(label, loop_body) + } +} diff --git a/crates/dash_compiler/src/lib.rs b/crates/dash_compiler/src/lib.rs index ed4cb18f..5a612eda 100644 --- a/crates/dash_compiler/src/lib.rs +++ b/crates/dash_compiler/src/lib.rs @@ -1,6 +1,5 @@ use std::cell::{Cell, RefCell}; use std::collections::HashSet; -use std::mem; use std::rc::Rc; use dash_log::{debug, span, Level}; @@ -21,8 +20,7 @@ use dash_middle::parser::statement::{ Asyncness, Binding, BlockStatement, Class, ClassMember, ClassMemberKey, ClassMemberValue, DoWhileLoop, ExportKind, ForInLoop, ForLoop, ForOfLoop, FunctionDeclaration, FunctionKind, IfStatement, ImportKind, Loop, Parameter, ReturnStatement, ScopeId, SpecifierKind, Statement, StatementKind, SwitchCase, SwitchStatement, TryCatch, - VariableBinding, VariableDeclaration, VariableDeclarationKind, VariableDeclarationName, VariableDeclarations, - WhileLoop, + VariableDeclaration, VariableDeclarationKind, VariableDeclarationName, VariableDeclarations, WhileLoop, }; use dash_middle::sourcemap::Span; use dash_middle::util::Counter; @@ -30,6 +28,7 @@ use dash_middle::visitor::Visitor; use dash_optimizer::consteval::ConstFunctionEvalCtx; use dash_optimizer::type_infer::{InferMode, LocalDeclToSlot, NameResolutionResults, TypeInferCtx}; use dash_optimizer::OptLevel; +use for_each_loop::{ForEachDesugarCtxt, ForEachLoopKind}; use instruction::compile_local_load; use jump_container::JumpContainer; @@ -38,12 +37,12 @@ use crate::builder::{InstructionBuilder, Label}; use self::instruction::NamedExportKind; pub mod builder; +mod for_each_loop; #[cfg(feature = "from_string")] pub mod from_string; pub mod instruction; pub mod transformations; -// #[cfg(test)] -// mod test; + mod jump_container; macro_rules! unimplementedc { @@ -367,154 +366,6 @@ impl<'interner> FunctionCompiler<'interner> { fn add_unnameable_local(&mut self, name: Symbol) -> Result { self.scopes.add_unnameable_local(self.current, name, None) } - - fn visit_for_each_kinded_loop( - &mut self, - kind: ForEachLoopKind, - binding: VariableBinding, - expr: Expr, - mut body: Box, - scope: ScopeId, - label: Option, - ) -> Result<(), Error> { - // For-Of Loop Desugaring: - - // === ORIGINAL === - // for (const x of [1,2]) console.log(x) - - // === AFTER DESUGARING === - // let __forOfIter = [1,2][Symbol.iterator](); - // let __forOfGenStep; - // let x; - - // while (!(__forOfGenStep = __forOfIter.next()).done) { - // console.log(x) - // } - - // For-In Loop Desugaring - - // === ORIGINAL === - // for (const x in { a: 3, b: 4 }) console.log(x); - - // === AFTER DESUGARING === - // let __forInIter = [1,2][__intrinsicForInIter](); - // let __forInGenStep; - // let x; - - // while (!(__forInGenStep = __forOfIter.next()).done) { - // console.log(x) - // } - - let mut ib = InstructionBuilder::new(self); - let for_of_iter_id = ib - .add_unnameable_local(sym::for_of_iter) - .map_err(|_| Error::LocalLimitExceeded(expr.span))?; - - let for_of_gen_step_id = ib - .add_unnameable_local(sym::for_of_gen_step) - .map_err(|_| Error::LocalLimitExceeded(expr.span))?; - - ib.accept_expr(expr)?; - match kind { - ForEachLoopKind::ForOf => ib.build_symbol_iterator(), - ForEachLoopKind::ForIn => ib.build_for_in_iterator(), - } - ib.build_local_store(AssignKind::Assignment, for_of_iter_id, false); - ib.build_pop(); - - let gen_step = compile_local_load(for_of_gen_step_id, false); - - // Assign iterator value to binding at the very start of the for loop body - let next_value = Statement { - span: Span::COMPILER_GENERATED, - kind: StatementKind::Variable(VariableDeclarations(vec![VariableDeclaration::new( - binding, - Some(Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::compiled(gen_step), - }, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::value), - }, - ), - }), - )])), - }; - - // Create a new block for the variable declaration - let old_body = mem::replace(&mut *body, Statement::dummy_empty()); - *body = Statement { - span: Span::COMPILER_GENERATED, - kind: StatementKind::Block(BlockStatement(vec![next_value, old_body], scope)), - }; - - let for_of_iter_binding_bc = compile_local_load(for_of_iter_id, false); - - // for..of -> while loop rewrite - ib.visit_while_loop( - Span::COMPILER_GENERATED, - label, - WhileLoop { - condition: Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::unary( - TokenType::LogicalNot, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::assignment_local_space( - for_of_gen_step_id, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::function_call( - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::compiled(for_of_iter_binding_bc), - }, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::next), - }, - ), - }, - Vec::new(), - false, - ), - }, - TokenType::Assignment, - ), - }, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::done), - }, - ), - }, - ), - }, - body, - }, - )?; - - Ok(()) - } -} - -enum ForEachLoopKind { - ForOf, - ForIn, } impl<'interner> Visitor> for FunctionCompiler<'interner> { @@ -567,6 +418,7 @@ impl<'interner> Visitor> for FunctionCompiler<'interner> { self.current_function_mut().buf.append(&mut buf); Ok(()) } + ExprKind::YieldStar(e) => self.visit_yield_star(span, e), ExprKind::Empty => self.visit_empty_expr(), } } @@ -864,6 +716,39 @@ impl<'interner> Visitor> for FunctionCompiler<'interner> { Ok(()) } + fn visit_yield_star(&mut self, span: Span, right: Box) -> Result<(), Error> { + if !matches!(self.current_function().ty, FunctionKind::Generator) { + return Err(Error::YieldOutsideGenerator { yield_expr: span }); + } + + // Desugar `yield* right` to: + // + // let _iterator = right; + // let _item; + // while (!(_item = _iterator.next()).done) { + // yield _item.value; + // } + // _item.value; + + let mut ib = InstructionBuilder::new(self); + let mut fcx = ForEachDesugarCtxt::new(&mut ib, span)?; + fcx.init_iterator(ForEachLoopKind::ForOf, *right)?; + fcx.compile_loop( + None, + Box::new(Statement { + span, + kind: StatementKind::Expression(Expr { + span, + kind: ExprKind::unary(TokenType::Yield, fcx.step_value_expr()), + }), + }), + )?; + let step_value = fcx.step_value_expr(); + ib.accept_expr(step_value)?; + + Ok(()) + } + fn visit_variable_declaration( &mut self, span: Span, @@ -1987,7 +1872,7 @@ impl<'interner> Visitor> for FunctionCompiler<'interner> { fn visit_for_of_loop( &mut self, - _span: Span, + span: Span, label: Option, ForOfLoop { binding, @@ -1996,12 +1881,19 @@ impl<'interner> Visitor> for FunctionCompiler<'interner> { scope, }: ForOfLoop, ) -> Result<(), Error> { - self.visit_for_each_kinded_loop(ForEachLoopKind::ForOf, binding, expr, body, scope, label) + ForEachDesugarCtxt::new(&mut InstructionBuilder::new(self), span)?.desugar_for_each_kinded_loop( + ForEachLoopKind::ForOf, + binding, + expr, + body, + scope, + label, + ) } fn visit_for_in_loop( &mut self, - _span: Span, + span: Span, label: Option, ForInLoop { binding, @@ -2010,7 +1902,14 @@ impl<'interner> Visitor> for FunctionCompiler<'interner> { scope, }: ForInLoop, ) -> Result<(), Error> { - self.visit_for_each_kinded_loop(ForEachLoopKind::ForIn, binding, expr, body, scope, label) + ForEachDesugarCtxt::new(&mut InstructionBuilder::new(self), span)?.desugar_for_each_kinded_loop( + ForEachLoopKind::ForIn, + binding, + expr, + body, + scope, + label, + ) } fn visit_import_statement(&mut self, span: Span, import: ImportKind) -> Result<(), Error> { diff --git a/crates/dash_compiler/src/test.rs b/crates/dash_compiler/src/test.rs deleted file mode 100644 index 502dc8df..00000000 --- a/crates/dash_compiler/src/test.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::CompileResult; -use super::FunctionCompiler; - -fn compile(source: &str) -> CompileResult { - let ast = Parser::from_str(source) - .expect("Lex error") - .parse_all() - .expect("Parse error"); - - FunctionCompiler::new().compile_ast(ast).expect("Compile error") -} - -#[test] -pub fn empty() { - let c = compile(""); - assert_eq!(c.instructions, [CONSTANT, 0, RET]); - assert_eq!(&*c.cp, [Constant::Undefined]); -} - -#[test] -fn binary_math() { - macro_rules! case { - ($($st:expr, $instr:expr);*) => { - $( - let c = compile(concat!("1234 ", $st, " 5678")); - assert_eq!(c.instructions, [CONSTANT, 0, CONSTANT, 1, $instr, RET]); - assert_eq!(&*c.cp, [Constant::Number(1234.0), Constant::Number(5678.0)]); - )* - }; - } - - case! { - "+", ADD; - "-", SUB; - "*", MUL; - "/", DIV; - "%", REM; - "**", POW; - ">", GT; - ">=", GE; - "<", LT; - "<=", LE; - "==", EQ; - "!=", NE - }; -} diff --git a/crates/dash_middle/src/parser/expr.rs b/crates/dash_middle/src/parser/expr.rs index 77d1eda9..69a0f77d 100644 --- a/crates/dash_middle/src/parser/expr.rs +++ b/crates/dash_middle/src/parser/expr.rs @@ -26,6 +26,8 @@ pub enum ExprKind { Literal(LiteralExpr), /// Represents an unary expression, i.e. `-foo`, `+bar`, `await foo` Unary(UnaryExpr), + #[display(fmt = "yield* {_0}")] + YieldStar(Box), /// An assignment expression, i.e. `foo = bar` Assignment(AssignmentExpr), /// A function call expression diff --git a/crates/dash_middle/src/visitor.rs b/crates/dash_middle/src/visitor.rs index 3eb57c3d..ea5522be 100755 --- a/crates/dash_middle/src/visitor.rs +++ b/crates/dash_middle/src/visitor.rs @@ -140,6 +140,8 @@ pub trait Visitor { /// Visits a labelled statement. fn visit_labelled(&mut self, span: Span, label: Symbol, stmt: Box) -> V; + + fn visit_yield_star(&mut self, span: Span, right: Box) -> V; } pub fn accept_default>(this: &mut V, Statement { kind, span }: Statement) -> T { @@ -192,5 +194,6 @@ where ExprKind::Object(e) => this.visit_object_literal(span, e), ExprKind::Compiled(..) => on_empty(this), ExprKind::Empty => this.visit_empty_expr(), + ExprKind::YieldStar(e) => this.visit_yield_star(span, e), } } diff --git a/crates/dash_optimizer/src/consteval.rs b/crates/dash_optimizer/src/consteval.rs index f0646856..1e69a761 100644 --- a/crates/dash_optimizer/src/consteval.rs +++ b/crates/dash_optimizer/src/consteval.rs @@ -252,6 +252,7 @@ impl<'b, 'interner> ConstFunctionEvalCtx<'b, 'interner> { ExprKind::Array(..) => self.visit_array_expression(expression), ExprKind::Object(..) => self.visit_object_expression(expression), ExprKind::Compiled(..) => {} + ExprKind::YieldStar(e) => self.visit(e), ExprKind::Empty => {} } } diff --git a/crates/dash_optimizer/src/type_infer.rs b/crates/dash_optimizer/src/type_infer.rs index ec3927aa..1263b39d 100644 --- a/crates/dash_optimizer/src/type_infer.rs +++ b/crates/dash_optimizer/src/type_infer.rs @@ -388,6 +388,10 @@ impl<'s> TypeInferCtx<'s> { ExprKind::Object(expr) => self.visit_object_expression(expr), ExprKind::Compiled(..) => None, ExprKind::Empty => None, + ExprKind::YieldStar(e) => { + self.visit(e); + None + } } } diff --git a/crates/dash_parser/src/expr.rs b/crates/dash_parser/src/expr.rs index d7081c05..2f43daad 100644 --- a/crates/dash_parser/src/expr.rs +++ b/crates/dash_parser/src/expr.rs @@ -36,10 +36,19 @@ impl<'a, 'interner> Parser<'a, 'interner> { fn parse_yield(&mut self) -> Option { if self.eat(TokenType::Yield, false).is_some() { - let lo_span = self.previous()?.span; + let yield_lo_span = self.previous()?.span; + + if !self.at_lineterm() && self.eat(TokenType::Star, false).is_some() { + let right = self.parse_yield()?; + return Some(Expr { + span: yield_lo_span.to(right.span), + kind: ExprKind::YieldStar(Box::new(right)), + }); + } + let right = self.parse_yield()?; return Some(Expr { - span: lo_span.to(right.span), + span: yield_lo_span.to(right.span), kind: ExprKind::unary(TokenType::Yield, right), }); }