Homework 12 and 13: the last homework


This homework comes in three parts:

  • lambdas: exercises of varying difficulty that allow you to practice using C++ lambdas. It comes with several samples that you can work from to come up with your solutions. Two exercises are required, two are BONUS exercises.

  • circular: two exercises that have you make a queue using a (resizeable) circular buffer, but where the underlying storage is a std::vector. This comes with my solution the the last homework. You can either modify mine, or your own, to make your class. As a BONUS, you can write this code as a template class.

  • syntaxTree: this gives an example of a class hierarchy describing an arithmetic expression over integer variables. Your job is to invent a similar hierarchy for boolean expressions over condition variables. You can think of these classes as providing a syntax tree for such expressions. These use C++ smart pointers.

In addition to these, there are three folders with BONUS exercises for you to take a look at, should you be interested in working on them. These were drafts of projects that I had hoped to assign, but they relied on modern C++ that we only reached just after Thanksgiving. Instead, they are BONUS exercises that you may tackle, or at least take a look at as FYIs.

  • logicSim: a single program assignment where you are to finish the code of a program that simulates circuits that don’t have feedback. A few pieces of code need to be completed for it to work.

  • cobra: an interpreter for a programming language Cobra that is very similar to Python. It only allows calculations on integers, and output of either integers or of text. The syntax is like Python’s but, rather than relying on indentation to denote block of code, relies on begin...end keywords that mimic the curly braces of C++ ({...}). You can add new features to the language by writing methods for how they execute and compute values. Execution and evaluation involves traversing a syntax tree built from parsing a program’s code.

  • cobrac: a compiler for Cobra that outputs MIPS code that can be executed in SPIM. You can add new features to the language by writing methods for converting a syntax tree of any new construct into lines of MIPS code.


Part 1: Lambdas

Here I ask you to practice writing functions using the functional component of the C++ Standard Template Library, particularly its definition of a “lambda” construct. In C++ you can express an anonymous function with the expression:

[captures](parameters) -> return-type { body }

In the above, the body and the parameters look as they might in a normal function definition. For example, this function computes the integer whose digits are the two given integers:

[](unsigned int tens, unsigned int ones) -> unsigned int { return tens*10+ones; }

Note that the return-type is just the type that would normally sit at the start of a normal function definition. The captures is a list of variables within the same scope of the lambda expression that are used within the body. They can either be “captured” by value (i.e. by copy), in which case only the variable name is listed, or by reference, in which case the variable name is listed with an & symbol at its front. As an example, consider the following:

std::vector<int> v {10,3,1,19};
int i = 3;
std::function<bool(int)> f = [&v,i](int j) -> bool {return v[i]==v[j];};
i--;
std::cout << f(3) << std::endl;

This code will output 1 (for true) because f checks whether the value of v at the index of its argument is the same as the value v[3]. This is because i is passed by value, and so v[i] means v[3] at the time that f is expressed. If the third line was instead:

std::function<bool(int)> f = [&v,&i](int j) -> bool {return v[i]==v[j];};

then the output f(3) would instead be 0 (for false) because f would be checking v[2] on the left side of the ==. We would need to call f(2) instead to get the result true.

Note the std::function type of the lambda in the declaration of f above. In general, if a lambda takes a series of arguments of type T1, T2, up to Tk and returns something of type T, then its type is

std::function<T(T1,T2,...,Tk)>

It is also possible for a lambda to “mutate” the state of variables captured by value in its capture list by including the keyword mutable before the arrow to the return type. For example,

int sum = 0;
std::function<int(int)> accum = 
  [sum](int v) mutable -> void {sum += v; return sum;});     
std::cout << accum(7) << std::endl;
std::cout << accum(30) << std::endl;
std::cout << accum(500) << std::endl;

This will output three lines with 7, 37, and 537. This is because, though sum is copied, the sum variable for accum is allowed to be changed because of the mutable keyword. The variable sum outside the lambda stays at value 0, but the one created by the capture changes.

Here is another example of capture by reference:

std::vector<int> v {10,3,1,19};
int sum = 0;
std::for_each(v.begin(),v.end(),[&sum](int e)->void{sum += e;});
std::cout << sum << std::endl;

The third line above feeds a lambda to the algorithm package’s for_each construct. This calls our lambda on each element value e of the vector v. Since sum is captured by reference, the += operation bumps the value of sum by the value of each element of v. This results in the output of 33 by the code.

