diff options
Diffstat (limited to 'js/src/frontend/Token.h')
-rw-r--r-- | js/src/frontend/Token.h | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/js/src/frontend/Token.h b/js/src/frontend/Token.h new file mode 100644 index 0000000000..da01169935 --- /dev/null +++ b/js/src/frontend/Token.h @@ -0,0 +1,218 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Token-affiliated data structures except for TokenKind (defined in its own + * header). + */ + +#ifndef frontend_Token_h +#define frontend_Token_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include <stdint.h> // uint32_t + +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex +#include "frontend/TokenKind.h" // js::frontend::TokenKind +#include "js/RegExpFlags.h" // JS::RegExpFlags + +namespace js { + +namespace frontend { + +struct TokenPos { + uint32_t begin = 0; // Offset of the token's first code unit. + uint32_t end = 0; // Offset of 1 past the token's last code unit. + + TokenPos() = default; + TokenPos(uint32_t begin, uint32_t end) : begin(begin), end(end) {} + + // Return a TokenPos that covers left, right, and anything in between. + static TokenPos box(const TokenPos& left, const TokenPos& right) { + MOZ_ASSERT(left.begin <= left.end); + MOZ_ASSERT(left.end <= right.begin); + MOZ_ASSERT(right.begin <= right.end); + return TokenPos(left.begin, right.end); + } + + bool operator==(const TokenPos& bpos) const { + return begin == bpos.begin && end == bpos.end; + } + + bool operator!=(const TokenPos& bpos) const { + return begin != bpos.begin || end != bpos.end; + } + + bool operator<(const TokenPos& bpos) const { return begin < bpos.begin; } + + bool operator<=(const TokenPos& bpos) const { return begin <= bpos.begin; } + + bool operator>(const TokenPos& bpos) const { return !(*this <= bpos); } + + bool operator>=(const TokenPos& bpos) const { return !(*this < bpos); } + + bool encloses(const TokenPos& pos) const { + return begin <= pos.begin && pos.end <= end; + } +}; + +enum DecimalPoint { NoDecimal = false, HasDecimal = true }; + +// The only escapes found in IdentifierName are of the Unicode flavor. +enum class IdentifierEscapes { None, SawUnicodeEscape }; + +enum class NameVisibility { Public, Private }; + +class TokenStreamShared; + +struct Token { + private: + // The lexical grammar of JavaScript has a quirk around the '/' character. + // As the spec puts it: + // + // > There are several situations where the identification of lexical input + // > elements is sensitive to the syntactic grammar context that is consuming + // > the input elements. This requires multiple goal symbols for the lexical + // > grammar. [...] The InputElementRegExp goal symbol is used in all + // > syntactic grammar contexts where a RegularExpressionLiteral is permitted + // > [...] In all other contexts, InputElementDiv is used as the lexical + // > goal symbol. + // + // https://tc39.github.io/ecma262/#sec-lexical-and-regexp-grammars + // + // What "sensitive to the syntactic grammar context" means is, the parser has + // to tell the TokenStream whether to interpret '/' as division or + // RegExp. Because only one or the other (or neither) will be legal at that + // point in the program, and only the parser knows which one. + // + // But there's a problem: the parser often gets a token, puts it back, then + // consumes it later; or (equivalently) peeks at a token, leaves it, peeks + // again later, then finally consumes it. Of course we don't actually re-scan + // the token every time; we cache it in the TokenStream. This leads to the + // following rule: + // + // The parser must not pass SlashIsRegExp when getting/peeking at a token + // previously scanned with SlashIsDiv; or vice versa. + // + // That way, code that asks for a SlashIsRegExp mode will never get a cached + // Div token. But this rule is easy to screw up, because tokens are so often + // peeked at on Parser.cpp line A and consumed on line B, where |A-B| is + // thousands of lines. We therefore enforce it with the frontend's most + // annoying assertion (in verifyConsistentModifier), and provide + // Modifier::SlashIsInvalid to help avoid tripping it. + // + // This enum belongs in TokenStream, but C++, so we define it here and + // typedef it there. + enum Modifier { + // Parse `/` and `/=` as the division operators. (That is, use + // InputElementDiv as the goal symbol.) + SlashIsDiv, + + // Parse `/` as the beginning of a RegExp literal. (That is, use + // InputElementRegExp.) + SlashIsRegExp, + + // Neither a Div token nor a RegExp token is syntactically valid here. When + // the parser calls `getToken(SlashIsInvalid)`, it must be prepared to see + // either one (and throw a SyntaxError either way). + // + // It's OK to use SlashIsInvalid to get a token that was originally scanned + // with SlashIsDiv or SlashIsRegExp. The reverse--peeking with + // SlashIsInvalid, then getting with another mode--is not OK. If either Div + // or RegExp is syntactically valid here, use the appropriate modifier. + SlashIsInvalid, + }; + friend class TokenStreamShared; + + public: + /** The type of this token. */ + TokenKind type; + + /** The token's position in the overall script. */ + TokenPos pos; + + union { + private: + friend struct Token; + + TrivialTaggedParserAtomIndex atom; + + struct { + /** Numeric literal's value. */ + double value; + + /** Does the numeric literal contain a '.'? */ + DecimalPoint decimalPoint; + } number; + + /** Regular expression flags; use charBuffer to access source chars. */ + JS::RegExpFlags reflags; + } u; + +#ifdef DEBUG + /** The modifier used to get this token. */ + Modifier modifier; +#endif + + // Mutators + + void setName(TaggedParserAtomIndex name) { + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); + u.atom = TrivialTaggedParserAtomIndex::from(name); + } + + void setAtom(TaggedParserAtomIndex atom) { + MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead || + type == TokenKind::NoSubsTemplate); + u.atom = TrivialTaggedParserAtomIndex::from(atom); + } + + void setRegExpFlags(JS::RegExpFlags flags) { + MOZ_ASSERT(type == TokenKind::RegExp); + u.reflags = flags; + } + + void setNumber(double n, DecimalPoint decimalPoint) { + MOZ_ASSERT(type == TokenKind::Number); + u.number.value = n; + u.number.decimalPoint = decimalPoint; + } + + // Type-safe accessors + + TaggedParserAtomIndex name() const { + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); + return u.atom; + } + + TaggedParserAtomIndex atom() const { + MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead || + type == TokenKind::NoSubsTemplate); + return u.atom; + } + + JS::RegExpFlags regExpFlags() const { + MOZ_ASSERT(type == TokenKind::RegExp); + return u.reflags; + } + + double number() const { + MOZ_ASSERT(type == TokenKind::Number); + return u.number.value; + } + + DecimalPoint decimalPoint() const { + MOZ_ASSERT(type == TokenKind::Number); + return u.number.decimalPoint; + } +}; + +} // namespace frontend + +} // namespace js + +#endif // frontend_Token_h |