usecrate::numeric_value::{parse_float, parse_int, NumericLiteralBase}; usecrate::parser::Parser; usecrate::unicode::{is_id_continue, is_id_start}; use ast::arena; use ast::source_atom_set::{CommonSourceAtomSetIndices, SourceAtomSet}; use ast::source_slice_list::SourceSliceList; use ast::SourceLocation; use bumpalo::{collections::String, Bump}; use generated_parser::{ParseError, Result, TerminalId, Token, TokenValue}; use std::cell::RefCell; use std::convert::TryFrom; use std::rc::Rc; use std::str::Chars;
/// Create a lexer for a part of a JS script or module. `offset` is the /// total length of all previous parts, in bytes; source locations for /// tokens created by the new lexer start counting from this number. pubfn with_offset(
allocator: &'alloc Bump,
chars: Chars<'alloc>,
offset: usize,
atoms: Rc<RefCell<SourceAtomSet<'alloc>>>,
slices: Rc<RefCell<SourceSliceList<'alloc>>>,
) -> Lexer<'alloc> { let source_length = offset + chars.as_str().len(); letmut token = arena::alloc(allocator, new_token());
token.is_on_new_line = true;
Lexer {
allocator,
token,
source_length,
chars,
atoms,
slices,
}
}
/// Returns an empty token which is meant as a place holder to be mutated later. fn new_token() -> Token {
Token::basic_token(TerminalId::End, SourceLocation::default())
}
impl<'alloc> Lexer<'alloc> { /// Skip a *MultiLineComment*. /// /// ```text /// MultiLineComment :: /// `/*` MultiLineCommentChars? `*/` /// /// MultiLineCommentChars :: /// MultiLineNotAsteriskChar MultiLineCommentChars? /// `*` PostAsteriskCommentChars? /// /// PostAsteriskCommentChars :: /// MultiLineNotForwardSlashOrAsteriskChar MultiLineCommentChars? /// `*` PostAsteriskCommentChars? /// /// MultiLineNotAsteriskChar :: /// SourceCharacter but not `*` /// /// MultiLineNotForwardSlashOrAsteriskChar :: /// SourceCharacter but not one of `/` or `*` /// ``` /// /// (B.1.3 splits MultiLineComment into two nonterminals: MultiLineComment /// and SingleLineDelimitedComment. The point of that is to help specify /// that a SingleLineHTMLCloseComment must occur at the start of a line. We /// use `is_on_new_line` for that.) /// fn skip_multi_line_comment(&mutself, builder: &mut AutoCow<'alloc>) -> Result<'alloc, ()> { whilelet Some(ch) = self.chars.next() { match ch { '*'ifself.peek() == Some('/') => { self.chars.next();
*builder = AutoCow::new(&self); return Ok(());
}
CR | LF | PS | LS => { self.token.is_on_new_line = true;
}
_ => {}
}
}
Err(ParseError::UnterminatedMultiLineComment.into())
}
/// Skip a *SingleLineComment* and the following *LineTerminatorSequence*, /// if any. /// /// ```text /// SingleLineComment :: /// `//` SingleLineCommentChars? /// /// SingleLineCommentChars :: /// SingleLineCommentChar SingleLineCommentChars? /// /// SingleLineCommentChar :: /// SourceCharacter but not LineTerminator /// ``` fn skip_single_line_comment(&mutself, builder: &mut AutoCow<'alloc>) { whilelet Some(ch) = self.chars.next() { match ch {
CR | LF | LS | PS => break,
_ => continue,
}
}
*builder = AutoCow::new(&self); self.token.is_on_new_line = true;
}
}
// ---------------------------------------------------------------------------- // 11.6 Names and Keywords
/// True if `c` is a one-character *IdentifierStart*. /// /// ```text /// IdentifierStart :: /// UnicodeIDStart /// `$` /// `_` /// `\` UnicodeEscapeSequence /// /// UnicodeIDStart :: /// > any Unicode code point with the Unicode property "ID_Start" /// ``` fn is_identifier_start(c: char) -> bool { // Escaped case is handled separately. if c.is_ascii() {
c == '$' || c == '_' || c.is_ascii_alphabetic()
} else {
is_id_start(c)
}
}
/// True if `c` is a one-character *IdentifierPart*. /// /// ```text /// IdentifierPart :: /// UnicodeIDContinue /// `$` /// `\` UnicodeEscapeSequence /// <ZWNJ> /// <ZWJ> /// /// UnicodeIDContinue :: /// > any Unicode code point with the Unicode property "ID_Continue" /// ``` fn is_identifier_part(c: char) -> bool { // Escaped case is handled separately. if c.is_ascii() {
c == '$' || c == '_' || c.is_ascii_alphanumeric()
} else {
is_id_continue(c) || c == ZWNJ || c == ZWJ
}
}
impl<'alloc> Lexer<'alloc> { /// Scan the rest of an IdentifierName, having already parsed the initial /// IdentifierStart and stored it in `builder`. /// /// On success, this returns `Ok((has_escapes, str))`, where `has_escapes` /// is true if the identifier contained any UnicodeEscapeSequences, and /// `str` is the un-escaped IdentifierName, including the IdentifierStart, /// on success. /// /// ```text /// IdentifierName :: /// IdentifierStart /// IdentifierName IdentifierPart /// ``` fn identifier_name_tail(
&mutself, mut builder: AutoCow<'alloc>,
) -> Result<'alloc, (bool, &'alloc str)> { whilelet Some(ch) = self.peek() { if !is_identifier_part(ch) { if ch == '\\' { self.chars.next();
builder.force_allocation_without_current_ascii_char(&self);
let value = self.unicode_escape_sequence_after_backslash()?; if !is_identifier_part(value) { return Err(ParseError::InvalidEscapeSequence.into());
}
let value = self.unicode_escape_sequence_after_backslash()?; if !is_identifier_start(value) { return Err(ParseError::IllegalCharacter(value).into());
}
builder.push_different(value);
}
other if is_identifier_start(other) => {
builder.push_matching(other);
}
/// Finish scanning an *IdentifierName* or keyword, having already scanned /// the *IdentifierStart* and pushed it to `builder`. /// /// `start` is the offset of the *IdentifierStart*. /// /// The lexer doesn't know the syntactic context, so it always identifies /// possible keywords. It's up to the parser to understand that, for /// example, `TerminalId::If` is not a keyword when it's used as a property /// or method name. /// /// If the source string contains no escape and it matches to possible /// keywords (including contextual keywords), the result is corresponding /// `TerminalId`. For example, if the source string is "yield", the result /// is `TerminalId::Yield`. /// /// If the source string contains no escape sequence and also it doesn't /// match to any possible keywords, the result is `TerminalId::Name`. /// /// If the source string contains at least one escape sequence, /// the result is always `TerminalId::NameWithEscape`, regardless of the /// StringValue of it. For example, if the source string is "\u{79}ield", /// the result is `TerminalId::NameWithEscape`, and the StringValue is /// "yield". fn identifier_tail(&mutself, start: usize, builder: AutoCow<'alloc>) -> Result<'alloc, ()> { let (has_different, text) = self.identifier_name_tail(builder)?;
Some('0'..='9') => { // This is almost always the token `0` in practice. // // In nonstrict code, as a legacy feature, other numbers // starting with `0` are allowed. If /0[0-7]+/ matches, it's a // LegacyOctalIntegerLiteral; but if we see an `8` or `9` in // the number, it's decimal. Decimal numbers can have a decimal // point and/or ExponentPart; octals can't. // // Neither is allowed with a BigIntLiteralSuffix `n`. // // LegacyOctalIntegerLiteral :: // `0` OctalDigit // LegacyOctalIntegerLiteral OctalDigit // // NonOctalDecimalIntegerLiteral :: // `0` NonOctalDigit // LegacyOctalLikeDecimalIntegerLiteral NonOctalDigit // NonOctalDecimalIntegerLiteral DecimalDigit // // LegacyOctalLikeDecimalIntegerLiteral :: // `0` OctalDigit // LegacyOctalLikeDecimalIntegerLiteral OctalDigit // // NonOctalDigit :: one of // `8` `9` //
// TODO: implement `strict_mode` check // let strict_mode = true; // if !strict_mode { // // TODO: Distinguish between Octal and NonOctalDecimal. // // TODO: Support NonOctalDecimal followed by a decimal // // point and/or ExponentPart. // self.decimal_digits()?; // } return Err(ParseError::NotImplemented("LegacyOctalIntegerLiteral").into());
}
_ => {}
}
self.check_after_numeric_literal()?;
Ok(NumericResult::Int { base })
}
/// Scan a NumericLiteral (defined in 11.8.3, extended by B.1.1) after /// having already consumed the first character, which is a decimal digit. fn decimal_literal_after_first_digit(&mutself) -> Result<'alloc, NumericResult> { // DecimalLiteral :: // DecimalIntegerLiteral `.` DecimalDigits? ExponentPart? // `.` DecimalDigits ExponentPart? // DecimalIntegerLiteral ExponentPart? // // DecimalIntegerLiteral :: // `0` #see `numeric_literal_starting_with_zero` // NonZeroDigit // NonZeroDigit NumericLiteralSeparator? DecimalDigits // NonOctalDecimalIntegerLiteral #see `numeric_literal_ // # starting_with_zero` // // NonZeroDigit :: one of // `1` `2` `3` `4` `5` `6` `7` `8` `9`
let has_exponent = self.optional_exponent()?; self.check_after_numeric_literal()?;
let result = if has_exponent {
NumericResult::Float
} else {
NumericResult::Int {
base: NumericLiteralBase::Decimal,
}
};
Ok(result)
}
fn decimal_literal_after_decimal_point(&mutself) -> Result<'alloc, NumericResult> { // The parts after `.` in // // `.` DecimalDigits ExponentPart? self.decimal_digits()?; self.optional_exponent()?; self.check_after_numeric_literal()?;
Ok(NumericResult::Float)
}
fn decimal_literal_after_decimal_point_after_digits(
&mutself,
) -> Result<'alloc, NumericResult> { // The parts after `.` in // // DecimalLiteral :: // DecimalIntegerLiteral `.` DecimalDigits? ExponentPart? self.optional_decimal_digits()?; self.optional_exponent()?; self.check_after_numeric_literal()?;
Ok(NumericResult::Float)
}
fn check_after_numeric_literal(&self) -> Result<'alloc, ()> { // The SourceCharacter immediately following a // NumericLiteral must not be an IdentifierStart or // DecimalDigit. (11.8.3) iflet Some(ch) = self.peek() { if is_identifier_start(ch) || ch.is_digit(10) { return Err(ParseError::IllegalCharacter(ch).into());
}
}
Ok(())
}
// ------------------------------------------------------------------------ // 11.8.4 String Literals (as extended by B.1.2)
/// Scan an LineContinuation or EscapeSequence in a string literal, having /// already consumed the initial backslash character. /// /// ```text /// LineContinuation :: /// `\` LineTerminatorSequence /// /// EscapeSequence :: /// CharacterEscapeSequence /// (in strict mode code) `0` [lookahead ∉ DecimalDigit] /// (in non-strict code) LegacyOctalEscapeSequence /// HexEscapeSequence /// UnicodeEscapeSequence /// /// CharacterEscapeSequence :: /// SingleEscapeCharacter /// NonEscapeCharacter /// /// SingleEscapeCharacter :: one of /// `'` `"` `\` `b` `f` `n` `r` `t` `v` /// /// LegacyOctalEscapeSequence :: /// OctalDigit [lookahead ∉ OctalDigit] /// ZeroToThree OctalDigit [lookahead ∉ OctalDigit] /// FourToSeven OctalDigit /// ZeroToThree OctalDigit OctalDigit /// /// ZeroToThree :: one of /// `0` `1` `2` `3` /// /// FourToSeven :: one of /// `4` `5` `6` `7` /// ``` fn escape_sequence(&mutself, text: &mutString<'alloc>) -> Result<'alloc, ()> { matchself.chars.next() {
None => { return Err(ParseError::UnterminatedString.into());
}
Some(c) => match c {
LF | LS | PS => { // LineContinuation. Ignore it. // // Don't set is_on_new_line because this LineContinuation // has no bearing on whether the current string literal was // the first token on the line where it started.
}
CR => { // LineContinuation. Check for the sequence \r\n; otherwise // ignore it. ifself.peek() == Some(LF) { self.chars.next();
}
}
other => { // "\8" and "\9" are invalid per spec, but SpiderMonkey and // V8 accept them, and JSC accepts them in non-strict mode. // "\8" is "8" and "\9" is "9".
text.push(other);
}
},
}
Ok(())
}
/// Scan a string literal, having already consumed the starting quote /// character `delimiter`. /// /// ```text /// StringLiteral :: /// `"` DoubleStringCharacters? `"` /// `'` SingleStringCharacters? `'` /// /// DoubleStringCharacters :: /// DoubleStringCharacter DoubleStringCharacters? /// /// SingleStringCharacters :: /// SingleStringCharacter SingleStringCharacters? /// /// DoubleStringCharacter :: /// SourceCharacter but not one of `"` or `\` or LineTerminator /// <LS> /// <PS> /// `\` EscapeSequence /// LineContinuation /// /// SingleStringCharacter :: /// SourceCharacter but not one of `'` or `\` or LineTerminator /// <LS> /// <PS> /// `\` EscapeSequence /// LineContinuation /// ``` fn string_literal(&mutself, delimiter: char) -> Result<'alloc, ()> { let offset = self.offset() - 1; letmut builder = AutoCow::new(&self); loop { matchself.chars.next() {
None | Some('\r') | Some('\n') => { return Err(ParseError::UnterminatedString.into());
}
Some(c @ '"') | Some(c @ '\'') => { if c == delimiter { let value = self.string_to_token_value(builder.finish_without_push(&self)); returnself.set_result(
TerminalId::StringLiteral,
SourceLocation::new(offset, self.offset()),
value,
);
} else {
builder.push_matching(c);
}
}
Some('\\') => { let text = builder.get_mut_string_without_current_ascii_char(&>self); self.escape_sequence(text)?;
}
Some(other) => { // NonEscapeCharacter :: // SourceCharacter but not one of EscapeCharacter or LineTerminator // // EscapeCharacter :: // SingleEscapeCharacter // DecimalDigit // `x` // `u`
builder.push_matching(other);
}
}
}
}
/// Parse a template literal component token, having already consumed the /// starting `` ` `` or `}` character. On success, the `id` of the returned /// `Token` is `subst` (if the token ends with `${`) or `tail` (if the /// token ends with `` ` ``). /// /// ```text /// NoSubstitutionTemplate :: /// ``` TemplateCharacters? ``` /// /// TemplateHead :: /// ``` TemplateCharacters? `${` /// /// TemplateMiddle :: /// `}` TemplateCharacters? `${` /// /// TemplateTail :: /// `}` TemplateCharacters? ``` /// /// TemplateCharacters :: /// TemplateCharacter TemplateCharacters? /// ``` fn template_part(
&mutself,
start: usize,
subst: TerminalId,
tail: TerminalId,
) -> Result<'alloc, ()> { letmut builder = AutoCow::new(&self); whilelet Some(ch) = self.chars.next() { // TemplateCharacter :: // `$` [lookahead != `{` ] // `\` EscapeSequence // `\` NotEscapeSequence // LineContinuation // LineTerminatorSequence // SourceCharacter but not one of ``` or `\` or `$` or LineTerminator // // NotEscapeSequence :: // `0` DecimalDigit // DecimalDigit but not `0` // `x` [lookahead <! HexDigit] // `x` HexDigit [lookahead <! HexDigit] // `u` [lookahead <! HexDigit] [lookahead != `{`] // `u` HexDigit [lookahead <! HexDigit] // `u` HexDigit HexDigit [lookahead <! HexDigit] // `u` HexDigit HexDigit HexDigit [lookahead <! HexDigit] // `u` `{` [lookahead <! HexDigit] // `u` `{` NotCodePoint [lookahead <! HexDigit] // `u` `{` CodePoint [lookahead <! HexDigit] [lookahead != `}`] // // NotCodePoint :: // HexDigits [> but only if MV of |HexDigits| > 0x10FFFF ] // // CodePoint :: // HexDigits [> but only if MV of |HexDigits| ≤ 0x10FFFF ] if ch == '$' && self.peek() == Some('{') { self.chars.next(); let value = self.string_to_token_value(builder.finish_without_push(&self)); returnself.set_result(subst, SourceLocation::new(start, self.offset()), value);
} if ch == '`' { let value = self.string_to_token_value(builder.finish_without_push(&self)); returnself.set_result(tail, SourceLocation::new(start, self.offset()), value);
} // TODO: Support escape sequences. if ch == '\\' { let text = builder.get_mut_string_without_current_ascii_char(&>self); self.escape_sequence(text)?;
} else {
builder.push_matching(ch);
}
}
Err(ParseError::UnterminatedString.into())
}
fn advance_impl<'parser>(&mut self, parser: &Parser<'parser>) -> Result<'alloc, ()> { letmut builder = AutoCow::new(&self); letmut start = self.offset(); whilelet Some(c) = self.chars.next() { match c { // 11.2 White Space // // WhiteSpace :: // <TAB> // <VT> // <FF> // <SP> // <NBSP> // <ZWNBSP> // <USP>
TAB |
VT |
FF |
SP |
NBSP |
ZWNBSP | '\u{1680}' | // Ogham space mark (in <USP>) '\u{2000}' ..= '\u{200a}' | // typesetting spaces (in <USP>) '\u{202f}' | // Narrow no-break space (in <USP>) '\u{205f}' | // Medium mathematical space (in <USP>) '\u{3000}'// Ideographic space (in <USP>)
=> { // TODO - The spec uses <USP> to stand for any character // with category "Space_Separator" (Zs). New Unicode // standards may add characters to this set. This should therefore be // implemented using the Unicode database somehow.
builder = AutoCow::new(&self);
start = self.offset(); continue;
}
let value = self.unicode_escape_sequence_after_backslash()?; if !is_identifier_start(value) { return Err(ParseError::IllegalCharacter(value).into());
}
builder.push_different(value);
// Push a char that matches lexer.chars.next() fn push_matching(&mutself, c: char) { iflet Some(text) = &mutself.value {
text.push(c);
}
}
// Push a different character than lexer.chars.next(). // force_allocation_without_current_ascii_char must be called before this. fn push_different(&mutself, c: char) {
debug_assert!(self.value.is_some()); self.value.as_mut().unwrap().push(c)
}
// Force allocation of a String, excluding the current ASCII character, // and return the reference to it fn get_mut_string_without_current_ascii_char<'b>(
&'b mut self,
lexer: &'_ Lexer<'alloc>,
) -> &'b mut String<'alloc> { self.force_allocation_without_current_ascii_char(lexer); self.value.as_mut().unwrap()
}
// Force allocation of a String, excluding the current ASCII character. fn force_allocation_without_current_ascii_char(&mutself, lexer: &'_ Lexer<'alloc>) { ifself.value.is_some() { return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.