This example highlights the common use of lambda expressions: they are a convenient way of working with the algorithms component of the C++ STL. Incidentally, the lambda expression isn’t entirely necessary for using this library. For example, we could have instead developed this class

class Summer {
private:
    int& _sum;
public:
    Summer(int& s) : _sum{s} {}
    void operator()(int e) { _sum += e; } 
};

which overloads the “call” operator. It has an “int reference” attribute. With this class definition, we could have instead written

std::for_each(v.begin(),v.end(),Summer {sum});

to obtain that same output of sum of 33.

This class definition suggest the work done by C++ behind the scenes to develop every anonymous function object expressed, semantically speaking.

Part 1 Exercises

For each of these, write testing code in a main inside a C++ source file named lambdas/exN.cc, where N is the exercise number. The code can be similar to the lambdas/sampleN.cc files I’ve included in this repo.

Note: The last two of these are somewhat tricky, especially in getting the type information correct. They are BONUS exercises.

Exercise 1.1.
Write and test the use of a lambda that computes the square of its integer parameter. It shouldn’t capture any variables. Show it working on several different parameter inputs. For example, when given 9, it should return 81.

Exercise 1.2.
Write a lambda that captures a local variable int base and a local variable that’s a vector digits of integers. That vector should have elements that are only 0s and 1s. It should take no parameters.

When called, it should output to std::cout a line with a single integer to the console. That value should be the integer formed from that integer sequence interpreted as digits in that base.

For example, if the base is 2 and the digits vector was initialized with the sequence

{1,0,1,1,1}

that lambda should output 23 because, interpreting those as bits from most- to least-significant, the lambda would calculate the number whose binary sequence is 10111. (23 is just 16 + 4 + 2 + 1).

If the base were changed to 10, then calling that same lambda object should output 10111. If we then called pop_back on the digits vector, then that same lambda object should then output 1011. If, after that, we changed the base back to 2, then it would ouput 11. (11 is just 8+2+1).

Note that the type of this lambda is std::function<void()>. It takes no parameters and returns no result. It outputs to std::cout instead.

BONUS: Exercise 1.3.
Write a lambda that takes a parameter f that is a function of type int(int), and a second parameter int x. Its capture list should be empty. When that lambda object is called, it should apply f twice to that given x and return that result of type int.

Test it with the squaring function you wrote in Exercise 1. For example, if you wrote the squaring lambda by giving it the name sqr, and then wrote this lambda giving it the name apply_twice, then

std::cout << apply_twice(sqr,3) << std::endl;

should lead to output of 81 because the square of the square of 3 is 81.

You can also demonstrate it by writing an “add 10” function lambda. In that case,

std::cout << apply_twice(add10,3) << std::endl;

should output 23.

BONUS Exercise 1.4.
Write a lambda that takes a parameter f that is a function of type int(int). It should take no other parameters. It should instead give back a function of type int(int). In other words, the body of the lambda should return a lambda!

Name this lambda object twice, and demonstrate it by creating several functions. For example, you could redo the work of testing Exercise 3 and try out lines like the following:

std::function<int(int)> quad = twice(sqr);
std::function<int(int)> add20 = twice(add10);

and then test these functions on several integer parameters.

Hint: You may have to pass arguments by reference, and you may have to capture variables by reference. Think it through!


Part 2: circular buffer as a vector

In the last homework, you were asked to devise a queue data structure that was represented as a fixed-capacity array of integers. It took a capacity in its constructor and then allocated a C array of integers on the heap of that size. I’ve included my solution for this exercise as a folder named hw12soln.

Part 2 Exercise

Excercise 2
Modify this solution’s code, or else the solution that you wrote, so that it instead uses a std::vector<int> to store the data of the queue rather than a C array. There should be one additional feature: when too many data items have been placed into the queue with enqueue, the underlying std::vector should be resized to twice its capacity, and then items should be moved around accordingly to preserve the “circular structure”, and then that extra item should get enqueued.


Part 3: syntax trees for boolean conditions

I’ve included a sub-folder named syntaxTree in this repo. It contains three files Arithmetic.hh, Arithmetic.cc, and arith.cc. The first of these defines a class hierarchy for arithmetic expressions, expressions like

((8 + (-y)) * (x + z))

