Skip to content

Commit

Permalink
Merge pull request #12 from rusty-ecma/feat/optional-chaining
Browse files Browse the repository at this point in the history
new operators, first try at optional chain
  • Loading branch information
FreeMasen authored Apr 27, 2024
2 parents 5811733 + e5cd232 commit dce9d64
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 9 deletions.
13 changes: 10 additions & 3 deletions src/expr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::pat::Pat;
use crate::{AssignOp, BinaryOp, IntoAllocated, LogicalOp, PropKind, UnaryOp, UpdateOp};
use crate::{
AssignOp, BinaryOp, IntoAllocated, LogicalOp, MemberIndexer, PropKind, UnaryOp, UpdateOp,
};
use crate::{Class, Func, FuncArg, FuncBody, Ident};

#[cfg(feature = "serde")]
Expand Down Expand Up @@ -89,6 +91,7 @@ pub enum Expr<T> {
Update(UpdateExpr<T>),
/// yield a value from inside of a generator function
Yield(YieldExpr<T>),
OptionalChain(Box<Expr<T>>),
}

impl<T> IntoAllocated for Expr<T>
Expand Down Expand Up @@ -134,6 +137,7 @@ where
Expr::Unary(inner) => Expr::Unary(inner.into_allocated()),
Expr::Update(inner) => Expr::Update(inner.into_allocated()),
Expr::Yield(inner) => Expr::Yield(inner.into_allocated()),
Expr::OptionalChain(inner) => Expr::OptionalChain(inner.into_allocated()),
}
}
}
Expand Down Expand Up @@ -406,7 +410,7 @@ where
pub struct MemberExpr<T> {
pub object: Box<Expr<T>>,
pub property: Box<Expr<T>>,
pub computed: bool,
pub indexer: MemberIndexer,
}

impl<T> IntoAllocated for MemberExpr<T>
Expand All @@ -419,7 +423,7 @@ where
MemberExpr {
object: self.object.into_allocated(),
property: self.property.into_allocated(),
computed: self.computed,
indexer: self.indexer,
}
}
}
Expand Down Expand Up @@ -458,6 +462,7 @@ where
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct CallExpr<T> {
pub optional: bool,
pub callee: Box<Expr<T>>,
pub arguments: Vec<Expr<T>>,
}
Expand All @@ -470,6 +475,7 @@ where

fn into_allocated(self) -> Self::Allocated {
CallExpr {
optional: self.optional,
callee: self.callee.into_allocated(),
arguments: self
.arguments
Expand Down Expand Up @@ -602,6 +608,7 @@ where
}
}
}

/// A Template literal preceded by a function identifier
/// see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates) for more details
#[derive(PartialEq, Debug, Clone)]
Expand Down
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,17 @@ impl<T> Class<T> {
}
}

/// The ways to access the member of a value
/// Either a Period `.`, Computed `[ ]`, Optional `?.` or optional computed `?.[ ]`
#[derive(Debug, Clone, PartialEq, Copy)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum MemberIndexer {
Period,
Computed,
Optional,
OptionalComputed,
}

/// The kind of variable being defined (`var`/`let`/`const`)
#[derive(Debug, Clone, PartialEq, Copy)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
Expand Down Expand Up @@ -367,6 +378,9 @@ pub enum AssignOp {
XOrEqual,
AndEqual,
PowerOfEqual,
DoubleAmpersandEqual,
DoublePipeEqual,
DoubleQuestionmarkEqual,
}

/// The available logical operators
Expand All @@ -375,6 +389,7 @@ pub enum AssignOp {
pub enum LogicalOp {
Or,
And,
NullishCoalescing,
}

/// The available operations for `Binary` Exprs
Expand Down
21 changes: 19 additions & 2 deletions src/spanned/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ mod decl {
}

mod expr {
use crate::spanned::expr::MemberIndexer;
use crate::spanned::{
expr::Boolean,
tokens::{QuasiQuote, Quote},
Expand Down Expand Up @@ -219,6 +220,7 @@ mod expr {
Expr::Update(inner) => Self::Update(inner.into()),
Expr::Yield(inner) => Self::Yield(inner.into()),
Expr::Wrapped(inner) => inner.expr.into(),
Expr::OptionalChain(inner) => Self::OptionalChain(Box::new((*inner.expr).into())),
}
}
}
Expand Down Expand Up @@ -408,13 +410,23 @@ mod expr {
}
}

impl From<MemberIndexer> for crate::MemberIndexer {
fn from(other: MemberIndexer) -> Self {
match other {
MemberIndexer::Period(_) => crate::MemberIndexer::Period,
MemberIndexer::Computed { .. } => crate::MemberIndexer::Computed,
MemberIndexer::Optional(_) => crate::MemberIndexer::Optional,
MemberIndexer::OptionalComputed { .. } => crate::MemberIndexer::OptionalComputed,
}
}
}

