subscript
Version:
Modular expression parser & evaluator
73 lines (65 loc) • 2.84 kB
JavaScript
/**
* Object accessor properties (getters/setters) - parse half
*
* { get x() { body } } → ['{}', ['get', 'x', body]]
* { set x(v) { body } } → ['{}', ['set', 'x', 'v', body]]
*/
import { token, expr, skip, next, parse, cur, idx } from '../parse.js';
const ASSIGN = 20, TOKEN = 200;
const LF = 10, CR = 13;
const DQUOTE = 34, HASH = 35, SQUOTE = 39, OPAREN = 40, CPAREN = 41, OBRACKET = 91, OBRACE = 123, CBRACE = 125;
const hasLineTerminator = (from, to) => {
while (from < to) {
const c = cur.charCodeAt(from++);
if (c === LF || c === CR) return true;
}
return false;
};
const computedKeyStart = c => c === DQUOTE || c === SQUOTE || c === OBRACKET || c === HASH;
const propertyKey = () => next(parse.id) || (computedKeyStart(cur.charCodeAt(idx)) ? expr(TOKEN - .5) : null);
const isMethodKey = a =>
typeof a === 'string' ||
(Array.isArray(a) && (a[0] === undefined || a[0] === '[]'));
// Shared parser for get/set — returns false if not valid accessor pattern (falls through to identifier)
// Returns false (not undefined) to signal "fall through without setting reserved"
const accessor = (kind) => a => {
if (a) return; // not prefix
const from = idx;
parse.space();
if (parse.semi || hasLineTerminator(from, idx)) return false;
const name = propertyKey();
if (!name) return false; // no property name = not accessor (e.g. `{ get: 1 }`)
parse.space();
if (cur.charCodeAt(idx) !== OPAREN) return false; // not followed by ( = not accessor
skip();
const params = expr(0, CPAREN);
parse.space();
if (cur.charCodeAt(idx) !== OBRACE) return false;
skip();
return [kind, name, params, expr(0, CBRACE)];
};
token('get', ASSIGN - 1, accessor('get'));
token('set', ASSIGN - 1, accessor('set'));
// Method shorthand: { foo() {} } / { async foo() {} } / class { static foo() {} }
// → [':', key, ['=>', ['()', params], body]]
// Accepts identifier, string-literal node [, "..."], ['async', key] from
// async.js, or ['static', key] from unary('static').
token('(', ASSIGN - 1, a => {
if (!a) return;
// ['static', key] from unary('static'): unwrap, re-wrap the resulting method node.
// ['async', key] from async.js: unwrap, wrap the method value in async.
let wrap, isAsync;
if (Array.isArray(a) && a[0] === 'static') wrap = 'static', a = a[1];
if (Array.isArray(a) && a[0] === 'async') isAsync = true, a = a[1];
// Accept identifier or string-literal node as key
if (!isMethodKey(a)) return;
const params = expr(0, CPAREN) || null;
parse.space();
// Not followed by { - not method shorthand, fall through
if (cur.charCodeAt(idx) !== OBRACE) return;
skip();
let value = ['=>', ['()', params], expr(0, CBRACE) || null];
if (isAsync) value = ['async', value];
const node = [':', a, value];
return wrap ? [wrap, node] : node;
});