From 38c8543ce859707b616884030738468cd7e94177 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:16:11 +0100 Subject: [PATCH] basic support for optional chaining --- crates/dash_compiler/src/for_each_loop.rs | 65 +++++----- crates/dash_compiler/src/lib.rs | 88 ++++++++------ crates/dash_compiler/src/transformations.rs | 22 ++-- crates/dash_lexer/src/lib.rs | 125 +++++++++----------- crates/dash_middle/src/lexer/token.rs | 10 +- crates/dash_middle/src/parser/expr.rs | 30 ++++- crates/dash_middle/src/util.rs | 4 +- crates/dash_middle/src/visitor.rs | 5 +- crates/dash_optimizer/src/consteval.rs | 5 +- crates/dash_optimizer/src/type_infer.rs | 7 +- crates/dash_parser/src/expr.rs | 96 +++++++++------ 11 files changed, 246 insertions(+), 211 deletions(-) diff --git a/crates/dash_compiler/src/for_each_loop.rs b/crates/dash_compiler/src/for_each_loop.rs index a4ed7966..2ea17606 100644 --- a/crates/dash_compiler/src/for_each_loop.rs +++ b/crates/dash_compiler/src/for_each_loop.rs @@ -1,7 +1,7 @@ use std::mem; use dash_middle::compiler::instruction::AssignKind; -use dash_middle::interner::{sym, Symbol}; +use dash_middle::interner::{Symbol, sym}; use dash_middle::lexer::token::TokenType; use dash_middle::parser::error::Error; use dash_middle::parser::expr::{Expr, ExprKind}; @@ -99,60 +99,53 @@ impl<'a, 'cx, 'interner> ForEachDesugarCtxt<'a, 'cx, 'interner> { /// 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 { + 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::unary( - TokenType::LogicalNot, + kind: ExprKind::property_access( + false, Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, + kind: ExprKind::assignment_local_space( + self.gen_step_local, Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::assignment_local_space( - self.gen_step_local, + kind: ExprKind::function_call( Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::function_call( + kind: ExprKind::property_access( + false, Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( + kind: ExprKind::compiled(compile_local_load( + self.iterator_local, 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), - }, - ), + )), + }, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::next), }, - Vec::new(), - false, ), }, - TokenType::Assignment, + Vec::new(), + false, ), }, - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::done), - }, + TokenType::Assignment, ), }, + Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::done), + }, ), - }, - body, + }), }, - )?; + body, + })?; Ok(()) } diff --git a/crates/dash_compiler/src/lib.rs b/crates/dash_compiler/src/lib.rs index ebd45a99..4a3cb37a 100644 --- a/crates/dash_compiler/src/lib.rs +++ b/crates/dash_compiler/src/lib.rs @@ -2,19 +2,19 @@ use std::cell::{Cell, RefCell}; use std::collections::HashSet; use std::rc::Rc; -use dash_log::{debug, span, Level}; +use dash_log::{Level, debug, span}; use dash_middle::compiler::constant::{Buffer, ConstantPool, Function, NumberConstant, SymbolConstant}; use dash_middle::compiler::external::External; use dash_middle::compiler::instruction::{AssignKind, Instruction, IntrinsicOperation}; use dash_middle::compiler::scope::{CompileValueType, LimitExceededError, Local, ScopeGraph}; use dash_middle::compiler::{CompileResult, DebugSymbols, FunctionCallMetadata, StaticImportKind}; -use dash_middle::interner::{sym, StringInterner, Symbol}; +use dash_middle::interner::{StringInterner, Symbol, sym}; use dash_middle::lexer::token::TokenType; use dash_middle::parser::error::Error; use dash_middle::parser::expr::{ ArrayLiteral, ArrayMemberKind, AssignmentExpr, AssignmentTarget, BinaryExpr, CallArgumentKind, ConditionalExpr, - Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind, Postfix, - PropertyAccessExpr, Seq, UnaryExpr, + Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind, + OptionalChainingComponent, OptionalChainingExpression, Postfix, PropertyAccessExpr, Seq, UnaryExpr, }; use dash_middle::parser::statement::{ Asyncness, Binding, BlockStatement, Class, ClassMember, ClassMemberKey, ClassMemberValue, DoWhileLoop, ExportKind, @@ -25,9 +25,9 @@ use dash_middle::parser::statement::{ use dash_middle::sourcemap::Span; use dash_middle::util::Counter; use dash_middle::visitor::Visitor; +use dash_optimizer::OptLevel; 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; @@ -414,6 +414,7 @@ impl Visitor> for FunctionCompiler<'_> { ExprKind::Class(e) => self.visit_class_expr(span, e), ExprKind::Array(e) => self.visit_array_literal(span, e), ExprKind::Object(e) => self.visit_object_literal(span, e), + ExprKind::Chaining(o) => self.visit_optional_chaining_expression(span, o), ExprKind::Compiled(mut buf) => { self.current_function_mut().buf.append(&mut buf); Ok(()) @@ -1529,6 +1530,35 @@ impl Visitor> for FunctionCompiler<'_> { Ok(()) } + fn visit_optional_chaining_expression( + &mut self, + span: Span, + chain: OptionalChainingExpression, + ) -> Result<(), Error> { + let mut ib = InstructionBuilder::new(self); + ib.accept_expr(*chain.base)?; + + ib.build_jmpnullishnp(Label::IfBranch { branch_id: 0 }, true); + + for component in chain.components { + match component { + OptionalChainingComponent::Ident(ident) => { + ib.build_static_prop_access(ident, false) + .map_err(|_| Error::ConstantPoolLimitExceeded(span))?; + } + } + } + ib.build_jmp(Label::IfEnd, true); + + ib.add_local_label(Label::IfBranch { branch_id: 0 }); + ib.build_undefined_constant() + .map_err(|_| Error::ConstantPoolLimitExceeded(span))?; + + ib.add_local_label(Label::IfEnd); + + Ok(()) + } + fn visit_sequence_expr(&mut self, _span: Span, (expr1, expr2): Seq) -> Result<(), Error> { self.accept_expr(*expr1)?; InstructionBuilder::new(self).build_pop(); @@ -2222,14 +2252,10 @@ impl Visitor> for FunctionCompiler<'_> { // Class.prototype let class_prototype = Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - load_class_binding.clone(), - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::prototype), - }, - ), + kind: ExprKind::property_access(false, load_class_binding.clone(), Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::prototype), + }), }; let methods = class.members.iter().filter(|member| { @@ -2260,25 +2286,17 @@ impl Visitor> for FunctionCompiler<'_> { kind: ExprKind::assignment( Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - class_prototype.clone(), - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::__proto__), - }, - ), + kind: ExprKind::property_access(false, class_prototype.clone(), Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::__proto__), + }), }, Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - super_id.clone(), - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::prototype), - }, - ), + kind: ExprKind::property_access(false, super_id.clone(), Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::prototype), + }), }, TokenType::Assignment, ), @@ -2291,14 +2309,10 @@ impl Visitor> for FunctionCompiler<'_> { kind: ExprKind::assignment( Expr { span: Span::COMPILER_GENERATED, - kind: ExprKind::property_access( - false, - load_class_binding.clone(), - Expr { - span: Span::COMPILER_GENERATED, - kind: ExprKind::identifier(sym::__proto__), - }, - ), + kind: ExprKind::property_access(false, load_class_binding.clone(), Expr { + span: Span::COMPILER_GENERATED, + kind: ExprKind::identifier(sym::__proto__), + }), }, super_id, TokenType::Assignment, diff --git a/crates/dash_compiler/src/transformations.rs b/crates/dash_compiler/src/transformations.rs index edf3a7b1..f9e7623b 100644 --- a/crates/dash_compiler/src/transformations.rs +++ b/crates/dash_compiler/src/transformations.rs @@ -121,13 +121,10 @@ pub fn hoist_declarations( } if !stmts.is_empty() { - stmts.insert( - 0, - Statement { - span: Span::COMPILER_GENERATED, - kind: StatementKind::Block(BlockStatement(prepend, *id)), - }, - ); + stmts.insert(0, Statement { + span: Span::COMPILER_GENERATED, + kind: StatementKind::Block(BlockStatement(prepend, *id)), + }); } } @@ -200,12 +197,9 @@ pub fn hoist_declarations( if !ast.is_empty() { let block_id = scopes.add_empty_block_scope(at, counter); - ast.insert( - 0, - Statement { - span: Span::COMPILER_GENERATED, - kind: StatementKind::Block(BlockStatement(prepend_function_assigns, block_id)), - }, - ); + ast.insert(0, Statement { + span: Span::COMPILER_GENERATED, + kind: StatementKind::Block(BlockStatement(prepend_function_assigns, block_id)), + }); } } diff --git a/crates/dash_lexer/src/lib.rs b/crates/dash_lexer/src/lib.rs index 677e03d5..c4bfa9f0 100644 --- a/crates/dash_lexer/src/lib.rs +++ b/crates/dash_lexer/src/lib.rs @@ -6,8 +6,8 @@ use std::borrow::Cow; use std::ops::Range; -use dash_middle::interner::{sym, StringInterner, Symbol}; -use dash_middle::lexer::token::{as_token, Token, TokenType, EXPR_PRECEDED_TOKENS}; +use dash_middle::interner::{StringInterner, Symbol, sym}; +use dash_middle::lexer::token::{EXPR_PRECEDED_TOKENS, Token, TokenType, as_token}; use dash_middle::parser::error::Error; use dash_middle::sourcemap::Span; use dash_middle::util; @@ -506,57 +506,42 @@ impl<'a, 'interner> Lexer<'a, 'interner> { b',' => self.token(TokenType::Comma), b'.' if self.current().is_some_and(|d| d.is_ascii_digit()) => self.read_number_literal(), b'.' => self.token(TokenType::Dot), - b'-' => self.conditional_token( - TokenType::Minus, - &[("-", TokenType::Decrement), ("=", TokenType::SubtractionAssignment)], - ), - b'+' => self.conditional_token( - TokenType::Plus, - &[("+", TokenType::Increment), ("=", TokenType::AdditionAssignment)], - ), - b'*' => self.conditional_token( - TokenType::Star, - &[ - ("*=", TokenType::ExponentiationAssignment), - ("*", TokenType::Exponentiation), - ("=", TokenType::MultiplicationAssignment), - ], - ), - b'|' => self.conditional_token( - TokenType::BitwiseOr, - &[ - ("|=", TokenType::LogicalOrAssignment), - ("=", TokenType::BitwiseOrAssignment), - ("|", TokenType::LogicalOr), - ], - ), + b'-' => self.conditional_token(TokenType::Minus, &[ + ("-", TokenType::Decrement), + ("=", TokenType::SubtractionAssignment), + ]), + b'+' => self.conditional_token(TokenType::Plus, &[ + ("+", TokenType::Increment), + ("=", TokenType::AdditionAssignment), + ]), + b'*' => self.conditional_token(TokenType::Star, &[ + ("*=", TokenType::ExponentiationAssignment), + ("*", TokenType::Exponentiation), + ("=", TokenType::MultiplicationAssignment), + ]), + b'|' => self.conditional_token(TokenType::BitwiseOr, &[ + ("|=", TokenType::LogicalOrAssignment), + ("=", TokenType::BitwiseOrAssignment), + ("|", TokenType::LogicalOr), + ]), b'^' => self.conditional_token(TokenType::BitwiseXor, &[("=", TokenType::BitwiseXorAssignment)]), - b'&' => self.conditional_token( - TokenType::BitwiseAnd, - &[ - ("&=", TokenType::LogicalAndAssignment), - ("=", TokenType::BitwiseAndAssignment), - ("&", TokenType::LogicalAnd), - ], - ), - b'>' => self.conditional_token( - TokenType::Greater, - &[ - (">>=", TokenType::UnsignedRightShiftAssignment), - (">=", TokenType::RightShiftAssignment), - (">>", TokenType::UnsignedRightShift), - ("=", TokenType::GreaterEqual), - (">", TokenType::RightShift), - ], - ), - b'<' => self.conditional_token( - TokenType::Less, - &[ - ("<=", TokenType::LeftShiftAssignment), - ("=", TokenType::LessEqual), - ("<", TokenType::LeftShift), - ], - ), + b'&' => self.conditional_token(TokenType::BitwiseAnd, &[ + ("&=", TokenType::LogicalAndAssignment), + ("=", TokenType::BitwiseAndAssignment), + ("&", TokenType::LogicalAnd), + ]), + b'>' => self.conditional_token(TokenType::Greater, &[ + (">>=", TokenType::UnsignedRightShiftAssignment), + (">=", TokenType::RightShiftAssignment), + (">>", TokenType::UnsignedRightShift), + ("=", TokenType::GreaterEqual), + (">", TokenType::RightShift), + ]), + b'<' => self.conditional_token(TokenType::Less, &[ + ("<=", TokenType::LeftShiftAssignment), + ("=", TokenType::LessEqual), + ("<", TokenType::LeftShift), + ]), b'%' => self.conditional_token(TokenType::Remainder, &[("=", TokenType::RemainderAssignment)]), b'/' => match self.tokens.last() { // '/' is very ambiguous, probably the most ambiguous character in the grammar @@ -582,30 +567,26 @@ impl<'a, 'interner> Lexer<'a, 'interner> { None => self.read_regex_literal(), _ => self.conditional_token(TokenType::Slash, &[("=", TokenType::DivisionAssignment)]), }, - b'!' => self.conditional_token( - TokenType::LogicalNot, - &[("==", TokenType::StrictInequality), ("=", TokenType::Inequality)], - ), + b'!' => self.conditional_token(TokenType::LogicalNot, &[ + ("==", TokenType::StrictInequality), + ("=", TokenType::Inequality), + ]), b'~' => self.token(TokenType::BitwiseNot), - b'?' => self.conditional_token( - TokenType::Conditional, - &[ - ("?=", TokenType::LogicalNullishAssignment), - ("?", TokenType::NullishCoalescing), - (".", TokenType::OptionalChaining), - ], - ), + b'?' => self.conditional_token(TokenType::Conditional, &[ + ("?=", TokenType::LogicalNullishAssignment), + (".[", TokenType::OptionalBracket), + (".(", TokenType::OptionalCall), + ("?", TokenType::NullishCoalescing), + (".", TokenType::OptionalDot), + ]), b'#' => self.token(TokenType::Hash), b':' => self.token(TokenType::Colon), b';' => self.token(TokenType::Semicolon), - b'=' => self.conditional_token( - TokenType::Assignment, - &[ - ("==", TokenType::StrictEquality), - ("=", TokenType::Equality), - (">", TokenType::FatArrow), - ], - ), + b'=' => self.conditional_token(TokenType::Assignment, &[ + ("==", TokenType::StrictEquality), + ("=", TokenType::Equality), + (">", TokenType::FatArrow), + ]), b'"' | b'\'' => self.read_string_literal(), b'`' => self.read_template_literal_segment(), _ => { diff --git a/crates/dash_middle/src/lexer/token.rs b/crates/dash_middle/src/lexer/token.rs index 5aa58593..82c0ffcd 100644 --- a/crates/dash_middle/src/lexer/token.rs +++ b/crates/dash_middle/src/lexer/token.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::interner::{sym, Symbol}; +use crate::interner::{Symbol, sym}; use crate::sourcemap::Span; use derive_more::Display; @@ -278,7 +278,13 @@ pub enum TokenType { Conditional, #[display(fmt = "?.")] - OptionalChaining, + OptionalDot, + + #[display(fmt = "?.[")] + OptionalBracket, + + #[display(fmt = "?.(")] + OptionalCall, #[display(fmt = "for")] For, diff --git a/crates/dash_middle/src/parser/expr.rs b/crates/dash_middle/src/parser/expr.rs index 69a0f77d..263128f7 100644 --- a/crates/dash_middle/src/parser/expr.rs +++ b/crates/dash_middle/src/parser/expr.rs @@ -2,11 +2,11 @@ use std::fmt::{self, Debug}; use derive_more::Display; -use crate::interner::{sym, Symbol}; +use crate::interner::{Symbol, sym}; use crate::lexer::token::TokenType; use crate::sourcemap::Span; -use super::statement::{fmt_list, Class, FunctionDeclaration}; +use super::statement::{Class, FunctionDeclaration, fmt_list}; /// The sequence operator (`expr, expr`) pub type Seq = (Box, Box); @@ -55,6 +55,8 @@ pub enum ExprKind { Array(ArrayLiteral), /// An object literal expression Object(ObjectLiteral), + /// An optional chaining expression + Chaining(OptionalChainingExpression), /// Compiled bytecode #[display(fmt = "")] Compiled(Vec), @@ -62,6 +64,30 @@ pub enum ExprKind { Empty, } +#[derive(Debug, Clone)] +pub struct OptionalChainingExpression { + pub base: Box, + pub components: Vec, +} + +impl fmt::Display for OptionalChainingExpression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&*self.base, f)?; + f.write_str("?")?; + for component in &self.components { + match component { + OptionalChainingComponent::Ident(symbol) => write!(f, ".{symbol}")?, + } + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub enum OptionalChainingComponent { + Ident(Symbol), +} + #[derive(Debug, Clone, Display)] #[display(fmt = "{kind}")] pub struct Expr { diff --git a/crates/dash_middle/src/util.rs b/crates/dash_middle/src/util.rs index fa10cf22..ff31ee5b 100644 --- a/crates/dash_middle/src/util.rs +++ b/crates/dash_middle/src/util.rs @@ -6,8 +6,6 @@ use std::mem::ManuallyDrop; use std::ops::RangeInclusive; use std::thread::ThreadId; -use smallvec::SmallVec; - const DIGIT: RangeInclusive = b'0'..=b'9'; const OCTAL_DIGIT: RangeInclusive = b'0'..=b'7'; const IDENTIFIER_START_LOWERCASE: RangeInclusive = b'a'..=b'z'; @@ -197,7 +195,7 @@ impl fmt::Debug for SharedOnce { } #[derive(Debug, Default)] -pub struct LevelStack(SmallVec<[u8; 4]>); +pub struct LevelStack(Vec); impl LevelStack { pub fn new() -> Self { diff --git a/crates/dash_middle/src/visitor.rs b/crates/dash_middle/src/visitor.rs index ea5522be..e1463170 100755 --- a/crates/dash_middle/src/visitor.rs +++ b/crates/dash_middle/src/visitor.rs @@ -1,7 +1,7 @@ use crate::interner::Symbol; use crate::parser::expr::{ ArrayLiteral, AssignmentExpr, BinaryExpr, ConditionalExpr, Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, - ObjectLiteral, Postfix, Prefix, PropertyAccessExpr, Seq, UnaryExpr, + ObjectLiteral, OptionalChainingExpression, Postfix, Prefix, PropertyAccessExpr, Seq, UnaryExpr, }; use crate::parser::statement::{ BlockStatement, Class, DoWhileLoop, ExportKind, ForInLoop, ForLoop, ForOfLoop, FunctionDeclaration, IfStatement, @@ -142,6 +142,8 @@ pub trait Visitor { fn visit_labelled(&mut self, span: Span, label: Symbol, stmt: Box) -> V; fn visit_yield_star(&mut self, span: Span, right: Box) -> V; + + fn visit_optional_chaining_expression(&mut self, span: Span, o: OptionalChainingExpression) -> V; } pub fn accept_default>(this: &mut V, Statement { kind, span }: Statement) -> T { @@ -195,5 +197,6 @@ where ExprKind::Compiled(..) => on_empty(this), ExprKind::Empty => this.visit_empty_expr(), ExprKind::YieldStar(e) => this.visit_yield_star(span, e), + ExprKind::Chaining(c) => this.visit_optional_chaining_expression(span, c), } } diff --git a/crates/dash_optimizer/src/consteval.rs b/crates/dash_optimizer/src/consteval.rs index 1e69a761..8d16f2bd 100644 --- a/crates/dash_optimizer/src/consteval.rs +++ b/crates/dash_optimizer/src/consteval.rs @@ -4,8 +4,8 @@ use dash_middle::interner::StringInterner; use dash_middle::lexer::token::TokenType; use dash_middle::parser::expr::{ ArrayLiteral, ArrayMemberKind, AssignmentExpr, AssignmentTarget, BinaryExpr, CallArgumentKind, ConditionalExpr, - Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind, PropertyAccessExpr, - UnaryExpr, + Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind, + OptionalChainingExpression, PropertyAccessExpr, UnaryExpr, }; use dash_middle::parser::statement::{ BlockStatement, Class, ClassMemberValue, DoWhileLoop, ExportKind, ForInLoop, ForLoop, ForOfLoop, @@ -251,6 +251,7 @@ impl<'b, 'interner> ConstFunctionEvalCtx<'b, 'interner> { ExprKind::Class(class) => self.visit_class_statement(class), ExprKind::Array(..) => self.visit_array_expression(expression), ExprKind::Object(..) => self.visit_object_expression(expression), + ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(base), 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 58971313..e825cd5c 100644 --- a/crates/dash_optimizer/src/type_infer.rs +++ b/crates/dash_optimizer/src/type_infer.rs @@ -2,12 +2,12 @@ use std::cell::RefCell; use dash_log::debug; use dash_middle::compiler::scope::{BlockScope, CompileValueType, FunctionScope, Local, ScopeGraph, ScopeKind}; -use dash_middle::interner::{sym, Symbol}; +use dash_middle::interner::{Symbol, sym}; use dash_middle::lexer::token::TokenType; use dash_middle::parser::expr::{ ArrayLiteral, ArrayMemberKind, AssignmentExpr, AssignmentTarget, BinaryExpr, CallArgumentKind, ConditionalExpr, - Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind, PropertyAccessExpr, - UnaryExpr, + Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind, + OptionalChainingExpression, PropertyAccessExpr, UnaryExpr, }; use dash_middle::parser::statement::{ Binding, BlockStatement, Class, ClassMemberKey, ClassMemberValue, DoWhileLoop, ExportKind, ForInLoop, ForLoop, @@ -394,6 +394,7 @@ impl<'s> TypeInferCtx<'s> { ExprKind::Class(class) => self.visit_class_expression(class), ExprKind::Array(expr) => self.visit_array_expression(expr), ExprKind::Object(expr) => self.visit_object_expression(expr), + ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(&**base), ExprKind::Compiled(..) => None, ExprKind::Empty => None, ExprKind::YieldStar(e) => { diff --git a/crates/dash_parser/src/expr.rs b/crates/dash_parser/src/expr.rs index e988502f..e6fd417b 100644 --- a/crates/dash_parser/src/expr.rs +++ b/crates/dash_parser/src/expr.rs @@ -1,8 +1,9 @@ -use dash_middle::interner::{sym, Symbol}; -use dash_middle::lexer::token::{Token, TokenType, ASSIGNMENT_TYPES}; +use dash_middle::interner::{Symbol, sym}; +use dash_middle::lexer::token::{ASSIGNMENT_TYPES, Token, TokenType}; use dash_middle::parser::error::Error; use dash_middle::parser::expr::{ ArrayMemberKind, AssignmentExpr, AssignmentTarget, CallArgumentKind, Expr, ExprKind, LiteralExpr, ObjectMemberKind, + OptionalChainingComponent, OptionalChainingExpression, PropertyAccessExpr, }; use dash_middle::parser::statement::{ Asyncness, BlockStatement, FunctionDeclaration, FunctionKind, Parameter, ReturnStatement, Statement, StatementKind, @@ -10,7 +11,7 @@ use dash_middle::parser::statement::{ use dash_middle::sourcemap::Span; use dash_regex::Flags; -use crate::{any, Parser}; +use crate::{Parser, any}; impl Parser<'_, '_> { pub fn parse_expression(&mut self) -> Option { @@ -331,7 +332,12 @@ impl Parser<'_, '_> { while self .eat( - any(&[TokenType::LeftParen, TokenType::Dot, TokenType::LeftSquareBrace]), + any(&[ + TokenType::LeftParen, + TokenType::Dot, + TokenType::LeftSquareBrace, + TokenType::OptionalDot, + ]), false, ) .is_some() @@ -371,13 +377,34 @@ impl Parser<'_, '_> { } TokenType::Dot => { let ident = self.expect_identifier_or_reserved_kw(true)?; - let property = Expr { - span: self.previous()?.span, - kind: ExprKind::identifier(ident), + if let ExprKind::Chaining(chain) = &mut expr.kind { + chain.components.push(OptionalChainingComponent::Ident(ident)); + expr.span = expr.span.to(self.previous()?.span); + } else { + let property = Expr { + span: self.previous()?.span, + kind: ExprKind::identifier(ident), + }; + expr = Expr { + span: expr.span.to(property.span), + kind: ExprKind::PropertyAccess(PropertyAccessExpr { + computed: false, + property: Box::new(property), + target: Box::new(expr), + }), + }; + } + } + TokenType::OptionalDot => { + let ident = self.expect_identifier_or_reserved_kw(true)?; + let span = expr.span.to(self.previous()?.span); + let chain = OptionalChainingExpression { + base: Box::new(expr), + components: vec![OptionalChainingComponent::Ident(ident)], }; expr = Expr { - span: expr.span.to(property.span), - kind: ExprKind::property_access(false, expr, property), + span, + kind: ExprKind::Chaining(chain), }; } TokenType::LeftSquareBrace => { @@ -553,31 +580,25 @@ impl Parser<'_, '_> { self.eat(TokenType::LeftBrace, true)?; let body = self.parse_block()?; let id = self.scope_count.inc(); - items.push(( - key, - Expr { - span: current.span.to(self.previous()?.span), - kind: ExprKind::function(FunctionDeclaration { - id, - name: None, - parameters, - statements: body.0, - ty: FunctionKind::Function(Asyncness::No), - ty_segment: None, - constructor_initializers: None, - has_extends_clause: false, - }), - }, - )); + items.push((key, Expr { + span: current.span.to(self.previous()?.span), + kind: ExprKind::function(FunctionDeclaration { + id, + name: None, + parameters, + statements: body.0, + ty: FunctionKind::Function(Asyncness::No), + ty_segment: None, + constructor_initializers: None, + has_extends_clause: false, + }), + })); } else { match key { - ObjectMemberKind::Static(name) => items.push(( - key, - Expr { - span: token.span, - kind: ExprKind::identifier(name), - }, - )), + ObjectMemberKind::Static(name) => items.push((key, Expr { + span: token.span, + kind: ExprKind::identifier(name), + })), ObjectMemberKind::Dynamic(..) => { self.error(Error::unexpected_token(token, TokenType::Colon)); return None; @@ -628,13 +649,10 @@ impl Parser<'_, '_> { constructor_initializers: None, has_extends_clause: false, }; - items.push(( - key, - Expr { - span: current.span.to(self.previous()?.span), - kind: ExprKind::function(fun), - }, - )); + items.push((key, Expr { + span: current.span.to(self.previous()?.span), + kind: ExprKind::function(fun), + })); } ObjectMemberKind::DynamicGetter(_) | ObjectMemberKind::DynamicSetter(_) => { unreachable!("never created")