From 94e06effe8513f86a79f81febd152f2bbc1ff824 Mon Sep 17 00:00:00 2001
From: Jan Gruteser <jan.gruteser@hhu.de>
Date: Wed, 15 Jan 2025 06:37:56 +0100
Subject: [PATCH] refactor operation handling

---
 .../java/de/tla2b/analysis/BOperation.java    | 296 +++++++-----------
 .../tla2b/translation/OperationsFinder.java   |  62 ++--
 2 files changed, 151 insertions(+), 207 deletions(-)

diff --git a/src/main/java/de/tla2b/analysis/BOperation.java b/src/main/java/de/tla2b/analysis/BOperation.java
index de3bbdb..814e5be 100644
--- a/src/main/java/de/tla2b/analysis/BOperation.java
+++ b/src/main/java/de/tla2b/analysis/BOperation.java
@@ -2,120 +2,115 @@ package de.tla2b.analysis;
 
 import de.be4.classicalb.core.parser.node.*;
 import de.tla2b.global.BBuiltInOPs;
-import de.tla2b.global.TranslationGlobals;
-import de.tla2b.types.TLAType;
 import de.tla2bAst.BAstCreator;
 import tla2sany.semantic.*;
 import tlc2.tool.BuiltInOPs;
 
 import java.util.*;
-import java.util.Map.Entry;
 import java.util.stream.Collectors;
 