impl<T> From<MemberExpr<T>> for crate::expr::MemberExpr<T> {
fn from(other: MemberExpr<T>) -> Self {
let computed = other.computed();
Self {
object: Box::new(From::from(*other.object)),
property: Box::new(From::from(*other.property)),
computed,
indexer: From::from(other.indexer),
}
}
}
Expand All @@ -432,6 +444,7 @@ mod expr {
impl<T> From<CallExpr<T>> for crate::expr::CallExpr<T> {
fn from(other: CallExpr<T>) -> Self {
Self {
optional: other.optional.is_some(),
callee: Box::new(From::from(*other.callee)),
arguments: other.arguments.into_iter().map(|e| e.item.into()).collect(),
}
Expand Down Expand Up @@ -692,6 +705,9 @@ impl From<AssignOp> for crate::AssignOp {
AssignOp::XOrEqual(_) => Self::XOrEqual,
AssignOp::AndEqual(_) => Self::AndEqual,
AssignOp::PowerOfEqual(_) => Self::PowerOfEqual,
AssignOp::DoubleAmpersandEqual(_) => Self::DoubleAmpersandEqual,
AssignOp::DoublePipeEqual(_) => Self::DoublePipeEqual,
AssignOp::DoubleQuestionmarkEqual(_) => Self::DoubleQuestionmarkEqual,
}
}
}
Expand All @@ -701,6 +717,7 @@ impl From<LogicalOp> for crate::LogicalOp {
match other {
LogicalOp::Or(_) => Self::Or,
LogicalOp::And(_) => Self::And,
LogicalOp::NullishCoalescing(_) => Self::NullishCoalescing,
}
}
}
Expand Down
67 changes: 63 additions & 4 deletions src/spanned/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use crate::spanned::{Class, Func, FuncArg, FuncBody, Ident};
use crate::IntoAllocated;

use super::tokens::{
AssignOp, Asterisk, Async, Await, BinaryOp, CloseBrace, CloseBracket, CloseParen, Colon, Comma,
Ellipsis, False, FatArrow, ForwardSlash, Get, LogicalOp, New, Null, OpenBrace, OpenBracket,
OpenParen, Period, QuasiQuote, QuestionMark, Quote, Set, Static, Super, This, Token, True,
UnaryOp, UpdateOp, Yield,
self, AssignOp, Asterisk, Async, Await, BinaryOp, CloseBrace, CloseBracket, CloseParen, Colon,
Comma, Ellipsis, False, FatArrow, ForwardSlash, Get, LogicalOp, New, Null, OpenBrace,
OpenBracket, OpenParen, Period, QuasiQuote, QuestionMark, QuestionMarkDot, Quote, Set, Static,
Super, This, Token, True, UnaryOp, UpdateOp, Yield,
};
use super::{FuncArgEntry, ListEntry, Node, Slice, SourceLocation};
#[cfg(feature = "serde")]
Expand Down Expand Up @@ -97,6 +97,7 @@ pub enum Expr<T> {
Wrapped(Box<WrappedExpr<T>>),
/// yield a value from inside of a generator function
Yield(YieldExpr<T>),
OptionalChain(OptionalChain<T>),
}

impl<T> IntoAllocated for Expr<T>
Expand Down Expand Up @@ -139,6 +140,7 @@ where
Expr::Update(inner) => Expr::Update(inner.into_allocated()),
Expr::Wrapped(inner) => Expr::Wrapped(inner.into_allocated()),
Expr::Yield(inner) => Expr::Yield(inner.into_allocated()),
Expr::OptionalChain(inner) => Expr::OptionalChain(inner.into_allocated()),
}
}
}
Expand Down Expand Up @@ -172,6 +174,7 @@ impl<T> Node for Expr<T> {
Expr::Update(inner) => inner.loc(),
Expr::Yield(inner) => inner.loc(),
Expr::Wrapped(inner) => inner.loc(),
Expr::OptionalChain(inner) => inner.loc(),
}
}
}
Expand Down Expand Up @@ -967,6 +970,7 @@ where
impl<T> MemberExpr<T> {
pub fn computed(&self) -> bool {
matches!(self.indexer, MemberIndexer::Computed { .. })
|| matches!(self.indexer, MemberIndexer::OptionalComputed { .. })
}
}

Expand All @@ -983,6 +987,15 @@ impl<T> Node for MemberExpr<T> {
}
}

/// An indexer
/// Either a Period ".", Computed "[..]", Optional "?." or optional computed "?.[..]"
/// ```js
/// var a = {b: 'c'};
/// a.b
/// a["b"]
/// a?.b
/// a?.["b"]
/// ```
#[derive(PartialEq, Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum MemberIndexer {
Expand All @@ -991,6 +1004,12 @@ pub enum MemberIndexer {
open_bracket: OpenBracket,
close_bracket: CloseBracket,
},
Optional(QuestionMarkDot),
OptionalComputed {
optional: QuestionMarkDot,
open_bracket: OpenBracket,
close_bracket: CloseBracket,
},
}