These are represented as a tree of subexpressions that could be depicted like so:

            |
            |
          Times
          /   \
         /     \
        /      Plus
       /       /  \
      /       /    \
    Plus   LookUp LookUp
    /  \     "x"    "z"
   /    \
Number Negation
  8       |
        LookUp
      "y"

The components of the tree are all pointers to objects that are all derived from the subclass Expression, namely Plus, Times, Negation, Number, and Lookup. At the top we have a pointer to a Times object that has pointers to a left subtree built as a Plus object. That, in turn, has a pointer to a Number object, holding the value 8, and to a Negation object. The latter has a pointer to a LookUp object for the variable named y. This whole left subtree from the Times root corresponds to the subexpression left of the * sign:

(8 + (-y))

The right subexpression past the * sign

(x + z)

corresponds to the right subtree under Times with subtree objects Plus and Lookup.

The Arithmetic.hh file defines all these syntax tree subclasses:

• class Plus: a syntax tree corresponding to additions.
• class Times: a syntax tree corresponding to multiplications.
• class Negation: a syntax tree corresponding to a negated subexpression.
• class Number: a syntax tree for a constant integer value.
• class LookUp: a syntax tree referencing a variable’s value.

In addition to their constructors, these all support two methods:

• std::string to_string(): this converts a syntax tree to a string of its arithmetic expression.
• int evaluate(variables): this method evaluates the syntax tree using

an unordered_map of variables and their assigned values. These methods are all defined in Arithmetic.cc. There is a client arith.cc that builds the expression tree for the above example, prints it out using to_string, and then evaluates it using evaluate on a dictionary of containing values of x, y, and z. They are set to 2, 3, and 4, respectively.

It can be compiled and run with the two command lines:

g++ -std=c++11 -o arith arith.cc Arithmetic.cc 
./arith                                        

You’ll get the following output:

With x set to 2, y set to 3, and z set to 4,
the value of ((8 + (-y)) * (x + z)) is 30.

For the exercise, I’m asking you to mimic what I’ve done, but to instead handle logical (boolean) expressions over variables that are either set true or false.

Part 3 Exercise

Exercise 3: make a similar class hierarchy as files named Condition.hh and Condition.cc. Instead of arithmetic expressions, it should define syntax trees for boolean conditions on boolean variables.

• class Or: a syntax tree corresponding to boolean disjunction.
• class And: a syntax tree corresponding to boolean conjuction.
• class Not: a syntax tree corresponding to a boolean negation.
• class TruthValue: a syntax tree for a constant boolean value, either true or false.
• class Variable: a syntax tree referencing a condition variable’s truth value.

These should all be subclasses of an abstract superclass named Condition. In addition to their constructors, they each support the methods:

• std::string to_string(): this converts a syntax tree to a string of its arithmetic expression.
• bool check(variables): this method determines whether or not the syntax tree is true given an assignment of truth values to the given variables map.

Also write a sample client condi.cc that builds sample boolean condition trees and checks whether or not they are true or false.


BONUS Part 4: a Logic Simulator

The logicSim folder contains a nearly implemented combinatorial circuit simulator named logicSim. It can be compiled with the command:

g++ -o logicSim -std=c++17 Gate.cc Circuit.cc logicSim.cc

When run with a specified filename, it reads a text file that specifies the components and wiring of a circuit. When your complete code is written, it should output the truth table for that circuit. The folder logicSim/circuits contains several sample circuit files

• xor.lsc: a two input XOR circuit
mux4.lsc: a 6 input circuit that behaves like a 4:1 MUX
• inc.lsc: the next state logic for a 3-bit cyclic counter
• dec2_4.lsc: a 2:4 decoder circuit

For example, running the command

./logicSim circuits/xor.lsc

will read that file, add all the components to a circuit, wire them together, and then attempt to print its truth table to the console. The same folder contains sample output of my solution run on each of these circuit files. For example the file circuits/xor.out is the result of my solution running on circuits/xor.lsc.

The code, as it stands, defines a main in LogicSim.cc. That code builds an instance of class Circuit from that input file, as defined in the files Circuit.cc and Circuit.hh. A circuit is just made up of a collection of Gate objects, a combination of Input, And, Or, Not, and Output objects, linked together, from the circuit’s outputs, through the logic gates, back to the circuit’s inputs, using shared_ptr<Gate> links. There are also symmetric weak_ptr<Gate> links that go from the circuit’s inputs, through the logic gates, to the circuit’s outputs. The components are all defined in Gate.cc and Gate.hh.

