Skip to content

Commit

Permalink
feat(parser): WIP: Enable JSX parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
KFlash committed Jul 5, 2019
1 parent e7d8dbf commit 9dd80d4
Show file tree
Hide file tree
Showing 14 changed files with 11,105 additions and 28 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -18,6 +18,7 @@
* Conforms to the standard ECMAScript® 2020 (ECMA-262 10th Edition) language specification
* Support TC39 proposals via option
* Support for additional ECMAScript features for Web Browsers
* JSX support via option
* Optionally track syntactic node locations
* Emits an ESTree-compatible abstract syntax tree.
* No backtracking
Expand Down Expand Up @@ -72,6 +73,8 @@ This is the available options:
source: false;
// Distinguish Identifier from IdentifierPattern
identifierPattern: false;
// Enable React JSX parsing
jsx: false
}
```

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "meriyah",
"version": "1.2.6",
"version": "1.3.0",
"description": "A 100% compliant, self-hosted javascript parser with high focus on both performance and stability",
"main": "dist/meriyah.umd.js",
"module": "dist/meriyah.esm.js",
Expand Down
2 changes: 1 addition & 1 deletion src/common.ts
Expand Up @@ -36,7 +36,7 @@ export const enum Context {
AllowNewTarget = 1 << 26,
DisallowIn = 1 << 27,
InClass = 1 << 28,
OptionsIdentifierPattern = 1 << 29,
OptionsIdentifierPattern = 1 << 29
}

export const enum PropertyKind {
Expand Down
10 changes: 8 additions & 2 deletions src/errors.ts
Expand Up @@ -157,7 +157,10 @@ export const enum Errors {
DuplicateLetConstBinding,
CantAssignToValidRHS,
ContinuousNumericSeparator,
TrailingNumericSeparator
TrailingNumericSeparator,
InvalidJSXAttributeValue,
ExpectedJSXClosingTag,
AdjacentJSXElements
}

/*@internal*/
Expand Down Expand Up @@ -326,7 +329,10 @@ export const errorMessages: {
[Errors.UndeclaredExportedBinding]: "Exported binding '%0' needs to refer to a top-level declared variable",
[Errors.UnexpectedPrivateField]: 'Unexpected private field',
[Errors.TrailingNumericSeparator]: 'Numeric separators are not allowed at the end of numeric literals',
[Errors.ContinuousNumericSeparator]: 'Only one underscore is allowed as numeric separator'
[Errors.ContinuousNumericSeparator]: 'Only one underscore is allowed as numeric separator',
[Errors.InvalidJSXAttributeValue]: 'JSX value should be either an expression or a quoted JSX text',
[Errors.ExpectedJSXClosingTag]: 'Expected corresponding JSX closing tag for %0',
[Errors.AdjacentJSXElements]: 'Adjacent JSX elements must be wrapped in an enclosing tag'
};

export class ParseError extends SyntaxError {
Expand Down
2 changes: 1 addition & 1 deletion src/estree.ts
Expand Up @@ -597,7 +597,7 @@ export interface JSXSpreadChild extends _Node {
export interface JSXText extends _Node {
type: 'JSXText';
value: string;
raw: string;
raw?: string;
}

export interface LabeledStatement extends _Node {
Expand Down
2 changes: 1 addition & 1 deletion src/lexer/comments.ts
Expand Up @@ -4,7 +4,7 @@ import { ParserState } from '../common';
import { report, Errors } from '../errors';

/**
* Skips BOM and hasbang (stage 3)
* Skips hasbang (stage 3)
*
* @param parser Parser object
*/
Expand Down
3 changes: 2 additions & 1 deletion src/lexer/common.ts
Expand Up @@ -7,7 +7,8 @@ export const enum LexerState {
None = 0,
NewLine = 1 << 0,
SameLine = 1 << 1,
LastIsCR = 1 << 2
LastIsCR = 1 << 2,
InJSXMode = 1 << 3
}

export const enum NumberKind {
Expand Down
94 changes: 94 additions & 0 deletions src/lexer/jsx.ts
@@ -0,0 +1,94 @@
import { isIdentifierStart, isIdentifierPart } from './charClassifier';
import { Chars } from '../chars';
import { Token } from '../token';
import { ParserState, Context } from '../common';
import { report, Errors } from '../errors';
import { nextCP, nextToken } from './';

export function scanJSXString(parser: ParserState): Token {
const quote = parser.nextCP;
let char = nextCP(parser);
const start = parser.index;
while (char !== quote) {
if (parser.index >= parser.end) report(parser, Errors.UnterminatedString);
char = nextCP(parser);
}

// check for unterminated string
if (char !== quote) report(parser, Errors.UnterminatedString);
parser.tokenValue = parser.source.slice(start, parser.index);
nextCP(parser); // skip the quote
return Token.StringLiteral;
}

export function scanJSXToken(parser: ParserState): Token {
parser.startIndex = parser.tokenIndex = parser.index;

if (parser.index >= parser.end) {
return (parser.token = Token.EOF);
}

let char = parser.source.charCodeAt(parser.index);

if (char === Chars.LessThan) {
if (parser.source.charCodeAt(parser.index + 1) === Chars.Slash) {
parser.index += 2;
parser.nextCP = parser.source.charCodeAt(parser.index);
return (parser.token = Token.JSXClose);
}
++parser.index;
parser.nextCP = parser.source.charCodeAt(parser.index);
return (parser.token = Token.LessThan);
}

if (char === Chars.LeftBrace) {
++parser.index;
parser.nextCP = parser.source.charCodeAt(parser.index);
return (parser.token = Token.LeftBrace);
}

while (parser.index < parser.end) {
parser.index++;
char = parser.source.charCodeAt(parser.index);
if (char === Chars.LeftBrace || char === Chars.LessThan) {
break;
}
}
parser.nextCP = parser.source.charCodeAt(parser.index);
parser.tokenValue = parser.source.slice(parser.tokenIndex, parser.index);
return (parser.token = Token.JSXText);
}
export function scanJSXIdentifier(parser: ParserState, _context: Context): Token {
if ((parser.token & Token.IsIdentifier) === Token.IsIdentifier) {
const firstCharPosition = parser.index;

while (parser.index < parser.end) {
let char = parser.nextCP;
if (
char === Chars.Hyphen ||
(firstCharPosition === parser.index ? isIdentifierStart(char) : isIdentifierPart(char))
) {
parser.index++;
} else break;
parser.nextCP = parser.source.charCodeAt(parser.index);
}

parser.tokenValue += parser.source.slice(firstCharPosition, parser.index); // - firstCharPosition);
}

return parser.token;
}

export function scanJsxAttributeValue(parser: ParserState, context: Context): any {
parser.startIndex = parser.index;

switch (parser.nextCP) {
case Chars.DoubleQuote:
case Chars.SingleQuote:
parser.token = scanJSXString(parser);
return (parser.token = Token.StringLiteral);
default:
// If this scans anything other than `{`, it's a parse error.
nextToken(parser, context);
}
}
13 changes: 13 additions & 0 deletions src/lexer/scan.ts
Expand Up @@ -343,6 +343,7 @@ export function scanSingleToken(parser: ParserState, context: Context, state: Le
return Token.Subtract;
}

// `/`, `/=`, `/>`, '/*..*/'
case Token.Divide: {
nextCP(parser);
if (parser.index < parser.end) {
Expand Down Expand Up @@ -390,6 +391,18 @@ export function scanSingleToken(parser: ParserState, context: Context, state: Le
state = skipSingleLineComment(parser, state);
continue;
}
} else if (next === Chars.Slash) {
if (!(context & Context.OptionsJSX)) break;
const index = parser.index + 1;

// Check that it's not a comment start.
if (index < parser.end) {
const next = parser.source.charCodeAt(index);
if (next === Chars.Asterisk || next === Chars.Slash) break;
}

nextCP(parser);
return Token.JSXClose;
}
}
return Token.LessThan;
Expand Down
38 changes: 19 additions & 19 deletions src/lexer/template.ts
Expand Up @@ -10,51 +10,51 @@ import { report, Errors } from '../errors';
*/
export function scanTemplate(parser: ParserState, context: Context): Token {
const { index: start } = parser;
let tail = true;
let tail = 1;
let ret: string | void = '';

let ch = nextCP(parser);
let char = nextCP(parser);

while (ch !== Chars.Backtick) {
if (ch === Chars.Dollar && parser.source.charCodeAt(parser.index + 1) === Chars.LeftBrace) {
nextCP(parser);
tail = false;
while (char !== Chars.Backtick) {
if (char === Chars.Dollar && parser.source.charCodeAt(parser.index + 1) === Chars.LeftBrace) {
nextCP(parser); // Skip: '}'
tail = 0;
break;
} else if ((ch & 8) === 8 && ch === Chars.Backslash) {
ch = nextCP(parser);
if (ch > 0x7e) {
ret += fromCodePoint(ch);
} else if ((char & 8) === 8 && char === Chars.Backslash) {
char = nextCP(parser);
if (char > 0x7e) {
ret += fromCodePoint(char);
} else {
const code = parseEscape(parser, context | Context.Strict, ch);
const code = parseEscape(parser, context | Context.Strict, char);
if (code >= 0) {
ret += fromCodePoint(code);
} else if (code !== Escape.Empty && context & Context.TaggedTemplate) {
ret = undefined;
ch = scanBadTemplate(parser, ch);
if (ch < 0) {
tail = false;
char = scanBadTemplate(parser, char);
if (char < 0) {
tail = 0;
}
break;
} else {
handleStringError(parser, code as Escape, /* isTemplate */ 1);
}
}
} else {
if (ch === Chars.CarriageReturn) {
if (char === Chars.CarriageReturn) {
if (parser.index < parser.end && parser.source.charCodeAt(parser.index) === Chars.LineFeed) {
ret += fromCodePoint(ch);
ret += fromCodePoint(char);
parser.nextCP = parser.source.charCodeAt(++parser.index);
}
}

if (ch === Chars.LineFeed || ch === Chars.LineSeparator || ch === Chars.ParagraphSeparator) {
if (((char & 83) < 3 && char === Chars.LineFeed) || (char ^ Chars.LineSeparator) <= 1) {
parser.column = -1;
parser.line++;
}
ret += fromCodePoint(ch);
ret += fromCodePoint(char);
}
if (parser.index >= parser.end) report(parser, Errors.UnterminatedTemplate);
ch = nextCP(parser);
char = nextCP(parser);
}

nextCP(parser); // Consume the quote or opening brace
Expand Down

0 comments on commit 9dd80d4

Please sign in to comment.