impl Node for MemberIndexer {
Expand All @@ -1004,10 +1023,47 @@ impl Node for MemberIndexer {
start: open_bracket.start(),
end: close_bracket.end(),
},
MemberIndexer::Optional(inner) => inner.loc(),
MemberIndexer::OptionalComputed {
optional,
open_bracket: _,
close_bracket,
} => SourceLocation {
start: optional.start(),
end: close_bracket.end(),
},
}
}
}

#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct OptionalChain<T> {
pub expr: Box<Expr<T>>,
pub op: tokens::QuestionMarkDot,
}

impl<T> IntoAllocated for OptionalChain<T>
where
T: ToString,
{
type Allocated = OptionalChain<String>;
fn into_allocated(self) -> Self::Allocated {
OptionalChain {
expr: Box::new((*self.expr).into_allocated()),
op: self.op,
}
}
}

impl<T> Node for OptionalChain<T> {
fn loc(&self) -> SourceLocation {
let start = self.expr.loc().start;
let end = self.op.end();
SourceLocation { start, end }
}
}

/// A ternery expression
/// ```js
/// var a = true ? 'stuff' : 'things';
Expand Down Expand Up @@ -1049,11 +1105,13 @@ impl<T> Node for ConditionalExpr<T> {
/// Calling a function or method
/// ```js
/// Math.random()
/// Math.random?.()
/// ```
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct CallExpr<T> {
pub callee: Box<Expr<T>>,
pub optional: Option<QuestionMarkDot>,
pub open_paren: OpenParen,
pub arguments: Vec<ListEntry<Expr<T>>>,
pub close_paren: CloseParen,
Expand All @@ -1068,6 +1126,7 @@ where
fn into_allocated(self) -> Self::Allocated {
CallExpr {
callee: self.callee.into_allocated(),
optional: self.optional,
open_paren: self.open_paren,
arguments: self
.arguments
Expand Down
13 changes: 13 additions & 0 deletions src/spanned/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ define_token!(CloseBracket, "]");
define_token!(Colon, ":");
define_token!(Comma, ",");
define_token!(DoubleAmpersand, "&&");
define_token!(DoubleAmpersandEqual, "&&=");
define_token!(DoubleAsterisk, "**");
define_token!(DoubleAsteriskEqual, "**=");
define_token!(DoubleEqual, "==");
Expand All @@ -171,6 +172,9 @@ define_token!(DoubleGreaterThanEqual, ">>=");
define_token!(DoubleLessThan, "<<");
define_token!(DoubleLessThanEqual, "<<=");
define_token!(DoublePipe, "||");
define_token!(DoublePipeEqual, "||=");
define_token!(DoubleQuestionmark, "??");
define_token!(DoubleQuestionmarkEqual, "??=");
define_token!(DoubleQuote, "\"");
define_token!(Ellipsis, "...");
define_token!(Equal, "=");
Expand All @@ -194,6 +198,7 @@ define_token!(PipeEqual, "|=");
define_token!(Plus, "+");
define_token!(PlusEqual, "+=");
define_token!(QuestionMark, "?");
define_token!(QuestionMarkDot, "?.");
define_token!(Semicolon, ";");
define_token!(SingleQuote, "'");
define_token!(Tilde, "~");
Expand Down Expand Up @@ -282,6 +287,9 @@ pub enum AssignOp {
XOrEqual(CaretEqual),
AndEqual(AmpersandEqual),
PowerOfEqual(DoubleAsteriskEqual),
DoubleAmpersandEqual(DoubleAmpersandEqual),
DoublePipeEqual(DoublePipeEqual),
DoubleQuestionmarkEqual(DoubleQuestionmarkEqual),
}

impl Node for AssignOp {
Expand All @@ -300,6 +308,9 @@ impl Node for AssignOp {
AssignOp::XOrEqual(tok) => tok.loc(),
AssignOp::AndEqual(tok) => tok.loc(),
AssignOp::PowerOfEqual(tok) => tok.loc(),
AssignOp::DoubleAmpersandEqual(tok) => tok.loc(),
AssignOp::DoublePipeEqual(tok) => tok.loc(),
AssignOp::DoubleQuestionmarkEqual(tok) => tok.loc(),
}
}
}
Expand All @@ -310,13 +321,15 @@ impl Node for AssignOp {
pub enum LogicalOp {
Or(DoublePipe),
And(DoubleAmpersand),
NullishCoalescing(DoubleQuestionmark),
}

impl Node for LogicalOp {
fn loc(&self) -> SourceLocation {
match self {
LogicalOp::Or(tok) => tok.loc(),
LogicalOp::And(tok) => tok.loc(),
LogicalOp::NullishCoalescing(tok) => tok.loc(),
}
}
}
Expand Down

0 comments on commit dce9d64

Please sign in to comment.