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