Files
core/src/ast/nodes/ExpressionNode.js
T
John Dvorak 717ae1031e initial commit: @arbiter/core authorization engine with js-rigor hardening
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/
binary modes, condensed snapshots, value relations) with 39 rigor test
campaigns. Includes fixes for snapshot binary writer/reader format
mismatch (snapshot-of-snapshot corruption), possibility write-boundary
validation, empty-graph snapshot serialization, relation lookup cache
direction collision, config-redefinition cache invalidation, binary
threshold semantics, defeasible compiled routing, and comparator
reason whitelisting.
2026-07-31 13:44:06 -07:00

261 lines
6.6 KiB
JavaScript

import { BaseNode } from './BaseNode.js';
/**
* AST node for expressions (variables, literals, attribute access, etc.)
*/
export class ExpressionNode extends BaseNode {
constructor(expressionType, location = null) {
super('Expression', location);
this.expressionType = expressionType; // 'variable', 'literal', 'attribute', 'function', etc.
this.value = null;
this.name = null;
this.attribute = null;
this.object = null;
this.arguments = [];
this.operator = null;
this.left = null;
this.right = null;
}
/**
* Set the value for this expression
* @param {*} value - Value to set
*/
setValue(value) {
this.value = value;
}
/**
* Set the name for this expression
* @param {string} name - Name to set
*/
setName(name) {
this.name = name;
}
/**
* Set the attribute for this expression
* @param {string} attribute - Attribute to set
*/
setAttribute(attribute) {
this.attribute = attribute;
}
/**
* Set the object for this expression
* @param {ExpressionNode} object - Object to set
*/
setObject(object) {
this.object = object;
this.addChild(object);
}
/**
* Add an argument to this expression
* @param {ExpressionNode} argument - Argument to add
*/
addArgument(argument) {
this.arguments.push(argument);
this.addChild(argument);
}
/**
* Set the operator for this expression
* @param {string} operator - Operator to set
*/
setOperator(operator) {
this.operator = operator;
}
/**
* Set the left operand for this expression
* @param {ExpressionNode} left - Left operand to set
*/
setLeft(left) {
this.left = left;
this.addChild(left);
}
/**
* Set the right operand for this expression
* @param {ExpressionNode} right - Right operand to set
*/
setRight(right) {
this.right = right;
this.addChild(right);
}
/**
* Check if this is a variable expression
* @returns {boolean} True if variable
*/
isVariable() {
return this.expressionType === 'variable';
}
/**
* Check if this is a literal expression
* @returns {boolean} True if literal
*/
isLiteral() {
return this.expressionType === 'literal';
}
/**
* Check if this is an attribute access expression
* @returns {boolean} True if attribute access
*/
isAttributeAccess() {
return this.expressionType === 'attribute';
}
/**
* Check if this is a function call expression
* @returns {boolean} True if function call
*/
isFunctionCall() {
return this.expressionType === 'function';
}
/**
* Check if this is a binary operation expression
* @returns {boolean} True if binary operation
*/
isBinaryOperation() {
return this.expressionType === 'binary';
}
/**
* Check if this is a wildcard variable
* @returns {boolean} True if wildcard
*/
isWildcard() {
return this.isVariable() && this.name && this.name.startsWith('*');
}
/**
* Get the variable name (without wildcard prefix)
* @returns {string|null} Variable name or null
*/
getVariableName() {
if (this.isVariable() && this.name) {
return this.name.startsWith('*') ? this.name.substring(1) : this.name;
}
return null;
}
/**
* Get the full attribute path
* @returns {string|null} Full attribute path or null
*/
getAttributePath() {
if (this.isAttributeAccess()) {
const objStr = this.object ? this.object.toString() : '';
return `${objStr}.${this.attribute}`;
}
return null;
}
/**
* Get the function signature
* @returns {string|null} Function signature or null
*/
getFunctionSignature() {
if (this.isFunctionCall()) {
const argStr = this.arguments.map(arg => arg.toString()).join(', ');
return `${this.name}(${argStr})`;
}
return null;
}
/**
* Validate the expression
* @returns {string[]} Array of error messages
*/
validate() {
const errors = [];
// Validate expression type
const validTypes = ['variable', 'literal', 'attribute', 'function', 'binary'];
if (!validTypes.includes(this.expressionType)) {
errors.push(`Invalid expression type: ${this.expressionType}`);
}
// Validate variable expressions
if (this.isVariable() && !this.name) {
errors.push('Variable expression must have a name');
}
// Validate literal expressions
if (this.isLiteral() && this.value === null) {
errors.push('Literal expression must have a value');
}
// Validate attribute access expressions
if (this.isAttributeAccess()) {
if (!this.attribute) {
errors.push('Attribute access expression must have an attribute');
}
if (this.object) {
const objErrors = this.object.validate ? this.object.validate() : [];
errors.push(...objErrors);
}
}
// Validate function call expressions
if (this.isFunctionCall()) {
if (!this.name) {
errors.push('Function call expression must have a name');
}
this.arguments.forEach((arg, index) => {
const argErrors = arg.validate ? arg.validate() : [];
errors.push(...argErrors.map(err => `Argument ${index + 1}: ${err}`));
});
}
// Validate binary operation expressions
if (this.isBinaryOperation()) {
if (!this.operator) {
errors.push('Binary operation expression must have an operator');
}
if (!this.left) {
errors.push('Binary operation expression must have a left operand');
}
if (!this.right) {
errors.push('Binary operation expression must have a right operand');
}
if (this.left) {
const leftErrors = this.left.validate ? this.left.validate() : [];
errors.push(...leftErrors);
}
if (this.right) {
const rightErrors = this.right.validate ? this.right.validate() : [];
errors.push(...rightErrors);
}
}
return errors;
}
toString() {
switch (this.expressionType) {
case 'variable':
return `Variable(${this.name})`;
case 'literal':
return `Literal(${this.value})`;
case 'attribute':
const objStr = this.object ? this.object.toString() : '';
return `Attribute(${objStr}.${this.attribute})`;
case 'function':
const argStr = this.arguments.map(arg => arg.toString()).join(', ');
return `Function(${this.name}(${argStr}))`;
case 'binary':
const leftStr = this.left ? this.left.toString() : 'null';
const rightStr = this.right ? this.right.toString() : 'null';
return `Binary(${leftStr} ${this.operator} ${rightStr})`;
default:
return `Expression(${this.expressionType})`;
}
}
}