one parser call: js.parse(entry, offset, { end, stopAt }) replaces the five parse-at methods, the answer is kept whole for its tables, and a scope's bindings are grouped here

goodbye-acorn
Nic 1 day ago
parent 5d7a91864c
commit 57c7de58a5

@ -13,36 +13,23 @@ import { keep_tables } from '../../utils/ast.js';
* @returns {Program} * @returns {Program}
*/ */
export function parse(source, comments, typescript) { export function parse(source, comments, typescript) {
let ast; let answer;
try { try {
ast = teasel.parse(source, { answer = new teasel.Source(source, {
sourceType: 'module', sourceType: 'module',
typescript, typescript,
comments: true, comments: true,
locations: true, locations: true,
scopes: true scopes: true
}); }).parse();
} catch (err) { } catch (err) {
return handle_parse_error(err); return handle_parse_error(err);
} }
add_comments(source, comments, /** @type {teasel.Comment[]} */ (ast.comments)); add_comments(source, comments, /** @type {teasel.Comment[]} */ (answer.comments));
delete ast.comments; keep_tables(answer.node, answer);
keep_tables(ast, tables(ast));
return ast;
}
/** return answer.node;
* Takes the tables off an answer, so they reach the scope analysis without showing in the tree.
* @param {{ scopes?: any; bindings?: any; references?: any }} answer
*/
function tables(answer) {
const { scopes, bindings, references } = answer;
delete answer.scopes;
delete answer.bindings;
delete answer.references;
return { scopes, bindings, references };
} }
/** /**
@ -53,9 +40,9 @@ function tables(answer) {
* @returns {Program} * @returns {Program}
*/ */
export function parse_script(parser, start, end) { export function parse_script(parser, start, end) {
let ast; let answer;
try { try {
ast = parser.js.parse(start, end); answer = parser.js.parse('program', start, { end });
} catch (err) { } catch (err) {
return handle_parse_error(err); return handle_parse_error(err);
} }
@ -63,15 +50,12 @@ export function parse_script(parser, start, end) {
add_comments( add_comments(
parser.template, parser.template,
parser.root.comments, parser.root.comments,
/** @type {teasel.Comment[]} */ (ast.comments) /** @type {teasel.Comment[]} */ (answer.comments)
); );
delete ast.comments; keep_tables(answer.node, answer);
delete ast.errors; unsupported(answer.typescript);
keep_tables(ast, tables(ast));
unsupported(ast.typescript);
delete ast.typescript;
return ast; return answer.node;
} }
/** /**
@ -109,8 +93,8 @@ function read(parser, run) {
*/ */
export function read_expression(parser, stop_at) { export function read_expression(parser, stop_at) {
const start = parser.index; const start = parser.index;
const answer = read(parser, (js) => js.parseExpressionAt(start, stop_at)); const answer = read(parser, (js) => js.parse('expression', start, { stopAt: stop_at }));
keep_tables(answer.node, tables(answer)); keep_tables(answer.node, answer);
const node = answer.node; const node = answer.node;
// the language tools copy an expression's text by its node's range, so the placeholder standing for one that could not be read spans what was read // the language tools copy an expression's text by its node's range, so the placeholder standing for one that could not be read spans what was read
if (node.type === 'Identifier' && node.name === '' && node.start === node.end) { if (node.type === 'Identifier' && node.name === '' && node.start === node.end) {
@ -146,8 +130,8 @@ export function read_pattern(parser) {
} }
} }
const answer = read(parser, (js) => js.parsePatternAt(start)); const answer = read(parser, (js) => js.parse('pattern', start));
keep_tables(answer.node, tables(answer)); keep_tables(answer.node, answer);
return answer.node; return answer.node;
} }
@ -157,9 +141,9 @@ export function read_pattern(parser) {
*/ */
export function read_params(parser) { export function read_params(parser) {
const start = parser.index; const start = parser.index;
const answer = read(parser, (js) => js.parseParamsAt(start)); const answer = read(parser, (js) => js.parse('params', start));
keep_tables(answer.params, tables(answer)); keep_tables(answer.node, answer);
return answer.params; return answer.node;
} }
/** /**
@ -167,7 +151,7 @@ export function read_params(parser) {
*/ */
export function read_type_parameters(parser) { export function read_type_parameters(parser) {
const start = parser.index; const start = parser.index;
read(parser, (js) => js.parseTypeParametersAt(start)); read(parser, (js) => js.parse('typeParameters', start));
} }
/** /**
@ -177,8 +161,8 @@ export function read_type_parameters(parser) {
export function read_statement(parser) { export function read_statement(parser) {
const start = parser.index; const start = parser.index;
const answer = read(parser, (js) => js.parseStatementAt(start)); const answer = read(parser, (js) => js.parse('statement', start));
keep_tables(answer.node, tables(answer)); keep_tables(answer.node, answer);
return answer.node; return answer.node;
} }

@ -174,7 +174,7 @@ function open(parser) {
if (parser.eat('(')) { if (parser.eat('(')) {
parser.allow_whitespace(); parser.allow_whitespace();
key = read_expression(parser, undefined, '('); key = read_expression(parser);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat(')', true); parser.eat(')', true);
parser.allow_whitespace(); parser.allow_whitespace();

@ -1004,14 +1004,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
const from_parser = new Map(); const from_parser = new Map();
/** /**
* Declares in `scope` what the parser found declared in `parsed`: its parameters, or the rest. * Declares in `scope` what the parser found declared in one of its scopes: the parameters, or the rest.
* @param {Scope} scope * @param {Scope} scope
* @param {import('@teasel/parser').Scope} parsed * @param {import('@teasel/parser').Binding[]} bindings
* @param {boolean} params * @param {boolean} params
* @param {boolean} [template] the declarations are a const tag's * @param {boolean} [template] the declarations are a const tag's
*/ */
function declare_parsed(scope, parsed, params, template = false) { function declare_parsed(scope, bindings, params, template = false) {
for (const binding of parsed.bindings) { for (const binding of bindings) {
if ((binding.kind === 'param') !== params) continue; if ((binding.kind === 'param') !== params) continue;
// no node: `arguments`, or a declaration erased with the TypeScript it belonged to // no node: `arguments`, or a declaration erased with the TypeScript it belonged to
if (binding.node === null || binding.kind === 'class-name' || binding.kind === 'pattern') if (binding.node === null || binding.kind === 'class-name' || binding.kind === 'pattern')
@ -1086,6 +1086,15 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
} }
return; return;
} }
/** @type {Map<import('@teasel/parser').Scope, import('@teasel/parser').Binding[]>} the answer's bindings by the scope that holds them */
const by_scope = new Map();
for (const binding of answer.bindings) {
const held = by_scope.get(binding.scope);
if (held === undefined) by_scope.set(binding.scope, [binding]);
else held.push(binding);
}
/** @param {import('@teasel/parser').Scope} parsed */
const of = (parsed) => by_scope.get(parsed) ?? [];
/** @type {Map<import('@teasel/parser').Scope, Scope>} the parser's scopes and ours; a function's holds its parameters */ /** @type {Map<import('@teasel/parser').Scope, Scope>} the parser's scopes and ours; a function's holds its parameters */
const ours = new Map([[outermost, scope]]); const ours = new Map([[outermost, scope]]);
/** @type {Map<import('@teasel/parser').Scope, Scope>} a function's body, which holds the rest */ /** @type {Map<import('@teasel/parser').Scope, Scope>} a function's body, which holds the rest */
@ -1108,7 +1117,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
return /** @type {Scope} */ (ours.get(scope)); return /** @type {Scope} */ (ours.get(scope));
} }
declare_parsed(scope, outermost, false, template); declare_parsed(scope, of(outermost), false, template);
for (let i = 1; i < answer.scopes.length; i++) { for (let i = 1; i < answer.scopes.length; i++) {
const parsed = answer.scopes[i]; const parsed = answer.scopes[i];
@ -1124,18 +1133,18 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
scopes.set(node, params); scopes.set(node, params);
ours.set(parsed, params); ours.set(parsed, params);
if (parsed.parent?.kind === 'function-name') ours.set(parsed.parent, params); if (parsed.parent?.kind === 'function-name') ours.set(parsed.parent, params);
declare_parsed(params, parsed, true); declare_parsed(params, of(parsed), true);
if ( if (
node.body.type !== 'BlockStatement' || node.body.type !== 'BlockStatement' ||
(node.type === 'FunctionExpression' && node.id) (node.type === 'FunctionExpression' && node.id)
) { ) {
declare_parsed(params, parsed, false); declare_parsed(params, of(parsed), false);
} }
if (node.body.type === 'BlockStatement') { if (node.body.type === 'BlockStatement') {
const body = params.child(); const body = params.child();
scopes.set(node.body, body); scopes.set(node.body, body);
bodies.set(parsed, body); bodies.set(parsed, body);
declare_parsed(body, parsed, false); declare_parsed(body, of(parsed), false);
} }
break; break;
} }
@ -1147,7 +1156,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
const inside = parent.child(true); const inside = parent.child(true);
scopes.set(node, inside); scopes.set(node, inside);
ours.set(parsed, inside); ours.set(parsed, inside);
declare_parsed(inside, parsed, false); declare_parsed(inside, of(parsed), false);
break; break;
} }
default: default:

