@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
139 lines (138 loc) • 5.3 kB
JavaScript
import { current, advance, expect } from "../parser/parser-context.js";
import { parseExpression } from "../parser/parse-expression.js";
export const accessPlugin = {
name: "access",
parsePostfix: (ctx, node) => {
const tok = current(ctx);
// Member access: .prop
if (tok.type === "Punctuation" && tok.value === ".") {
advance(ctx);
const prop = expect(ctx, "Identifier");
return {
type: "MemberExpression",
object: node,
property: { type: "Identifier", name: prop.value, start: prop.start, end: prop.end },
computed: false,
start: node.start,
end: prop.end,
};
}
// Computed member access: [expr]
if (tok.type === "Punctuation" && tok.value === "[") {
advance(ctx);
const property = parseExpression(ctx, 0);
const end = expect(ctx, "Punctuation", "]").end;
return {
type: "MemberExpression",
object: node,
property,
computed: true,
start: node.start,
end,
};
}
// Optional chaining: ?.prop or ?.[expr]
if (tok.type === "OptionalChain") {
advance(ctx);
if (current(ctx).type === "Punctuation" && current(ctx).value === "[") {
advance(ctx);
const property = parseExpression(ctx, 0);
const end = expect(ctx, "Punctuation", "]").end;
return {
type: "OptionalMemberExpression",
object: node,
property,
computed: true,
start: node.start,
end,
};
}
const prop = expect(ctx, "Identifier");
return {
type: "OptionalMemberExpression",
object: node,
property: { type: "Identifier", name: prop.value, start: prop.start, end: prop.end },
computed: false,
start: node.start,
end: prop.end,
};
}
// Function call: (args)
if (tok.type === "Punctuation" && tok.value === "(") {
advance(ctx);
const args = [];
while (!(current(ctx).type === "Punctuation" && current(ctx).value === ")")) {
if (args.length > 0)
expect(ctx, "Punctuation", ",");
if (current(ctx).type === "Punctuation" && current(ctx).value === ")")
break;
args.push(parseExpression(ctx, 0));
}
const end = expect(ctx, "Punctuation", ")").end;
return {
type: "CallExpression",
callee: node,
args,
start: node.start,
end,
};
}
return null;
},
optimize: (node, opt) => {
if (node.type === "MemberExpression" || node.type === "OptionalMemberExpression") {
const n = node;
return {
...node,
object: opt(n.object),
property: n.computed ? opt(n.property) : n.property,
};
}
if (node.type === "CallExpression") {
const n = node;
return { ...node, callee: opt(n.callee), args: n.args.map((a) => opt(a)) };
}
return null;
},
evaluate: (node, context, evalFn) => {
if (node.type === "MemberExpression") {
const n = node;
const object = evalFn(n.object, context);
if (n.computed) {
const property = evalFn(n.property, context);
return { value: object[property] };
}
return { value: object[n.property.name] };
}
if (node.type === "OptionalMemberExpression") {
const n = node;
const object = evalFn(n.object, context);
if (object == null)
return { value: undefined };
if (n.computed) {
const property = evalFn(n.property, context);
return { value: object[property] };
}
return { value: object[n.property.name] };
}
if (node.type === "CallExpression") {
const n = node;
const args = n.args.map((a) => evalFn(a, context));
// Method call — preserve this binding
if (n.callee.type === "MemberExpression" || n.callee.type === "OptionalMemberExpression") {
const obj = evalFn(n.callee.object, context);
if (n.callee.type === "OptionalMemberExpression" && obj == null) {
return { value: undefined };
}
const prop = n.callee.computed
? evalFn(n.callee.property, context)
: n.callee.property.name;
const method = obj[prop];
return { value: method.apply(obj, args) };
}
const fn = evalFn(n.callee, context);
return { value: fn(...args) };
}
return null;
},
};