readers move the cursor themselves; read/context and read/expression fold into js.js

goodbye-acorn
Nic 22 hours ago
parent e5e5f4d40f
commit 803476f793

@ -3,6 +3,7 @@
/** @import { Parser } from './index.js' */ /** @import { Parser } from './index.js' */
import * as teasel from '@teasel/parser'; import * as teasel from '@teasel/parser';
import * as e from '../../errors.js'; import * as e from '../../errors.js';
import { find_matching_bracket } from './utils/bracket.js';
/** /**
* A standalone module, as `analyze_module` reads one. * A standalone module, as `analyze_module` reads one.
@ -63,88 +64,113 @@ export function parse_script(parser, start, end) {
} }
/** /**
* Reads JavaScript at the cursor with the template's parser and moves the cursor past it.
* @template {{ end: number, comments?: teasel.Comment[], typescript?: teasel.Kept[] }} T
* @param {Parser} parser * @param {Parser} parser
* @param {number} index * @param {(js: teasel.Source) => T} run
* @param {'as'} [until] the host's `as` follows the expression, as an each block's item does * @returns {T}
* @returns {{ node: Expression, end: number }}
*/ */
export function parse_expression_at(parser, index, until) { function read(parser, run) {
let answer; let answer;
try { try {
answer = parser.js.parseExpressionAt(index, until); answer = run(parser.js);
} catch (err) { } catch (err) {
// the parser's syntax errors; a compile error thrown while reading is the host's own
if (!(err instanceof SyntaxError)) throw err;
return handle_parse_error(err); return handle_parse_error(err);
} }
return accept(parser, answer, answer.node); add_comments(
parser.template,
parser.root.comments,
/** @type {teasel.Comment[]} */ (answer.comments)
);
unsupported(answer.typescript);
parser.index = answer.end;
return answer;
} }
/** /**
* @param {Parser} parser * @param {Parser} parser
* @param {number} index * @param {'as'} [until] the host's `as` follows the expression, as an each block's item does
* @returns {{ node: Pattern, end: number }} * @param {string} [opening_token] the bracket the expression sits in, for loose mode
* @returns {Expression}
*/ */
export function parse_pattern_at(parser, index) { export function read_expression(parser, until, opening_token = '{') {
let answer; const start = parser.index;
try { try {
answer = parser.js.parsePatternAt(index); return read(parser, (js) => js.parseExpressionAt(start, until)).node;
} catch (err) { } catch (err) {
return handle_parse_error(err); if (parser.loose) {
} // Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, start, opening_token);
if (end !== undefined) {
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return { type: 'Identifier', start, end, name: '' };
}
}
return accept(parser, answer, answer.node); throw err;
}
} }
/** /**
* @param {Parser} parser * @param {Parser} parser
* @param {number} index the opening paren * @returns {Pattern}
* @returns {{ node: Pattern[], end: number }}
*/ */
export function parse_params_at(parser, index) { export function read_pattern(parser) {
let answer; const start = parser.index;
try {
answer = parser.js.parseParamsAt(index); const id = parser.read_identifier();
} catch (err) {
return handle_parse_error(err); if (id.name !== '') {
const after = parser.index;
parser.allow_whitespace();
// a type annotation makes it a job for the parser
if (!parser.match(':')) {
parser.index = after;
return id;
}
} else {
const char = parser.template[start];
if (char !== '{' && char !== '[') {
e.expected_pattern(start);
}
} }
return accept(parser, answer, answer.params); return read(parser, (js) => js.parsePatternAt(start)).node;
} }
/** /**
* @param {Parser} parser * @param {Parser} parser at the opening paren
* @param {number} index * @returns {Pattern[]}
* @returns {{ node: Statement, end: number }}
*/ */
export function parse_statement_at(parser, index) { export function read_params(parser) {
let answer; const start = parser.index;
try { return read(parser, (js) => js.parseParamsAt(start)).params;
answer = parser.js.parseStatementAt(index);
} catch (err) {
if (/** @type {any} */ (err).code === 'unexpected_eof') e.unexpected_eof(parser.template.length);
return handle_parse_error(err);
}
return accept(parser, answer, answer.node);
} }
/** /**
* Keeps an answer's comments, rejects what erasure could not express, and hands back the node
* with the offset the parse stopped at.
* @template T
* @param {Parser} parser * @param {Parser} parser
* @param {{ end: number, comments?: teasel.Comment[], typescript?: teasel.Kept[] }} answer * @returns {Statement}
* @param {T} node
* @returns {{ node: T, end: number }}
*/ */
function accept(parser, answer, node) { export function read_statement(parser) {
add_comments( const start = parser.index;
parser.template,
parser.root.comments, return read(parser, (js) => {
/** @type {teasel.Comment[]} */ (answer.comments) try {
); return js.parseStatementAt(start);
unsupported(answer.typescript); } catch (err) {
return { node, end: answer.end }; if (/** @type {any} */ (err).code === 'unexpected_eof')
e.unexpected_eof(parser.template.length);
throw err;
}
}).node;
} }
/** What erasure leaves in place needs a compiler, not this one */ /** What erasure leaves in place needs a compiler, not this one */