-public class BOperation extends BuiltInOPs implements TranslationGlobals {
+import static de.be4.classicalb.core.parser.util.ASTBuilder.createConjunction;
+import static de.be4.classicalb.core.parser.util.ASTBuilder.createIdentifier;
+import static de.tla2b.global.TranslationGlobals.SUBSTITUTE_PARAM;
+
+public class BOperation extends BuiltInOPs {
 	private final String name;
 	private final OpApplNode node;
 	private final List<OpApplNode> existQuans;
-	private List<String> opParams;
-	private List<FormalParamNode> formalParams;
-	private List<String> unchangedVariables;
-	private final List<OpDeclNode> unchangedVariablesList;
-	private final List<ExprOrOpArgNode> guards;
-	private final List<ExprOrOpArgNode> beforeAfterPredicates;
-	private final Map<SymbolNode, ExprOrOpArgNode> assignments = new LinkedHashMap<>();
-	private List<OpDeclNode> anyVariables;
-	private final SpecAnalyser specAnalyser;
+	private final List<FormalParamNode> formalParams = new ArrayList<>();
+	private final List<OpDeclNode> unchangedVariables = new ArrayList<>();
+	private final List<ExprOrOpArgNode> guards = new ArrayList<>();
+	private final List<ExprOrOpArgNode> beforeAfterPredicates = new ArrayList<>();
+	private final Map<OpDeclNode, ExprOrOpArgNode> assignments = new LinkedHashMap<>();
+	private final List<OpDeclNode> anyVariables = new ArrayList<>();
 
 	public BOperation(String name, OpApplNode n, List<OpApplNode> existQuans, SpecAnalyser specAnalyser) {
 		this.name = name;
 		this.node = n;
 		this.existQuans = existQuans;
-		this.specAnalyser = specAnalyser;
-		this.unchangedVariablesList = new ArrayList<>();
-		this.guards = new ArrayList<>();
-		this.beforeAfterPredicates = new ArrayList<>();
-		anyVariables = new ArrayList<>(Arrays.asList(specAnalyser.getModuleNode().getVariableDecls()));
+		OpDeclNode[] variableDecls = specAnalyser.getModuleNode().getVariableDecls();
+		this.anyVariables.addAll(Arrays.asList(variableDecls));
 
-		evalParams();
-		// System.out.println("Construction B Operation for TLA+ action: " + name);
-		findUnchangedVariables();
+		// eval params
+		for (OpApplNode ex : existQuans) {
+			for (FormalParamNode[] param : ex.getBdedQuantSymbolLists()) { // (bounded exists)
+				formalParams.addAll(Arrays.asList(param));
+			}
+		}
+		// DebugUtils.printDebugMsg("Constructing B Operation for TLA+ action: " + name);
+		findUnchangedVariablesInSemanticNode(node);
 		// System.out.println(" UNCHANGED = " + unchangedVariables.toString());
 		separateGuardsAndBeforeAfterPredicates(node);
-		findAssignments();
+		findAssignments(variableDecls);
 	}
 
 	public AOperation getBOperation(BAstCreator bASTCreator) {
-		bASTCreator.setUnchangedVariablesNames(unchangedVariables);
+		bASTCreator.setUnchangedVariablesNames(this.getUnchangedVariables());
+
 		List<PPredicate> whereList = new ArrayList<>();
 		for (OpApplNode o : this.getExistQuans()) {
 			whereList.add(bASTCreator.visitBoundsOfLocalVariables(o));
 		}
-
 		for (ExprOrOpArgNode g : guards) {
 			whereList.add(bASTCreator.visitExprOrOpArgNodePredicate(g));
 		}
 
-		List<PExpression> leftSideOfAssigment = new ArrayList<>();
-		List<PExpression> rightSideOfAssigment = new ArrayList<>();
-		for (Entry<SymbolNode, ExprOrOpArgNode> entry : assignments.entrySet()) {
-			leftSideOfAssigment.add(bASTCreator.createIdentifierNode(entry.getKey()));
-			rightSideOfAssigment.add(bASTCreator.visitExprOrOpArgNodeExpression(entry.getValue()));
-		}
+		List<PExpression> lhsAssignment = new ArrayList<>();
+		List<PExpression> rhsAssignment = new ArrayList<>();
+		assignments.forEach((id, assignExpr) -> {
+			lhsAssignment.add(bASTCreator.createIdentifierNode(id));
+			rhsAssignment.add(bASTCreator.visitExprOrOpArgNodeExpression(assignExpr));
+		});
+
 		PSubstitution operationBody;
 		AAssignSubstitution assign = new AAssignSubstitution();
 		if (!anyVariables.isEmpty()) { // ANY x_n WHERE P THEN A END
 			List<PExpression> anyParams = new ArrayList<>();
 			for (OpDeclNode var : anyVariables) {
-				String nextName = var.getName().toString() + "_n";
-				anyParams.add(BAstCreator.createIdentifierNode(nextName));
-
-				whereList.add(new AMemberPredicate(
-						BAstCreator.createIdentifierNode(nextName),
-						((TLAType) var.getToolObject(TYPE_ID)).getBNode()
-				));
-				leftSideOfAssigment.add(bASTCreator.createIdentifierNode(var));
-				rightSideOfAssigment.add(BAstCreator.createIdentifierNode(nextName));
+				AIdentifierExpression nextName = createIdentifier(var.getName().toString() + "_n");
+				anyParams.add(nextName);
+				whereList.add(new AMemberPredicate(nextName.clone(), TypeChecker.getType(var).getBNode()));
+				lhsAssignment.add(bASTCreator.createIdentifierNode(var));
+				rhsAssignment.add(nextName.clone());
 			}
 			whereList.addAll(createBeforeAfterPredicates(bASTCreator));
-			operationBody = new AAnySubstitution(anyParams, bASTCreator.createConjunction(whereList), assign);
+			operationBody = new AAnySubstitution(anyParams, createConjunction(whereList), assign);
 		} else if (!whereList.isEmpty()) { // SELECT P THEN A END
 			whereList.addAll(createBeforeAfterPredicates(bASTCreator));
 			for (ExprOrOpArgNode e : beforeAfterPredicates) {
 				whereList.add(bASTCreator.visitExprOrOpArgNodePredicate(e));
 			}
-			operationBody = new ASelectSubstitution(bASTCreator.createConjunction(whereList), assign, new ArrayList<>(), null);
+			operationBody = new ASelectSubstitution(createConjunction(whereList), assign, new ArrayList<>(), null);
 		} else { // BEGIN A END
 			operationBody = new ABlockSubstitution(assign);
 		}
 
-		if (!leftSideOfAssigment.isEmpty()) {
-			assign.setLhsExpression(leftSideOfAssigment);
-			assign.setRhsExpressions(rightSideOfAssigment);
+		if (!lhsAssignment.isEmpty()) {
+			assign.setLhsExpression(lhsAssignment);
+			assign.setRhsExpressions(rhsAssignment);
 		} else { // skip
 			assign.replaceBy(new ASkipSubstitution());
 		}
 
-		return new AOperation(
-				new ArrayList<>(),
+		return new AOperation(new LinkedList<>(),
 				bASTCreator.createPositionedTIdentifierLiteral(name, getNode()),
 				this.getFormalParams().stream().map(bASTCreator::createIdentifierNode).collect(Collectors.toList()),
 				operationBody
 		);
 	}
 
-	private ArrayList<PPredicate> createBeforeAfterPredicates(BAstCreator bAstCreator) {
-		ArrayList<PPredicate> predicates = new ArrayList<>();
+	private List<PPredicate> createBeforeAfterPredicates(BAstCreator bAstCreator) {
+		List<PPredicate> predicates = new ArrayList<>();
 		for (ExprOrOpArgNode e : beforeAfterPredicates) {
 			PPredicate body = null;
 			if (e instanceof OpApplNode) {
 				OpApplNode opApplNode = (OpApplNode) e;
 				if (opApplNode.getOperator().getKind() == UserDefinedOpKind
-					&& !BBuiltInOPs.contains(opApplNode.getOperator().getName())) {
+						&& !BBuiltInOPs.contains(opApplNode.getOperator().getName())) {
 					OpDefNode def = (OpDefNode) opApplNode.getOperator();
 					FormalParamNode[] params = def.getParams();
 					for (int j = 0; j < params.length; j++) {
-						FormalParamNode param = params[j];
-						param.setToolObject(SUBSTITUTE_PARAM, opApplNode.getArgs()[j]);
+						params[j].setToolObject(SUBSTITUTE_PARAM, opApplNode.getArgs()[j]);
 					}
 					body = bAstCreator.visitExprNodePredicate(def.getBody());
 				}
@@ -128,45 +123,41 @@ public class BOperation extends BuiltInOPs implements TranslationGlobals {
 		return predicates;
 	}
 
-	private void findAssignments() {
+	private void findAssignments(OpDeclNode[] variableDecls) {
 		PrimedVariablesFinder primedVariablesFinder = new PrimedVariablesFinder(beforeAfterPredicates);
 		for (ExprOrOpArgNode node : new ArrayList<>(beforeAfterPredicates)) {
 			if (node instanceof OpApplNode) {
 				OpApplNode opApplNode = (OpApplNode) node;
-				if (opApplNode.getOperator().getKind() == BuiltInKind) {
-
-					if (OPCODE_eq == getOpCode(opApplNode.getOperator().getName())) {
-						ExprOrOpArgNode arg1 = opApplNode.getArgs()[0]; // we have equality arg1 = RHS
-						try {
-							OpApplNode arg11 = (OpApplNode) arg1;
-							if (getOpCode(arg11.getOperator().getName()) == OPCODE_prime) {
-								OpApplNode v = (OpApplNode) arg11.getArgs()[0];
-								SymbolNode var = v.getOperator();
-								// we have equality var' = RHS
-								if (!primedVariablesFinder.getTwiceUsedVariables().contains(var)) {
-									// var' is only used once in all before after predicates
-									// meaning we do not need it as parameter of the ANY
-									// and can add an assignment var := RHS
-									assignments.put(v.getOperator(), opApplNode.getArgs()[1]); // RHS of assignment
-									beforeAfterPredicates.remove(node);
-									// System.out.println("Detected assignment " + var.getName().toString() + "' = <RHS>");
-								}
+				if (opApplNode.getOperator().getKind() == BuiltInKind
+						&& getOpCode(opApplNode.getOperator().getName()) == OPCODE_eq) {
+					ExprOrOpArgNode arg1 = opApplNode.getArgs()[0]; // we have equality arg1 = RHS
+					try {
+						OpApplNode primeAppl = (OpApplNode) arg1;
+						if (getOpCode(primeAppl.getOperator().getName()) == OPCODE_prime) {
+							OpDeclNode var = (OpDeclNode) ((OpApplNode) primeAppl.getArgs()[0]).getOperator(); // var is first arg of prime
+							// we have equality var' = RHS
+							if (!primedVariablesFinder.getTwiceUsedVariables().contains(var)) {
+								// var' is only used once in all before after predicates
+								// meaning we do not need it as parameter of the ANY
+								// and can add an assignment var := RHS
+								assignments.put(var, opApplNode.getArgs()[1]); // RHS of assignment
+								beforeAfterPredicates.remove(node);
+								// System.out.println("Detected assignment " + var.getName().toString() + "' = <RHS>");
 							}
-						} catch (ClassCastException e) {
 						}
+					} catch (ClassCastException e) {
 					}
-
 				}
 			}
 		}
-		anyVariables = new ArrayList<>();
-		Collections.addAll(anyVariables, specAnalyser.getModuleNode().getVariableDecls());
+		anyVariables.clear();
+		Collections.addAll(anyVariables, variableDecls);
 
 		// for (SymbolNode symbol : primedVariablesFinder.getAllVariables()) {
 		// anyVariables.add((OpDeclNode) symbol);
 		// }
 		anyVariables.removeAll(assignments.keySet());
-		anyVariables.removeAll(unchangedVariablesList);
+		anyVariables.removeAll(unchangedVariables);
 	}
 
 	private void separateGuardsAndBeforeAfterPredicates(ExprOrOpArgNode node) {
@@ -174,132 +165,48 @@ public class BOperation extends BuiltInOPs implements TranslationGlobals {
 			OpApplNode opApplNode = (OpApplNode) node;
 			if (opApplNode.getOperator().getKind() == BuiltInKind) {
 				switch (getOpCode(opApplNode.getOperator().getName())) {
-					case OPCODE_land: // \land
-					{
-						separateGuardsAndBeforeAfterPredicates(opApplNode.getArgs()[0]);
-						separateGuardsAndBeforeAfterPredicates(opApplNode.getArgs()[1]);
-						return;
-					}
+					case OPCODE_land: // \land (getArgs has 2 args)
 					case OPCODE_cl: // $ConjList
-					{
-						for (int i = 0; i < opApplNode.getArgs().length; i++) {
-							separateGuardsAndBeforeAfterPredicates(opApplNode
-								.getArgs()[i]);
-						}
+						Arrays.stream(opApplNode.getArgs()).forEach(this::separateGuardsAndBeforeAfterPredicates);
 						return;
-					}
-					default: {
-						if (opApplNode.level < 2) {
-							guards.add(node); // should we be checking nonLeibnizParams is empty ?
-						} else {
-							beforeAfterPredicates.add(node);
-						}
+					default:
+						(opApplNode.level < 2 ? guards : beforeAfterPredicates).add(node);
+						// guards: should we check if nonLeibnizParams is empty?
 						return;
-					}
-
 				}
 			}
 		}
-		if (node.level < 2) {
-			guards.add(node);
-		} else {
-			beforeAfterPredicates.add(node);
-		}
-		// beforeAfterPredicates.add(node);
-	}
-
-	private void evalParams() {
-		opParams = new ArrayList<>();
-		formalParams = new ArrayList<>();
-		for (OpApplNode n : existQuans) {
-			FormalParamNode[][] params = n.getBdedQuantSymbolLists();
-			for (FormalParamNode[] param : params) {
-				for (FormalParamNode formalParamNode : param) {
-					formalParams.add(formalParamNode);
-					opParams.add(formalParamNode.getName().toString());
-				}
-			}
-		}
-	}
-
-	public SymbolNode getSymbolNode() {
-		if (node != null && node.getOperator().getKind() == UserDefinedOpKind) {
-			return node.getOperator();
-		}
-		return null;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public ExprNode getNode() {
-		return node;
-	}
-
-	public List<OpApplNode> getExistQuans() {
-		return new ArrayList<>(existQuans);
-	}
-
-	public List<String> getOpParams() {
-		return opParams;
-	}
-
-	public List<FormalParamNode> getFormalParams() {
-		return formalParams;
-	}
-
-	public List<String> getUnchangedVariables() {
-		return unchangedVariables;
-	}
-
-	private void findUnchangedVariables() {
-		unchangedVariables = new ArrayList<>();
-		findUnchangedVariablesInSemanticNode(node);
+		(node.level < 2 ? guards : beforeAfterPredicates).add(node); // level = 2 means action predicate
 	}
 
 	private void findUnchangedVariablesInSemanticNode(SemanticNode node) {
 		switch (node.getKind()) {
-			case OpApplKind: {
+			case OpApplKind:
 				findUnchangedVariablesInOpApplNode((OpApplNode) node);
 				return;
-			}
-			case LetInKind: {
-				LetInNode letNode = (LetInNode) node;
-				findUnchangedVariablesInSemanticNode(letNode.getBody());
+			case LetInKind:
+				findUnchangedVariablesInSemanticNode(((LetInNode) node).getBody());
 				return;
-			}
-
-			case SubstInKind: {
-				SubstInNode substInNode = (SubstInNode) node;
-				findUnchangedVariablesInSemanticNode(substInNode.getBody());
-			}
+			case SubstInKind:
+				findUnchangedVariablesInSemanticNode(((SubstInNode) node).getBody());
 		}
 	}
 
 	private void findUnchangedVariablesInOpApplNode(OpApplNode n) {
-
 		int kind = n.getOperator().getKind();
 		if (kind == UserDefinedOpKind && !BBuiltInOPs.contains(n.getOperator().getName())) {
-			OpDefNode def = (OpDefNode) n.getOperator();
-			findUnchangedVariablesInSemanticNode(def.getBody());
+			findUnchangedVariablesInSemanticNode(((OpDefNode) n.getOperator()).getBody());
 		} else if (kind == BuiltInKind) {
-			int opcode = BuiltInOPs.getOpCode(n.getOperator().getName());
-			switch (opcode) {
+			switch (BuiltInOPs.getOpCode(n.getOperator().getName())) {
 				case OPCODE_land: // \land
-				case OPCODE_cl: { // $ConjList
-					for (int i = 0; i < n.getArgs().length; i++) {
-						findUnchangedVariablesInSemanticNode(n.getArgs()[i]);
-					}
+				case OPCODE_cl: // $ConjList
+					Arrays.stream(n.getArgs()).forEach(this::findUnchangedVariablesInSemanticNode);
 					return;
-				}
 				case OPCODE_unchanged: {
-					n.setToolObject(USED, false);
+					// n.setToolObject(USED, false); FIXME: this tool object is never used?
 					OpApplNode k = (OpApplNode) n.getArgs()[0];
 					if (k.getOperator().getKind() == VariableDeclKind) {
-						unchangedVariablesList.add((OpDeclNode) k.getOperator());
-						String name = k.getOperator().getName().toString();
-						unchangedVariables.add(name);
+						unchangedVariables.add((OpDeclNode) k.getOperator());
 					} else {
 						// Tuple
 						for (int i = 0; i < k.getArgs().length; i++) {
@@ -310,7 +217,7 @@ public class BOperation extends BuiltInOPs implements TranslationGlobals {
 								// we have a definition
 								OpDefNode def = (OpDefNode) var.getOperator();
 								if (def.getParams().length > 0) {
-									// we do not support definitions with arguments yet
+									// TODO: we do not support definitions with arguments yet
 									throw new RuntimeException("Declaration with parameters not supported for UNCHANGED: " + var.getOperator().getName() + " " + var.getLocation());
 								}
 								ExprNode body = def.getBody();
@@ -323,9 +230,7 @@ public class BOperation extends BuiltInOPs implements TranslationGlobals {
 							} else if (!(var.getOperator() instanceof OpDeclNode)) {
 								throw new RuntimeException("Cannot convert to list of UNCHANGED variables: " + var.getOperator().getName() + " " + var.getLocation());
 							} else {
-								unchangedVariablesList.add((OpDeclNode) var.getOperator());
-								String name = var.getOperator().getName().toString();
-								unchangedVariables.add(name);
+								unchangedVariables.add((OpDeclNode) var.getOperator());
 							}
 						}
 					}
@@ -333,6 +238,37 @@ public class BOperation extends BuiltInOPs implements TranslationGlobals {
 			}
 		}
 	}
+
+	public SymbolNode getSymbolNode() {
+		if (node != null && node.getOperator().getKind() == UserDefinedOpKind) {
+			return node.getOperator();
+		}
+		return null;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public ExprNode getNode() {
+		return node;
+	}
+
+	public List<OpApplNode> getExistQuans() {
+		return new ArrayList<>(existQuans);
+	}
+
+	public List<String> getOpParams() {
+		return formalParams.stream().map(f -> f.getName().toString()).collect(Collectors.toList());
+	}
+
+	public List<FormalParamNode> getFormalParams() {
+		return formalParams;
+	}
+
+	public List<String> getUnchangedVariables() {
+		return unchangedVariables.stream().map(u -> u.getName().toString()).collect(Collectors.toList());
+	}
 }
 
 class PrimedVariablesFinder extends AbstractASTVisitor {
diff --git a/src/main/java/de/tla2b/translation/OperationsFinder.java b/src/main/java/de/tla2b/translation/OperationsFinder.java
index c04bfba..5a9d77f 100644
--- a/src/main/java/de/tla2b/translation/OperationsFinder.java
+++ b/src/main/java/de/tla2b/translation/OperationsFinder.java
@@ -22,6 +22,7 @@ public class OperationsFinder extends AbstractASTVisitor {
 	private List<OpApplNode> exists;
 	// a list containing all existential quantifier which will be parameters in the resulting B Machine
 	private final List<BOperation> bOperations;
+	private final List<String> generatedOperations = new ArrayList<>();
 
 	public OperationsFinder(SpecAnalyser specAnalyser) {
 		this.specAnalyser = specAnalyser;
@@ -55,7 +56,7 @@ public class OperationsFinder extends AbstractASTVisitor {
 	public void visitUserDefinedNode(OpApplNode n) {
 		OpDefNode def = (OpDefNode) n.getOperator();
 		if (BBuiltInOPs.contains(def.getName())) {
-			bOperations.add(new BOperation(def.getName().toString(), n, exists, specAnalyser));
+			addOperation(def.getName().toString(), n, exists, specAnalyser);
 			return;
 		}
 
@@ -73,32 +74,18 @@ public class OperationsFinder extends AbstractASTVisitor {
 		int opcode = BuiltInOPs.getOpCode(opname);
 		DebugUtils.printDebugMsg("OPCODE of " + opname + " = "+ opcode);
 		switch (opcode) {
-			case OPCODE_dl: { // DisjList: split action further
+			case OPCODE_dl: // DisjList: split action further
 				if (n.getArgs().length == 1) {
 					visitExprOrOpArgNode(n.getArgs()[0]);
 				} else {
-					String oldName = currentName;
-					List<OpApplNode> oldExists = new ArrayList<>(exists);
-
-					for (int i = 0; i < n.getArgs().length; i++) {
-						exists = new ArrayList<>(oldExists);
-						currentName = oldName + i;
-						visitExprOrOpArgNode(n.getArgs()[i]);
-					}
+					visitArgs(n);
 				}
 				return;
-			}
-			case OPCODE_lor: { // logical or: split action further
-				String oldName = currentName;
-				List<OpApplNode> oldExists = new ArrayList<>(exists);
-
-				for (int i = 0; i < n.getArgs().length; i++) {
-					exists = new ArrayList<>(oldExists);
-					currentName = oldName + i;
-					visitExprOrOpArgNode(n.getArgs()[i]);
-				}
+
+			case OPCODE_lor: // logical or: split action further
+				visitArgs(n);
 				return;
-			}
+
 			case OPCODE_be: { // BoundedExists
 				exists.add(n);
 				visitExprOrOpArgNode(n.getArgs()[0]);
@@ -119,24 +106,45 @@ public class OperationsFinder extends AbstractASTVisitor {
 			case OPCODE_in: // \in
 			case OPCODE_notin: // \notin
 			case OPCODE_subseteq: // \subseteq - subset or equal
-			case OPCODE_fa: // $FcnApply f[1]
+			case OPCODE_fa: // $FcnApply f[1] FIXME: how can FcnApply be an action?
 			case OPCODE_ite: // IF THEN ELSE
-			case OPCODE_case: {
+			case OPCODE_case: // CASE p1 -> e1 [] p2 -> e2
+			// TODO case OPCODE_aa: // <<A>>_e
+			// TODO case OPCODE_sa: // [A]_e
 				// no further decomposing: create a B operation
-				bOperations.add(new BOperation(currentName, n, exists, specAnalyser));
+				addOperation(currentName, n, exists, specAnalyser);
 				return;
-			}
 		}
-		//System.out.println("OPCODE of " + opname + " = "+ opcode);
 
 		if (opname == BBuildIns.OP_false || opname == BBuildIns.OP_true) {
 			// FALSE: always disabled; TRUE: CHAOS
-			bOperations.add(new BOperation(currentName, n, exists, specAnalyser));
+			addOperation(currentName, n, exists, specAnalyser);
 			return;
 		}
 		throw new RuntimeException(String.format("Expected an action at '%s':%n%s", opname, n.getLocation()));
 	}
 
+	private void visitArgs(OpApplNode n) {
+		String oldName = currentName;
+		List<OpApplNode> oldExists = new ArrayList<>(exists);
+
+		for (int i = 0; i < n.getArgs().length; i++) {
+			exists = new ArrayList<>(oldExists);
+			currentName = oldName + i;
+			visitExprOrOpArgNode(n.getArgs()[i]);
+		}
+	}
+
+	private void addOperation(String name, OpApplNode node, List<OpApplNode> exists, SpecAnalyser specAnalyser) {
+		if (!generatedOperations.contains(name)) {
+			bOperations.add(new BOperation(name, node, exists, specAnalyser));
+			generatedOperations.add(name);
+			return;
+		}
+		DebugUtils.printMsg("Duplicate operation not translated: " + name);
+		// TODO: handle fixed parameters of an action definition, e.g. Act1(2) /\ Act1(2)
+	}
+
 	public List<BOperation> getBOperations() {
 		return bOperations;
 	}
-- 
GitLab