The sample files do not specify circuits with “cycles”, that is, no gate’s output eventually feeds its way back into itself as input. They are all combinatorial circuits because there areno cycles.

Part 4 BONUS Exercise

There is more to tell you about the whole implementation, but first I’d like to summarize this bonus assignment: You are to complete one method of the circuit class, namely the method:

• Circuit::evaluate: This should work through the components of a circuit, setting the outputs based on the logic components’ behavior and connectivity, from some set of input settings.

This code is used by the method Circuit::outputTruthTable. It cycles through all the possible input settings, evaluates the circuit for each, and then outputs each as a row of a well-formatted truth table.

A lot of code is written for you. The bulk of the your assignment is actually to read through the existing code, understand it, and use it to write the code for this evaluate method in Circuit.cc.

Some notes on the circuits code

Gates
There are five different component types, each a subclass of class Gate. They share the following methods and attributes:

• name(): a string identifying the component from the file
• reset(): gets the component ready to be evaluated
• evaluate(): the rule for computing the output of the component from its input(s).
• _inputs: wired links to other components that feed into this component
• _outputs: wired links to other components that are fed this component’s output
• _outputValue: this is the value computed by evaluate and is read by the method outputValue.
• _outputReady: indicates whether this component has had its evaluate method called yet, since reset. This is used by the outputIsReady() method.
_numberOfInputsReady: a count of how many of this component’s input components have had their evaluate method called. This is used by the allInputsAreReady() method.
• _signalReady(): method that is called at the end of evaluate, alerting subsequent components

Here are the differences amongst the component types:

And: outputs true only if all of its input components are feeding true. Can have 0 or more inputs fed to it.

Or: outputs true only if at least of its input components is feeding true. Can have 0 or more inputs fed to it.

Not: should only have one fed input. Inverts that to produce its output.

Input: should have no fed inputs. Instead, has a method setValue to set its value to true or false.

Output: should feed its output nowhere and should have only one fed input.

For all of them, evaluate computes the component’s rule for producing its output, sets its _outputValue, and then signals that evaluation’s completion by decrementing _numberOfInputsReady for each of the components that are fed by it.

Computing with a Circuit
You are asked to write Circuit::evaluate, the code for evaluating all the components of a circuit. Before describing that method’s algorithm below, first it’s best to understand the steps incolved in computing with a circuit.

To compute with a Circuit c, code is expected to follow these four steps:

  1. Call c.reset(). This “clears” all the state of each of the circuit’s components, specifically by having them indicate that their outputs aren’t “ready”, and thus that none of their fed inputs are ready either.

  2. Call c.setInputsFrom(ibv) with some vector ibv of type vector<bool>. It sets each of the Input components of c to true or false based on a vector<bool> with inputSize() elements. Make sure that ibv.size() is the same as c.inputSize(). This call gets the inputs ready for the next step.

  3. Call c.evaluate(). This should simulate the circuit and ultimately set the outputs. You need to write this code.

  4. Call c.readOutputsInto(obv) with some vector obv of type vector<bool>. Make sure its length is c.outputSize(). This reads the outputs of the circuit into that vector.

Your code for c.outputTruthTable should repeatedly perform steps 1 through 4, once for each different set of boolean inputs.

The algorithm for Circuit::evaluate()
There are several ways you can write your code for evaluate. All of the ways should, in essence, traverse the “network” of wired components, determining outputs of the circuit based on the inputs to the circuit.

Here is a standard description of one such evaluation scheme: keep track of which components have all their fed inputs “ready”, and haven’t yet been evaluated. Pick one such gate g that is ready in this way, and call g.evaluate() on it. This, presumably, will make other components ready. Keep repeating this until all the components have been evaluated. Note that this process can be started by choosing any of the Input components since they have no fed inputs. Because their _inputs vector has size 0, then allInputsAreReady will be true for them.

Other methods can involve performing a search through the circuit, either by working forward from the inputs, evaluating those, then evaluating the components they feed into, etc, until the circuit’s output values have all been determined.

