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})`; } } }