717ae1031e
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.
261 lines
6.6 KiB
JavaScript
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})`;
|
|
}
|
|
}
|
|
}
|