UNPKG

uppaal-to-tchecker

Version:

JavaScript implementation of Uppaal to TChecker translator - convert Uppaal timed automata models to TChecker format

319 lines (274 loc) 12.7 kB
/** * Expression Translator * JavaScript implementation of utot-expr.cc */ const { TranslationException } = require('./exceptions'); class ExpressionTranslator { constructor() { this.constants = { // Binary operators PLUS: '+', MINUS: '-', MULT: '*', DIV: '/', MOD: '%', BIT_AND: '&', BIT_OR: '|', BIT_XOR: '^', BIT_LSHIFT: '<<', BIT_RSHIFT: '>>', AND: '&&', OR: '||', LT: '<', LE: '<=', EQ: '==', NEQ: '!=', GE: '>=', GT: '>', // Unary operators NOT: '!', UNARY_MINUS: '-', // Special MIN: 'min', MAX: 'max', INLINEIF: '?:' }; } /** * Evaluate integer constant expressions * @param {Object} instance - Process instance * @param {Object} expr - Expression to evaluate * @returns {number} - Evaluated constant value */ evalIntegerConstant(instance, expr) { if (expr.type && !this.isIntegerType(expr.type)) { throw new TranslationException(`Expression '${JSON.stringify(expr)}' is not an integer constant.`); } switch (expr.kind) { case 'PLUS': return this.evalIntegerConstant(instance, expr.left) + this.evalIntegerConstant(instance, expr.right); case 'MINUS': return this.evalIntegerConstant(instance, expr.left) - this.evalIntegerConstant(instance, expr.right); case 'MULT': return this.evalIntegerConstant(instance, expr.left) * this.evalIntegerConstant(instance, expr.right); case 'DIV': return Math.floor(this.evalIntegerConstant(instance, expr.left) / this.evalIntegerConstant(instance, expr.right)); case 'MOD': return this.evalIntegerConstant(instance, expr.left) % this.evalIntegerConstant(instance, expr.right); case 'BIT_AND': return this.evalIntegerConstant(instance, expr.left) & this.evalIntegerConstant(instance, expr.right); case 'BIT_OR': return this.evalIntegerConstant(instance, expr.left) | this.evalIntegerConstant(instance, expr.right); case 'BIT_XOR': return this.evalIntegerConstant(instance, expr.left) ^ this.evalIntegerConstant(instance, expr.right); case 'BIT_LSHIFT': return this.evalIntegerConstant(instance, expr.left) << this.evalIntegerConstant(instance, expr.right); case 'BIT_RSHIFT': return this.evalIntegerConstant(instance, expr.left) >> this.evalIntegerConstant(instance, expr.right); case 'AND': return this.evalIntegerConstant(instance, expr.left) && this.evalIntegerConstant(instance, expr.right); case 'OR': return this.evalIntegerConstant(instance, expr.left) || this.evalIntegerConstant(instance, expr.right); case 'LT': return this.evalIntegerConstant(instance, expr.left) < this.evalIntegerConstant(instance, expr.right) ? 1 : 0; case 'LE': return this.evalIntegerConstant(instance, expr.left) <= this.evalIntegerConstant(instance, expr.right) ? 1 : 0; case 'EQ': return this.evalIntegerConstant(instance, expr.left) === this.evalIntegerConstant(instance, expr.right) ? 1 : 0; case 'NEQ': return this.evalIntegerConstant(instance, expr.left) !== this.evalIntegerConstant(instance, expr.right) ? 1 : 0; case 'GE': return this.evalIntegerConstant(instance, expr.left) >= this.evalIntegerConstant(instance, expr.right) ? 1 : 0; case 'GT': return this.evalIntegerConstant(instance, expr.left) > this.evalIntegerConstant(instance, expr.right) ? 1 : 0; case 'MIN': { const v1 = this.evalIntegerConstant(instance, expr.left); const v2 = this.evalIntegerConstant(instance, expr.right); return Math.min(v1, v2); } case 'MAX': { const v1 = this.evalIntegerConstant(instance, expr.left); const v2 = this.evalIntegerConstant(instance, expr.right); return Math.max(v1, v2); } case 'NOT': return this.evalIntegerConstant(instance, expr.operand) ? 0 : 1; case 'UNARY_MINUS': return -this.evalIntegerConstant(instance, expr.operand); case 'INLINEIF': { const condition = this.evalIntegerConstant(instance, expr.condition); return condition ? this.evalIntegerConstant(instance, expr.trueExpr) : this.evalIntegerConstant(instance, expr.falseExpr); } case 'IDENTIFIER': { const symbol = expr.symbol; if (!symbol.isConstant) { throw new TranslationException(`Symbol '${symbol.name}' is not a constant.`); } if (instance && instance.mapping && instance.mapping[symbol.name]) { return this.evalIntegerConstant(instance, instance.mapping[symbol.name]); } if (symbol.value !== undefined) { return symbol.value; } throw new TranslationException(`Unknown constant symbol '${symbol.name}'.`); } case 'CONSTANT': return expr.value; case 'ARRAY_ACCESS': { const baseExpr = expr.base; const indexExpr = expr.index; if (baseExpr.kind === 'IDENTIFIER') { const arrayName = baseExpr.symbol.name; const index = this.evalIntegerConstant(instance, indexExpr); if (instance && instance.mapping && instance.mapping[arrayName]) { const arrayData = instance.mapping[arrayName]; if (Array.isArray(arrayData) && index >= 0 && index < arrayData.length) { return arrayData[index]; } } } throw new TranslationException(`Cannot evaluate array access: ${JSON.stringify(expr)}`); } default: throw new TranslationException(`Unsupported expression kind: ${expr.kind}`); } } /** * Translate expression to TChecker format * @param {Object} instance - Process instance * @param {Object} expr - Expression to translate * @param {Object} context - Context prefix * @returns {string} - Translated expression */ translateExpression(instance, expr, context) { if (!expr) return ''; switch (expr.kind) { case 'CONSTANT': return expr.value.toString(); case 'IDENTIFIER': return this.translateIdentifier(instance, expr, context); case 'ARRAY_ACCESS': return this.translateArrayAccess(instance, expr, context); case 'PLUS': case 'MINUS': case 'MULT': case 'DIV': case 'MOD': case 'BIT_AND': case 'BIT_OR': case 'BIT_XOR': case 'BIT_LSHIFT': case 'BIT_RSHIFT': case 'AND': case 'OR': case 'LT': case 'LE': case 'EQ': case 'NEQ': case 'GE': case 'GT': return this.translateBinaryExpression(instance, expr, context); case 'NOT': case 'UNARY_MINUS': return this.translateUnaryExpression(instance, expr, context); case 'MIN': case 'MAX': return this.translateMinMaxExpression(instance, expr, context); case 'INLINEIF': return this.translateConditionalExpression(instance, expr, context); default: throw new TranslationException(`Unsupported expression kind: ${expr.kind}`); } } /** * Translate assignment expression * @param {Object} instance - Process instance * @param {Object} expr - Assignment expression * @param {Object} context - Context prefix * @returns {string} - Translated assignment */ translateAssignment(instance, expr, context) { if (expr.kind === 'MULTIPLE_ASSIGN') { // 处理多变量赋值: x = 0, n = 1 const assignments = expr.assignments.map(assignment => { return this.translateAssignment(instance, assignment, context); }); return assignments.join(', '); } if (expr.kind !== 'ASSIGN') { throw new TranslationException(`Expected assignment expression, got: ${expr.kind}`); } const lhs = this.translateExpression(instance, expr.left, context); const rhs = this.translateExpression(instance, expr.right, context); return `${lhs} = ${rhs}`; } translateIdentifier(instance, expr, context) { const name = expr.symbol.name; const prefix = context ? context.toString() : ''; // Check if it's a constant that should be substituted if (instance && instance.constants && instance.constants[name] !== undefined) { return instance.constants[name].toString(); } // Check if it's a local variable that needs prefixing if (instance && instance.localVars && instance.localVars.has(name)) { return prefix + name; } return name; } translateArrayAccess(instance, expr, context) { const base = this.translateExpression(instance, expr.base, context); const index = this.translateExpression(instance, expr.index, context); // Handle array offset for shifted indices if (expr.base.type && expr.base.type.offset) { return `${base}[${index} - ${expr.base.type.offset}]`; } return `${base}[${index}]`; } translateBinaryExpression(instance, expr, context) { const left = this.translateExpression(instance, expr.left, context); const right = this.translateExpression(instance, expr.right, context); const operator = this.constants[expr.kind]; return `(${left} ${operator} ${right})`; } translateUnaryExpression(instance, expr, context) { const operand = this.translateExpression(instance, expr.operand, context); const operator = this.constants[expr.kind]; return `(${operator}${operand})`; } translateMinMaxExpression(instance, expr, context) { const left = this.translateExpression(instance, expr.left, context); const right = this.translateExpression(instance, expr.right, context); const func = expr.kind.toLowerCase(); return `${func}(${left}, ${right})`; } translateConditionalExpression(instance, expr, context) { const condition = this.translateExpression(instance, expr.condition, context); const trueExpr = this.translateExpression(instance, expr.trueExpr, context); const falseExpr = this.translateExpression(instance, expr.falseExpr, context); return `(${condition} ? ${trueExpr} : ${falseExpr})`; } isIntegerType(type) { return type && (type.isInteger || type.isBoolean || type.isScalar); } /** * Compute range bounds for a type * @param {Object} instance - Process instance * @param {Object} type - Type definition * @returns {Object} - {min, max} bounds */ computeRangeBounds(instance, type) { while (type.kind === 'LABEL' || type.kind === 'CONSTANT') { type = type.subType; } if (!type.isRange) { throw new TranslationException(`Type is not a range: ${JSON.stringify(type)}`); } const min = this.evalIntegerConstant(instance, type.range.min); const max = this.evalIntegerConstant(instance, type.range.max); if (min > max) { throw new TranslationException(`Empty range: [${min}, ${max}]`); } return { min, max }; } } module.exports = ExpressionTranslator;