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
JavaScript
/**
* 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;