Another possibility is to make this search a recursive one. You start with some output, look at what wire feeds into it, and figure out the output of each such gate. To do that, you can recursively figure out the output of each of the gates that produce a signal of some wire feeding into it. Continuing this way, you reach the inputs, terminating that particular recursive call of the backward search immediately. NOTE: if you do choose this backwards recursive method, you must write it in such a way that each backwards-reachable component of the circuit is evaluated exactly once.


BONUS Part 5: a Cobra interpreter

In the folder named cobra I’ve given a full suite of C++ source files that define tools for executing programs written in the Cobra programming language. The language is very similar to Python, shares much of the basic syntax, except it is limited in that it only computes with integers. It has no other data types, though text strings can be output. The syntax also does not rely on indentation.

Here is a sample Python program that outputs the positive divisors of an integer:

def mod(number,divisor):
  while number < 0:
    number = number + divisor
  while divisor <= number:
    number = number - divisor
  return number

def divides(d,n):
  if ((not (d == 0)) and (mod(n,d) == 0)):
    return 1
  return 0

value = int(input("Enter a number: "))
candidate = 1
while not (value < candidate):
  if divides(candidate,value) == 1:
    print(candidate)
  candidate += 1

The code consists of two function definitions mod and divides followed by a “main” script sitting at the bottom.

Here is the equivalent Cobra program (also given as a file samples/divisors.cob):

def mod(number,divisor):
begin
  while number < 0:
    number = number + divisor
  while divisor <= number:
    number = number - divisor
  return number
end

def divides(d,n):
begin
  if ((not (d == 0)) and (mod(n,d) == 0)):
    return 1
  return 0
end

begin
  value = input("Enter a number: ")
  candidate = 1
  while not (value < candidate):
  begin
    if divides(candidate,value) == 1:
      print(candidate)
    candidate += 1
  end
end

