uppaal-to-tchecker
Version:
JavaScript implementation of Uppaal to TChecker translator - convert Uppaal timed automata models to TChecker format
315 lines (281 loc) • 11.8 kB
JavaScript
/**
* Declaration Translator
* JavaScript implementation of utot-decl.cc
*/
const { TranslationException } = require('./exceptions');
const ExpressionTranslator = require('./expression-translator');
const TCheckerOutputter = require('./tchecker-outputter');
class DeclarationTranslator {
constructor() {
this.exprTranslator = new ExpressionTranslator();
}
/**
* Translate declarations to TChecker format
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {Object} context - Context prefix
* @param {Object} declarations - Declarations to translate
*/
translateDeclarations(outputter, instance, context, declarations) {
if (!declarations) return;
for (const decl of declarations) {
this.translateDeclaration(outputter, instance, context, decl);
}
}
/**
* Translate a single declaration
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {Object} context - Context prefix
* @param {Object} decl - Declaration to translate
*/
translateDeclaration(outputter, instance, context, decl) {
switch (decl.kind) {
case 'VARIABLE':
this.translateVariableDeclaration(outputter, instance, context, decl);
break;
case 'CLOCK':
this.translateClockDeclaration(outputter, instance, context, decl);
break;
case 'ARRAY':
this.translateArrayDeclaration(outputter, instance, context, decl);
break;
case 'CHANNEL':
// Channel declarations generate events, handled in translator
break;
case 'CONSTANT':
// Constants should not generate variable declarations in TChecker
// They are handled during expression evaluation by substitution
break;
default:
console.warn(`Unsupported declaration kind: ${decl.kind}`);
}
}
/**
* Translate variable declaration
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {Object} context - Context prefix
* @param {Object} decl - Variable declaration
*/
translateVariableDeclaration(outputter, instance, context, decl) {
const varName = context.applyToVariable(decl.name);
const size = decl.size || 1;
this.outputIntegerVariable(outputter, instance, varName, decl.type, decl.init, size);
}
/**
* Translate clock declaration
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {Object} context - Context prefix
* @param {Object} decl - Clock declaration
*/
translateClockDeclaration(outputter, instance, context, decl) {
const clockName = context.applyToVariable(decl.name);
const size = decl.size || 1;
outputter.clock(clockName, size);
}
/**
* Translate array declaration
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {Object} context - Context prefix
* @param {Object} decl - Array declaration
*/
translateArrayDeclaration(outputter, instance, context, decl) {
const arrayName = context.applyToVariable(decl.name);
// Handle new array structure with dimensions property
if (decl.dimensions && decl.dimensions.length === 1) {
// One-dimensional array - can be handled as TChecker array
const size = decl.dimensions[0];
this.outputIntegerVariable(outputter, instance, arrayName, decl.type, decl.init, size);
} else if (decl.dimensions && decl.dimensions.length > 1) {
// Multi-dimensional array - flatten to individual variables
this.flattenMultiDimensionalArray(outputter, instance, context, decl);
} else {
// Fallback to old logic for backward compatibility
if (this.isOneDimArrayType(instance, decl.type)) {
// Handle as TChecker array
this.outputIntegerVariable(outputter, instance, arrayName, decl.type.elementType, decl.init);
} else {
// Flatten multi-dimensional arrays
this.enumerateArrayElements(outputter, instance, arrayName, decl.type, decl.init, []);
}
}
}
/**
* Flatten multi-dimensional array to individual variables
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {Object} context - Context prefix
* @param {Object} decl - Array declaration
*/
flattenMultiDimensionalArray(outputter, instance, context, decl) {
const arrayName = context.applyToVariable(decl.name);
const dimensions = decl.dimensions;
// Generate all combinations of indices
const generateIndices = (dims, current = []) => {
if (current.length === dims.length) {
// Generate variable name for this index combination
const varName = `${arrayName}_${current.join('_')}`;
this.outputIntegerVariable(outputter, instance, varName, decl.type, decl.init);
return;
}
const dimSize = dims[current.length];
for (let i = 0; i < dimSize; i++) {
generateIndices(dims, [...current, i]);
}
};
generateIndices(dimensions);
}
/**
* Output integer variable
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {string} varName - Variable name
* @param {Object} type - Variable type
* @param {Object} init - Initial value expression
*/
outputIntegerVariable(outputter, instance, varName, type, init, size = 1) {
let min, max, initVal;
const kind = this.getTypeKind(type);
if (kind === 'BOOL') {
min = 0;
max = 1;
} else if (kind === 'RANGE') {
// 处理有界整数类型
if (type.range) {
min = type.range.min;
max = type.range.max;
} else {
const bounds = this.exprTranslator.computeRangeBounds(instance, type);
min = bounds.min;
max = bounds.max;
}
} else if (kind === 'INT') {
// Default integer range
min = -32768;
max = 32767;
} else {
throw new TranslationException(`Unsupported variable type: ${kind}`);
}
if (init === null || init === undefined) {
// Use default initial value
initVal = (min <= 0 && 0 <= max) ? 0 : min;
if (this.verboseLevel > 0) {
console.warn(`Enforcing initial value of variable '${varName}' to ${initVal}.`);
}
} else if (typeof init === 'number') {
initVal = init;
} else {
initVal = this.exprTranslator.evalIntegerConstant(instance, init);
}
if (kind === 'CONSTANT') {
min = max = initVal;
}
if (!(min <= initVal && initVal <= max)) {
throw new TranslationException(
`Initial value ${initVal} is out of domain [${min}, ${max}] for variable ${varName}`
);
}
outputter.intvar(min, max, initVal, varName, size);
}
/**
* Enumerate array elements for flattening
* @param {Object} outputter - TChecker outputter
* @param {Object} instance - Process instance
* @param {string} arrayName - Array name
* @param {Object} type - Array type
* @param {Object} init - Initial values
* @param {Array} indices - Current dimension indices
*/
enumerateArrayElements(outputter, instance, arrayName, type, init, indices) {
const kind = this.getTypeKind(type);
if (kind === 'ARRAY') {
const subType = type.elementType;
const bounds = this.exprTranslator.computeRangeBounds(instance, type.indexType);
const { min, max } = bounds;
const initSize = init && init.elements ? init.elements.length : 0;
if ((max - min + 1) > initSize && init) {
throw new TranslationException(`Invalid initializer size for array type: ${JSON.stringify(init)}`);
}
for (let i = min; i <= max; i++) {
const newIndices = [...indices, i];
const elementInit = (init && init.elements) ? init.elements[i - min] : null;
this.enumerateArrayElements(outputter, instance, arrayName, subType, elementInit, newIndices);
}
} else {
// Leaf element - create individual variable
const elementName = `${arrayName}_${indices.join('_')}`;
this.outputIntegerVariable(outputter, instance, elementName, type, init);
}
}
/**
* Check if type is one-dimensional array compatible with TChecker
* @param {Object} instance - Process instance
* @param {Object} type - Type to check
* @returns {boolean} - True if one-dimensional array
*/
isOneDimArrayType(instance, type) {
if (this.getTypeKind(type) !== 'ARRAY') {
return false;
}
// Check if it's single dimension and zero-indexed
const indexType = type.indexType;
if (indexType && indexType.isRange) {
const bounds = this.exprTranslator.computeRangeBounds(instance, indexType);
return bounds.min === 0;
}
return false;
}
/**
* Check if all elements in array initializer are equal
* @param {Object} instance - Process instance
* @param {Object} init - Array initializer
* @returns {Object|null} - Common value if all equal, null otherwise
*/
areAllEqualsInList(instance, init) {
if (!init || !init.elements || init.elements.length === 0) {
return null;
}
const firstValue = this.exprTranslator.evalIntegerConstant(instance, init.elements[0]);
for (let i = 1; i < init.elements.length; i++) {
const value = this.exprTranslator.evalIntegerConstant(instance, init.elements[i]);
if (value !== firstValue) {
return null;
}
}
return { kind: 'CONSTANT', value: firstValue };
}
/**
* Get type kind string
* @param {Object} type - Type object
* @returns {string} - Type kind
*/
getTypeKind(type) {
while (type.kind === 'LABEL') {
type = type.subType;
}
return type.kind;
}
/**
* Check if variable is one-dimensional array
* @param {Object} instance - Process instance
* @param {Object} expr - Expression
* @returns {Object|null} - Array info if one-dimensional, null otherwise
*/
isOneDimArrayVariable(instance, expr) {
if (expr.kind === 'IDENTIFIER' && expr.symbol && expr.symbol.type) {
const type = expr.symbol.type;
if (this.isOneDimArrayType(instance, type)) {
const bounds = this.exprTranslator.computeRangeBounds(instance, type.indexType);
return {
minSize: bounds.min,
maxSize: bounds.max
};
}
}
return null;
}
}
module.exports = DeclarationTranslator;