@ -1,36 +0,0 @@
/** @import { Pattern } from 'estree' */
/** @import { Parser } from '../index.js' */
import { parse_pattern_at } from '../js.js';
import * as e from '../../../errors.js';
/**
* @param {Parser} parser
* @returns {Pattern}
*/
export default function read_pattern(parser) {
const start = parser.index;
const id = parser.read_identifier();
if (id.name !== '') {
const after = parser.index;
parser.allow_whitespace();
// a type annotation makes it a job for the parser
if (!parser.match(':')) {
parser.index = after;
return id;
}
} else {
const char = parser.template[start];
if (char !== '{' && char !== '[') {
e.expected_pattern(start);
}
}
const { node, end } = parse_pattern_at(parser, start);
parser.index = end;
return node;
}

@ -1,52 +0,0 @@
/** @import { Expression, Identifier } from 'estree' */
/** @import { Parser } from '../index.js' */
import { parse_expression_at } from '../js.js';
import * as e from '../../../errors.js';
import { find_matching_bracket } from '../utils/bracket.js';
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @returns {Expression | undefined}
*/
export function get_loose_identifier(parser, opening_token) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return {
type: 'Identifier',
start,
end,
name: ''
};
}
}
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @param {boolean} [disallow_loose]
* @param {'as'} [until] the host's `as` follows the expression, as an each block's item does
* @returns {Expression}
*/
export default function read_expression(parser, opening_token, disallow_loose, until) {
try {
const { node, end } = parse_expression_at(parser, parser.index, until);
parser.index = end;
return node;
} catch (err) {
// If we are in an each loop we need the error to be thrown in cases like
// `as { y = z }` so we still throw and handle the error there
if (parser.loose && !disallow_loose) {
const expression = get_loose_identifier(parser, opening_token);
if (expression) {
return expression;
}
}
throw err;
}
}

@ -3,7 +3,7 @@
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */ /** @import { Parser } from '../index.js' */
import { is_void, REGEX_VALID_TAG_NAME } from '../../../../utils.js'; import { is_void, REGEX_VALID_TAG_NAME } from '../../../../utils.js';
import read_expression from '../read/expression.js'; import { read_expression } from '../js.js';
import { read_script } from '../read/script.js'; import { read_script } from '../read/script.js';
import read_style from '../read/style.js'; import read_style from '../read/style.js';
import { decode_character_references } from '../utils/html.js'; import { decode_character_references } from '../utils/html.js';
@ -11,7 +11,11 @@ import * as e from '../../../errors.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { create_attribute, ExpressionMetadata, is_element_node } from '../../nodes.js'; import { create_attribute, ExpressionMetadata, is_element_node } from '../../nodes.js';
import { get_attribute_expression, is_expression_attribute, value_names } from '../../../utils/ast.js'; import {
get_attribute_expression,
is_expression_attribute,
value_names
} from '../../../utils/ast.js';
import { closing_tag_omitted } from '../../../../html-tree-validation.js'; import { closing_tag_omitted } from '../../../../html-tree-validation.js';
import { list } from '../../../utils/string.js'; import { list } from '../../../utils/string.js';
import { locator } from '../../../state.js'; import { locator } from '../../../state.js';

