UNPKG

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