UNPKG

uppaal-to-tchecker

Version:

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

504 lines (436 loc) 18.9 kB
/** * Main Translator * JavaScript implementation of utot-translate.cc */ const TCheckerOutputter = require('./tchecker-outputter'); const ExpressionTranslator = require('./expression-translator'); const DeclarationTranslator = require('./declaration-translator'); const ContextPrefix = require('./context-prefix'); const { TranslationException } = require('./exceptions'); class Translator { constructor() { this.exprTranslator = new ExpressionTranslator(); this.declTranslator = new DeclarationTranslator(); // Constants for special processes/events this.STUCK_PROCESS = 'Stuck'; this.NO_SYNC_EVENT = 'nosync'; } /** * Translate Uppaal model to TChecker format * @param {string} systemName - System name * @param {Object} timedAutomataSystem - Parsed Uppaal system * @param {Object} outputter - TChecker outputter * @returns {boolean} - Success status */ translateModel(systemName, timedAutomataSystem, outputter) { try { // Output system declaration outputter.system(systemName); // Track global events for synchronization const globalEvents = this.analyzeGlobalEvents(timedAutomataSystem); // Collect global constants for expression substitution const globalConstants = {}; if (timedAutomataSystem.declarations) { for (const decl of timedAutomataSystem.declarations) { if (decl.kind === 'CONSTANT') { globalConstants[decl.name] = decl.value; } } } // Translate global declarations if (timedAutomataSystem.declarations) { const globalContext = new ContextPrefix(''); this.declTranslator.translateDeclarations( outputter, null, globalContext, timedAutomataSystem.declarations ); } // Process system declaration to get instantiated processes const processInstances = this.processSystemDeclaration( timedAutomataSystem.system, timedAutomataSystem.templates, timedAutomataSystem.instances || [], globalConstants ); // Translate each process instance for (const instance of processInstances) { this.translateProcessInstance(outputter, instance, globalEvents); } // Generate synchronization vectors this.generateSynchronizations(outputter, globalEvents); return true; } catch (error) { if (error instanceof TranslationException) { throw error; } throw new TranslationException(`Translation failed: ${error.message}`); } } /** * Analyze global events for synchronization * @param {Object} system - Timed automata system * @returns {Object} - Global events analysis */ analyzeGlobalEvents(system) { const globalEvents = { emitters: new Map(), // event -> Set of processes receivers: new Map(), // event -> Set of processes csp: new Map(), // event -> Set of processes labels: new Set(), // all event labels broadcasts: new Set() // broadcast events }; // Analyze channels from global declarations if (system.declarations) { for (const decl of system.declarations) { if (decl.kind === 'CHANNEL') { globalEvents.labels.add(decl.name); if (decl.broadcast) { globalEvents.broadcasts.add(decl.name); } } } } return globalEvents; } /** * Process system declaration to get instantiated processes * @param {string} systemDecl - System declaration * @param {Array} templates - Available templates * @param {Array} templateInstances - Template instances from parser * @param {Object} globalConstants - Global constants * @returns {Array} - Process instances */ processSystemDeclaration(systemDecl, templates, templateInstances = [], globalConstants = {}) { const instances = []; if (!systemDecl) return instances; // Simple system parsing - extract process names const systemLine = systemDecl.replace(/system\s+/, ''); const processNames = systemLine.split(',').map(name => name.trim().replace(/;$/, '')); for (const processName of processNames) { // First check if this is a template instance const templateInstance = templateInstances.find(t => t.name === processName); if (templateInstance) { const template = templates.find(t => t.name === templateInstance.template); if (template) { // Create instance with parameters const parameters = this.processTemplateParameters(template.parameters, templateInstance.parameters, globalConstants); instances.push(this.createProcessInstance(template, processName, parameters, globalConstants)); continue; } } // Otherwise, find matching template with same name as process const template = templates.find(t => t.name === processName); if (template) { instances.push(this.createProcessInstance(template, processName, {}, globalConstants)); } } return instances; } /** * Create process instance from template * @param {Object} template - Template definition * @param {string} instanceName - Instance name * @param {Object} parameters - Template parameters * @returns {Object} - Process instance */ createProcessInstance(template, instanceName, parameters, constants = {}) { // Merge template parameters with global constants const mergedConstants = { ...constants }; // Add template parameters to constants for substitution if (parameters && typeof parameters === 'object') { for (const [paramName, paramValue] of Object.entries(parameters)) { mergedConstants[paramName] = typeof paramValue === 'object' ? paramValue.value : paramValue; } } return { name: instanceName, template: template, parameters: parameters, context: ContextPrefix.fromProcess(instanceName), localVars: new Set(), mapping: {}, constants: mergedConstants }; } /** * Translate process instance * @param {Object} outputter - TChecker outputter * @param {Object} instance - Process instance * @param {Object} globalEvents - Global events */ translateProcessInstance(outputter, instance, globalEvents) { // Output process declaration outputter.process(instance.name); // Translate local declarations if (instance.template.declarations) { this.declTranslator.translateDeclarations( outputter, instance, instance.context, instance.template.declarations ); } // Translate locations this.translateStates(outputter, instance); // Translate transitions this.translateTransitions(outputter, instance, globalEvents); } /** * Translate states/locations * @param {Object} outputter - TChecker outputter * @param {Object} instance - Process instance */ translateStates(outputter, instance) { const template = instance.template; const processName = instance.name; for (const location of template.locations) { const attributes = {}; // Add invariant if present if (location.invariant) { attributes[TCheckerOutputter.LOCATION_INVARIANT] = this.exprTranslator.translateExpression(instance, location.invariant, instance.context); } // Add location properties if (location.committed) { attributes[TCheckerOutputter.LOCATION_COMMITTED] = ''; } if (location.urgent) { attributes[TCheckerOutputter.LOCATION_URGENT] = ''; } // Mark initial location if (location.id === template.init) { attributes[TCheckerOutputter.LOCATION_INITIAL] = ''; } outputter.location(processName, location.id, attributes); } } /** * Translate transitions * @param {Object} outputter - TChecker outputter * @param {Object} instance - Process instance * @param {Object} globalEvents - Global events */ translateTransitions(outputter, instance, globalEvents) { const template = instance.template; const processName = instance.name; for (const transition of template.transitions) { // Determine event name let eventName = TCheckerOutputter.TAU_EVENT; if (transition.sync) { eventName = this.translateSyncEvent(transition.sync, globalEvents); this.updateGlobalEvents(globalEvents, processName, transition.sync); } // Build edge attributes const attributes = {}; // Add guard condition if (transition.guard) { attributes[TCheckerOutputter.EDGE_PROVIDED] = this.exprTranslator.translateExpression(instance, transition.guard, instance.context); } // Add update actions if (transition.update) { attributes[TCheckerOutputter.EDGE_DO] = this.exprTranslator.translateAssignment(instance, transition.update, instance.context); } // Output the edge outputter.edge(processName, transition.source, transition.target, eventName, attributes); // Declare event if not already declared if (eventName !== TCheckerOutputter.TAU_EVENT && !globalEvents.labels.has(eventName)) { outputter.event(eventName); globalEvents.labels.add(eventName); } } } /** * Translate synchronization event * @param {Object} sync - Sync object * @param {Object} globalEvents - Global events * @returns {string} - Event name */ translateSyncEvent(sync, globalEvents) { const channel = sync.channel; if (sync.kind === 'SEND') { return this.getEmitEvent(channel); } else if (sync.kind === 'RECEIVE') { return this.getRecvEvent(channel); } else { return this.getCSPEvent(channel); } } /** * Update global events tracking * @param {Object} globalEvents - Global events * @param {string} processName - Process name * @param {Object} sync - Sync object */ updateGlobalEvents(globalEvents, processName, sync) { const channel = sync.channel; if (sync.kind === 'SEND') { const event = this.getEmitEvent(channel); if (!globalEvents.emitters.has(event)) { globalEvents.emitters.set(event, new Set()); } globalEvents.emitters.get(event).add(processName); } else if (sync.kind === 'RECEIVE') { const event = this.getRecvEvent(channel); if (!globalEvents.receivers.has(event)) { globalEvents.receivers.set(event, new Set()); } globalEvents.receivers.get(event).add(processName); } else { const event = this.getCSPEvent(channel); if (!globalEvents.csp.has(event)) { globalEvents.csp.set(event, new Set()); } globalEvents.csp.get(event).add(processName); } } /** * Generate synchronization vectors * @param {Object} outputter - TChecker outputter * @param {Object} globalEvents - Global events */ generateSynchronizations(outputter, globalEvents) { // Handle CCS-like synchronizations this.generateCCSSynchronizations(outputter, globalEvents); // Handle CSP synchronizations this.generateCSPSynchronizations(outputter, globalEvents); } /** * Generate CCS-like synchronizations * @param {Object} outputter - TChecker outputter * @param {Object} globalEvents - Global events */ generateCCSSynchronizations(outputter, globalEvents) { // Match emitters with receivers for (const [emitEvent, emitters] of globalEvents.emitters.entries()) { const channel = emitEvent.replace('_emit', ''); const recvEvent = this.getRecvEvent(channel); if (globalEvents.receivers.has(recvEvent)) { const receivers = globalEvents.receivers.get(recvEvent); const isBroadcast = globalEvents.broadcasts.has(channel); if (isBroadcast) { // Broadcast: one emitter, multiple receivers for (const emitter of emitters) { const syncVector = {}; syncVector[emitter] = { event: emitEvent, marked: false }; for (const receiver of receivers) { syncVector[receiver] = { event: recvEvent, marked: true }; } outputter.sync(syncVector); } } else { // Point-to-point: pair emitters with receivers for (const emitter of emitters) { for (const receiver of receivers) { const syncVector = {}; syncVector[emitter] = { event: emitEvent, marked: false }; syncVector[receiver] = { event: recvEvent, marked: false }; outputter.sync(syncVector); } } } } else { // No receivers - create stuck process to block this.createStuckSynchronization(outputter, emitEvent, emitters); } } // Handle receivers without emitters for (const [recvEvent, receivers] of globalEvents.receivers.entries()) { const channel = recvEvent.replace('_recv', ''); const emitEvent = this.getEmitEvent(channel); if (!globalEvents.emitters.has(emitEvent)) { this.createStuckSynchronization(outputter, recvEvent, receivers); } } } /** * Generate CSP synchronizations * @param {Object} outputter - TChecker outputter * @param {Object} globalEvents - Global events */ generateCSPSynchronizations(outputter, globalEvents) { for (const [cspEvent, processes] of globalEvents.csp.entries()) { if (processes.size >= 2) { const syncVector = {}; for (const process of processes) { syncVector[process] = { event: cspEvent, marked: false }; } outputter.sync(syncVector); } } } /** * Create stuck process synchronization for blocking * @param {Object} outputter - TChecker outputter * @param {string} event - Event to block * @param {Set} processes - Processes using the event */ createStuckSynchronization(outputter, event, processes) { // Create stuck process if not already created if (!this.stuckProcessCreated) { outputter.process(this.STUCK_PROCESS); outputter.event(this.NO_SYNC_EVENT); outputter.location(this.STUCK_PROCESS, 'sink', { [TCheckerOutputter.LOCATION_INITIAL]: '' }); this.stuckProcessCreated = true; } // Create sync vector with stuck process for (const process of processes) { const syncVector = {}; syncVector[process] = { event: event, marked: false }; syncVector[this.STUCK_PROCESS] = { event: this.NO_SYNC_EVENT, marked: false }; outputter.sync(syncVector); } } /** * Get emit event name * @param {string} channel - Channel name * @returns {string} - Emit event name */ getEmitEvent(channel) { return `${channel}_emit`; } /** * Get receive event name * @param {string} channel - Channel name * @returns {string} - Receive event name */ getRecvEvent(channel) { return `${channel}_recv`; } /** * Get CSP event name * @param {string} channel - Channel name * @returns {string} - CSP event name */ getCSPEvent(channel) { return channel; } /** * Process template parameters for instantiation * @param {Array} templateParams - Template parameter definitions * @param {Array} instanceArgs - Instance arguments * @param {Object} globalConstants - Global constants * @returns {Object} - Parameter mapping */ processTemplateParameters(templateParams, instanceArgs, globalConstants) { const parameters = {}; if (!templateParams || !instanceArgs) return parameters; for (let i = 0; i < Math.min(templateParams.length, instanceArgs.length); i++) { const param = templateParams[i]; const arg = instanceArgs[i]; // Resolve argument value let value; if (globalConstants[arg] !== undefined) { value = globalConstants[arg]; } else if (/^\d+$/.test(arg)) { value = parseInt(arg); } else { value = arg; // Keep as string for now } parameters[param.name] = { type: param.type, value: value, isConst: param.isConst }; } return parameters; } } module.exports = Translator;