From a1f1dfc2607b56c896a0cbeaf2c15e46d8a3f0f9 Mon Sep 17 00:00:00 2001 From: Robert Masen Date: Sun, 18 Feb 2024 19:02:21 -0600 Subject: [PATCH] Add new simple operators `&&=` `||=` `??` `??=` --- Cargo.toml | 6 ++-- src/spanned/mod.rs | 36 ++++++++++++++++-------- tests/snippets.rs | 69 ++++++++++++++++++++++++++++++---------------- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b53c961..e89554a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,10 @@ edition = "2021" [dependencies] hash-chain = "0.3" log = "0.4" -ress = "0.11" -resast = "0.6.0-alpha.5" +# ress = "0.11" +# resast = "0.6.0-alpha.5" +ress = { path = "../ress" } +resast = { path = "../resast" } res-regex = "0.1" tracing = "0.1" diff --git a/src/spanned/mod.rs b/src/spanned/mod.rs index 4f6e808..219f052 100644 --- a/src/spanned/mod.rs +++ b/src/spanned/mod.rs @@ -712,11 +712,9 @@ where fn parse_import_default_specifier(&mut self) -> Res>> { let start = self.look_ahead_position; let id = self.parse_ident_name()?; - self.context.lexical_names.declare( - id.slice.source.clone(), - DeclKind::Lex(true), - start, - )?; + self.context + .lexical_names + .declare(id.slice.source.clone(), DeclKind::Lex(true), start)?; Ok(DefaultImportSpec { id }) } @@ -4795,7 +4793,6 @@ where }; elements.push(ele); } - } } let close_bracket = self.expect_punct(Punct::CloseBracket)?; @@ -5070,7 +5067,7 @@ where &item, &[ "=", "+=", "-=", "/=", "*=", "**=", "|=", "&=", "~=", "%=", "<<=", - ">>=", ">>>=", + ">>=", ">>>=", "&&=", "||=", "??=", ], ); } @@ -5080,7 +5077,7 @@ where &item, &[ "=", "+=", "-=", "/=", "*=", "**=", "|=", "&=", "~=", "%=", "<<=", ">>=", - ">>>=", + ">>>=", "&&=", "||=", "??=", ], ); } @@ -5120,6 +5117,9 @@ where Punct::CaretEqual => Some(AssignOp::XOrEqual(slice.into())), Punct::AmpersandEqual => Some(AssignOp::AndEqual(slice.into())), Punct::DoubleAsteriskEqual => Some(AssignOp::PowerOfEqual(slice.into())), + Punct::DoubleAmpersandEqual => Some(AssignOp::DoubleAmpersandEqual(slice.into())), + Punct::DoublePipeEqual => Some(AssignOp::DoublePipeEqual(slice.into())), + Punct::DoubleQuestionMarkEqual => Some(AssignOp::DoubleQuestionmarkEqual(slice.into())), _ => None, } } @@ -5658,6 +5658,7 @@ where log::debug!("left: {:#?} {}", left, self.context.allow_yield); if op.token.matches_punct(Punct::DoubleAmpersand) || op.token.matches_punct(Punct::DoublePipe) + || op.token.matches_punct(Punct::DoubleQuestionMark) { stack.push(Expr::Logical(LogicalExpr { operator: self.logical_operator(&op).ok_or_else(|| { @@ -5692,6 +5693,7 @@ where .ok_or_else(|| self.op_error("invalid binary operation, too few operators"))?; if op.token.matches_punct(Punct::DoubleAmpersand) || op.token.matches_punct(Punct::DoublePipe) + || op.token.matches_punct(Punct::DoubleQuestionMark) { let operator = self .logical_operator(&op) @@ -5855,6 +5857,7 @@ where Token::Punct(ref p) => match p { Punct::DoubleAmpersand => Some(LogicalOp::And(slice.into())), Punct::DoublePipe => Some(LogicalOp::Or(slice.into())), + Punct::DoubleQuestionMark => Some(LogicalOp::NullishCoalescing(slice.into())), _ => None, }, _ => None, @@ -6372,7 +6375,7 @@ where | Punct::Comma | Punct::Equal | Punct::CloseBracket => 0, - Punct::DoublePipe => 1, + Punct::DoublePipe | Punct::DoubleQuestionMark => 1, Punct::DoubleAmpersand => 2, Punct::Pipe => 3, Punct::Caret => 4, @@ -6668,6 +6671,15 @@ where || self.look_ahead.token.matches_punct(Punct::PipeEqual) || self.look_ahead.token.matches_punct(Punct::CaretEqual) || self.look_ahead.token.matches_punct(Punct::AmpersandEqual) + || self + .look_ahead + .token + .matches_punct(Punct::DoubleAmpersandEqual) + || self.look_ahead.token.matches_punct(Punct::DoublePipeEqual) + || self + .look_ahead + .token + .matches_punct(Punct::DoubleQuestionMarkEqual) } /// The keyword `async` is conditional, that means to decided /// if we are actually at an async function we need to check the @@ -6763,8 +6775,8 @@ where let slice = self.scanner.str_for(&item.span).unwrap(); let contents = match &item.token { Token::String(lit) => match lit { - ress::tokens::StringLit::Double(inner) => inner.content.clone(), - ress::tokens::StringLit::Single(inner) => inner.content.clone(), + ress::tokens::StringLit::Double(inner) => inner.content, + ress::tokens::StringLit::Single(inner) => inner.content, }, _ => { return Err(Error::UnexpectedToken( @@ -6821,7 +6833,7 @@ where // regex will be on 1 line if `validate` is successful let line = item.location.start.line; let Token::RegEx(token) = &item.token else { - return self.expected_token_error(item, &[""]) + return self.expected_token_error(item, &[""]); }; let open_slash_pos = Position { line: line as u32, diff --git a/tests/snippets.rs b/tests/snippets.rs index 8fce198..7912eb2 100644 --- a/tests/snippets.rs +++ b/tests/snippets.rs @@ -1283,8 +1283,8 @@ fn setter_scope() { fn array_pattern_with_empty_entry() { use resast::spanned::{ decl::{Decl, VarDecl, VarDecls}, - pat::{ArrayPat, ArrayPatPart, Pat}, expr::Expr, + pat::{ArrayPat, ArrayPatPart, Pat}, Ident, ListEntry, Program, ProgramPart, Slice, SourceLocation, VarKind, }; let js = "let [x,,] = y"; @@ -1298,35 +1298,36 @@ fn array_pattern_with_empty_entry() { item: VarDecl { id: Pat::Array(ArrayPat { open_bracket: Position::new(1, 5).into(), - elements: vec![ListEntry { - item: Some(ArrayPatPart::Pat(Pat::Ident(Ident { - slice: Slice { - source: Cow::Borrowed("x").into(), - loc: SourceLocation { - start: Position::new(1, 6), - end: Position::new(1, 7) + elements: vec![ + ListEntry { + item: Some(ArrayPatPart::Pat(Pat::Ident(Ident { + slice: Slice { + source: Cow::Borrowed("x").into(), + loc: SourceLocation { + start: Position::new(1, 6), + end: Position::new(1, 7) + } } - } - }))), - comma: Some(Position::new(1, 7).into()), - }, ListEntry { - item: None, - comma: Some(Position::new(1, 8).into()), - }], + }))), + comma: Some(Position::new(1, 7).into()), + }, + ListEntry { + item: None, + comma: Some(Position::new(1, 8).into()), + } + ], close_bracket: Position::new(1, 9).into() }), eq: Some(Position::new(1, 11).into()), - init: Some(Expr::Ident( - Ident { - slice: Slice { - source: Cow::Borrowed("y").into(), - loc: SourceLocation { - start: Position::new(1, 13), - end: Position::new(1, 14) - } + init: Some(Expr::Ident(Ident { + slice: Slice { + source: Cow::Borrowed("y").into(), + loc: SourceLocation { + start: Position::new(1, 13), + end: Position::new(1, 14) } } - )), + })), }, comma: None }] @@ -1382,6 +1383,26 @@ fn call_args() { run_spanned_test("call(/.+/, '')", false).unwrap(); } +#[test] +fn and_and_equal() { + run_spanned_test("x &&= false", false).unwrap(); +} + +#[test] +fn or_or_equal() { + run_spanned_test("x ||= false", false).unwrap(); +} + +#[test] +fn q_q_equal() { + run_spanned_test("x ??= false", false).unwrap(); +} + +#[test] +fn nullish_coalesce() { + run_spanned_test("b ?? false", false).unwrap(); +} + fn run_test(js: &str, as_mod: bool) -> Result<(), ressa::Error> { let p = generate_program(js, as_mod); log::debug!("{:#?}", p);