The key difference you’ll see is the explicit way that blocks of code, several lines long, need to be marked with a begin followed with an end. This is needed for function bodies that are a sequence of more than one program statement (the divides functionhas two statements: anifand areturn). It is also needed forif-elseorwhilebodies that are sequences of more than one statement (thewhileloop in main is two statements: anifand an increment; the main script has three statements: an input assignment, an initialization assignment, and awhile` loop). This is because Cobra, unlike, Python, does not rely on indentation (for better and for worse).

There is a lot of code provided in this folder. The Makefile builds to different executables. One of these cobrac is actually used for the sixth part of this homework. We’ll talk about this later when we describe BONUS Part 6. The Makefile also describes the construction of an executable named cobra. This is the Cobra interpreter. If you compile it using make, you can then run Cobra programs, including the ones in the samples folder. For example, you can run

make cobra
./cobra samples/hello.cob

and it should build the Cobra interpreter, and then run it on the Cobra version of the Hello, world! program.

The bulk of the files provided in this folder are related to the parsing of Cobra source files (ending in .cob) and building a syntax tree data structure that represents the function definitions and main script of the Cobra program it processes. These are essentially the same as the work you did for Part 3 of this homework. The syntax tree hierarchy is defined in cobra-ast.hh and consists of subclasses of these classes:

class Prgm: a Cobra program; contains the syntax trees for each function definition • class Defn: the syntax tree for a function definition’s code.
class Stmt: the syntax tree for every possible statement allowed in Cobra.
class Expn: the syntax tree for every possible arithmetic expression allowed in Cobra.
class Cndn: the syntax tree for boolean expressions used in Cobra if and while.

These each rely heavily on std::shared_ptr to represent the structure of a parsed Cobra program’s code.

To run a Cobra program, the cobra executable essentially traverses the syntax tree of the main script code, executing its program statements line by line. When a function is called (either as a procedure or to compute a value), that function’s definition is looked up, and its syntax tree is then traversed with a set of passed parameters. Cobra statements can modify the variables of the main script, or of a running function. Cobra arithmetic and condition expressions can look up the values of those variables. All this work is handled by these major functions:

Prgm::run: run the main script of a Cobra program.
Defn::call: execute a Cobra function definition with some passed parameters.
Stmt::exec: execute a Cobra statement, possibly modifying (local) variables.
Expn::eval: evaluate a Cobra arithmetic expression, yielding an integer value.
Cndn:chck: check whether a Cobra boolean condition is true or false.

Take a look at these methods for each subclass in cobra-ast.cc.

Part 5 BONUS Exercises

Exercise 5.1: if
Write the code for IfTn::exec which executes if statements that don’t contain an else:. You can mimic what was written for IfTE::exec for statements if statements that have an else:.

Exercise 5.2: +=
Write the code for Updt::exec which executes statements that update a variable by incrementing them by an expression. For example, the Cobra statement

x += i*i

is an example of an Updt term that adds the square of i to the variable x.

Exercise 5.3: -
Write the code for Mnus::eval which evaluates subtraction expressions like

y - 35

You can look at my code for Plus::eval and Tmes::eval which handle addition and multiplication.

Exercise 5.4: <=
Write the code for LEql::chck which checks comparison conditions like

y - 35 <= 2 * x

You can look at my code for Less::chck and Eqls::chck which handle “less than” and equality checking.

Exercise 5.5: or
Write the code for OrCn::chck which checks distunctions of conditions like

(y < 35) or (y == 35) 

You can look at my code for AndC::chck which handles conjunctions.

Exercise 5.6: for
Write ForL::exec to execute Cobra for loop statements with the syntax

for <name> = <start> to <end>: <body>

Here name is a variable name, start and end are integer expressions, and body is a statment (or sequence of statements) that gets executed once each time the for loop counts upward. For example the Cobra program

begin 
  sum = 0
  for count = 1 to 10:
  begin
    print("count")
    sum = sum + count
  end
  print(sum)
end

will print the text count on 10 lines, followed by the value 55.


BONUS Part 6: a Cobra compiler

The folder named cobra also contains C++ source code for building a compiler cobrac. This program takes Cobra programs (like hello.cob) and produces their equivalent in MIPS assembly that you can run in SPIM. If you type these commands

make cobra
./cobrac samples/hello.cob
./spim -f samples/hello.s

then, assuming you’ve put spim and its support files in this folder, this will compile and run a MIPS program named hello.s built from the Cobra source code hello.cob.

The compiler relies on the same parsing infrastructure used by the interpreter cobra in Part 5. But, rather than calling Prgm::run on the parsed Cobra program, it instead runs Program::compile to produce the appropriate .s MIPS assembly file. The compiler makes several passes over the source code:

• PASS #0: read and parse the Cobra source program, converting its function definitions and main script into a collection of syntax trees describing their code. This is the same as what is done by cobra in Part 5 of this homework.

• PASS #1: convert the statements and expressions of the syntax tree of the Cobra program into sequences of “pseudo-instructions.” These pseudo-instructions are subclasses of the class INST defined in cobra-assem.hh. They are almost exactly MIPS assembly instructions except, rather than acting directly on MIPS processor registers, they instead act on temporary variables. This allows functions to have essentially an infinite supply of storage values to operate on (rather than on a limited number of registers).

• PASS #2: read through the sequences of pseudo-instructions for each Cobra function definition, assigning each temporary variable a slot in a stack frame for that function.

• PASS #3: read again through the sequences of pseudo-instructions, writing them out as a series of MIPS assembly instructions. These snippets of MIPS code will load registers with values taken from the stack frame, act on those registers to modify a register, then store that result back into the stack frame.

The code for PASS #0 is complicated. It sits primarily in cobra-scanner.cc and cobra-parser.cc. These are not written directly by me. Rather they are constructed from cobra-scanner.ll and cobra-parser.yy using the Unix compilation tools Flex and Bison.

The code for PASS #1 is distributed throughout the code of cobra-assem.cc as the assemble methods of subclasses of Stmt, the assembleToSet methods of subclasses of Expn, and as the dual assembleToBranchIfTrue and assembleToBranchIfFalse methods of subclasses of Cndn. These are what you’ll be asked to extend in the exercises below.

The code for PASS #2 is the method Assembly::frameVars that traverses the assembled pseudo-instructions of a Cobra program. It relies on each INST subclass defining a frameVar for its pseudo-instruction.

Finally, the code for PASS #3 is distributed throughout cobra-assem.cc by defining a toMIPS method for each of the pseudo-instructions that are subtypes of INST.

Part 6 BONUS Exercises

Exercise 6.1-6.6 Write the appropriate “assemble” methods for each of the Cobra syntactic constructs listed as exercises in Part 5. This involves performing the work of PASS #1 for each of them. You’ll generate pseudo-instructions (objects who are a subtype of INST) for each of them, appending them to the given Assembly object.