@ -4,21 +4,21 @@ import { walk } from 'zimmerframe';
import { bindingOf } from '@teasel/parser'; import { bindingOf } from '@teasel/parser';
/** /**
* @typedef {{ scopes: import('@teasel/parser').Scope[]; bindings: import('@teasel/parser').Binding[]; references: import('@teasel/parser').Reference[] }} Tables * @typedef {Required<Pick<import('@teasel/parser').Parsed<unknown>, 'scopes' | 'bindings' | 'references'>>} Tables
* the parser's scope, binding and reference tables for one answer * the parser's scope, binding and reference tables for one answer, parsed with `scopes`
*/ */
// a symbol on the root: a property load where the walk asks every node, not a map lookup // a symbol on the root: a property load where the walk asks every node, not a map lookup
const PARSED = Symbol('parsed'); const PARSED = Symbol('parsed');
/** /**
* Keeps the parser's tables on the root it parsed, a script's program or a template's * Keeps the parser's answer on the root it parsed, a script's program or a template's
* expression, pattern, parameter list or declaration. * expression, pattern, parameter list or declaration, for its tables.
* @param {object} root * @param {object} root
* @param {Tables} tables * @param {import('@teasel/parser').Parsed<unknown>} answer
*/ */
export function keep_tables(root, tables) { export function keep_tables(root, answer) {
/** @type {any} */ (root)[PARSED] = tables; /** @type {any} */ (root)[PARSED] = answer;
} }
/** /**

Loading…
Cancel
Save