@ -3,9 +3,7 @@
/** @import { Parser } from '../index.js' */ /** @import { Parser } from '../index.js' */
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { ExpressionMetadata } from '../../nodes.js'; import { ExpressionMetadata } from '../../nodes.js';
import { parse_params_at, parse_statement_at } from '../js.js'; import { read_expression, read_params, read_pattern, read_statement } from '../js.js';
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { find_matching_bracket, match_bracket } from '../utils/bracket.js'; import { find_matching_bracket, match_bracket } from '../utils/bracket.js';
@ -89,14 +87,12 @@ function read_declaration(parser) {
/** @type {import('estree').Statement | import('estree').VariableDeclaration} */ /** @type {import('estree').Statement | import('estree').VariableDeclaration} */
let declaration; let declaration;
/** @type {number} */
let end;
try { try {
({ node: declaration, end } = parse_statement_at(parser, start)); declaration = read_statement(parser);
} catch (error) { } catch (error) {
if (!parser.loose) throw error; if (!parser.loose) throw error;
end = /** @type {number} */ (find_matching_bracket(parser.template, start, '{')); const end = find_matching_bracket(parser.template, start, '{');
if (end === undefined) throw error; if (end === undefined) throw error;
parser.index = end; parser.index = end;
@ -126,6 +122,7 @@ function read_declaration(parser) {
if (declaration.type !== 'VariableDeclaration') { if (declaration.type !== 'VariableDeclaration') {
if (declaration.type === 'ExpressionStatement') { if (declaration.type === 'ExpressionStatement') {
parser.index = start;
parser.root.comments.length = initial_comment_count; // Else they show up duplicated parser.root.comments.length = initial_comment_count; // Else they show up duplicated
return null; return null;
} else { } else {
@ -142,7 +139,6 @@ function read_declaration(parser) {
e.declaration_tag_invalid_type(declaration); e.declaration_tag_invalid_type(declaration);
} }
parser.index = end;
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
@ -184,7 +180,7 @@ function open(parser) {
parser.require_whitespace(); parser.require_whitespace();
// the list ends at the `as` that names the item, so a TypeScript assertion in it needs parens // the list ends at the `as` that names the item, so a TypeScript assertion in it needs parens
let expression = read_expression(parser, undefined, false, 'as'); let expression = read_expression(parser, 'as');
parser.allow_whitespace(); parser.allow_whitespace();
@ -224,7 +220,7 @@ function open(parser) {
if (parser.eat('(')) { if (parser.eat('(')) {
parser.allow_whitespace(); parser.allow_whitespace();
key = read_expression(parser, '('); key = read_expression(parser, undefined, '(');
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat(')', true); parser.eat(')', true);
parser.allow_whitespace(); parser.allow_whitespace();
@ -410,16 +406,14 @@ function open(parser) {
/** @type {import('estree').Pattern[]} */ /** @type {import('estree').Pattern[]} */
let parameters = []; let parameters = [];
if (parser.eat('(', true, false)) { if (parser.match('(')) {
const open = parser.index - 1; if (find_matching_bracket(parser.template, parser.index + 1, '(') === undefined) {
if (find_matching_bracket(parser.template, parser.index, '(') === undefined) {
e.expected_token(parser.template.length, ')'); e.expected_token(parser.template.length, ')');
} }
const { node, end } = parse_params_at(parser, open); parameters = read_params(parser);
parameters = node; } else if (!parser.loose) {
parser.index = end; e.expected_token(parser.index, '(');
} }
parser.allow_whitespace(); parser.allow_whitespace();

Loading…
Cancel
Save