ANML API Tutorials

Introduction

This section describes step-by-step how to use the programmatic ANML APIs to create automata. Each tutorial will describe a new set of APIs, with each tutorial building on the last. Each will end with compiling the ANML object to an automaton, saving the automaton, then instruction on how to use the emulator to test it.

List of tutorials

Each tutorial is contained in its entirety as a project in the SDK. Although error checking is vital for a real application, it also complicates the code and can obscure the core functionality. For this reason, the code shown in the tutorials below will not contain the same error checking code that is present in the projects. As well, the details of saving automata files is omitted in the tutorials but present in the corresponding projects.

The tutorial projects are installed with the SDK at the following locations:

  • Windows: By default, the tutorial projects are installed at C:/Program Files/Micron Automata Processor SDK/examples/C/tutorials/anml_api. In order to build the projects where they are installed, you need to have administrator rights. It is suggested that you copy the tutorial projects somewhere other than C:/Program Files, in order to remove this requirement.
  • Linux: By default, the tutorial projects are installed at /usr/share/micron/ap/examples/C/tutorials/anml_api. In order to build the projects where they are installed, you need to have root privileges. It is suggested that you copy the tutorial projects somewhere in your home directory, in order to remove this requirement.

How to create a simple Automata Network

This page describes how to create a simple Automata Network.

An Automata Network is also known as an ANML "application," which describes the highest level of an automaton. Here, we describe how to create an automaton that matches "abc".

First, you must create the top-level container of all ANML objects, the "anml" object. The value that is returned is a reference to the anml object.

1 anml = Anml()
Anml anml = new Anml();

Next, you must create an Automata Network object in the anml object. The value that is returned is a reference to the automata network. The third argument is the name of the automata network. It needs to be specified so that reporting element IDs will be included in the element map that is created when the ANML object is compiled. The element map translates element references to element IDs for use in identifying which elements report matches. More on this later.

AP_CreateAutomataNetwork(anml, &anmlNet, "an1");
1 anmlNet = anml.CreateAutomataNetwork('an1')
AnmlNetwork anmlNet = anml.createAutomataNetwork("an1");

At this point, you need to create the AP resources, or "elements" needed to form the automaton. An element can be an STE, counter, or boolean. The element structure is loaded with the type of resource desired along with the specifics of the resource. Let's start with an element that matches the character, "a". We first clear out the element members, then we specify that the element type is an STE, it will start matching at the start of data, it matches the character "a" and it is not a matching element. The value that is returned is a reference to the element.

struct ap_anml_element element;
memset(&element, 0, sizeof(element);
element.res_type = RT_STE;
element.start = START_OF_DATA;
element.symbols = "a";
element.match = 0;
AP_AddAnmlElement(anmlNet, &element_a, &element);
1 element_a = anmlNet.AddSTE('a', startType=AnmlDefs.START_OF_DATA)
ElementRef element_a = anmlNet.addSTE("a", AnmlDefs.START_OF_DATA);

We will now create an element that matches the character "b", but this element is not a starting element; instead it will be activated from the previous element, above. We will see how that works a bit later.

element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "b";
element.match = 0;
AP_AddAnmlElement(anmlNet, &element_b, &element);
1 element_b = anmlNet.AddSTE('b')
ElementRef element_b = anmlNet.addSTE("b");

The last element to create is one that not only matches the character "c" but also informs us that a match of the string "abc" has been made, in other words, although this is not a starting element, it is a matching element. As mentioned earlier, in order to be able to identify this element when it reports a match, it needs an ID. We'll use "ste3".

element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "c";
element.match = 1;
element.id = "ste3";
AP_AddAnmlElement(anmlNet, &element_c, &element);
1 element_c = anmlNet.AddSTE('c', anmlId='ste3', match=True)
ElementRef element_c = anmlNet.addSTE("c", AnmlDefs.NO_START, "ste3", true);

Now that we have created all of the elements, we need to connect them together to form the automaton. This involved the creation of "edges", which refer to the edges of the graph that is the automaton. And edge is connected between 2 elements. We use the element reference that were returned when the element were created. First, let's connect the STE that matches "a" (which is the starting element – in this case it starts matching at the beginning of data) with the STE that matches "b". For now, don't worry about the last argument of the edge creation API – just set it to 0.

AP_AddAnmlEdge(anmlNet, element_a, element_b, 0);
1 anmlNet.AddAnmlEdge(element_a, element_b, 0)
anmlNet.addAnmlEdge(element_a, element_b, 0);

That's all there is to creating an edge, since the elements themselves have already been created. Now, let's complete the creation of the automaton by creating an edge from the STE that represents "b" to the one that represents "c". Remember that the last STE will report that a match has been made if the input data contains the string, "abc".

AP_AddAnmlEdge(anmlNet, element_b, element_c, 0);
1 anmlNet.AddAnmlEdge(element_b, element_c, 0)
anmlNet.addAnmlEdge(element_b, element_c, 0);

At this point, the automaton has been described in the anml object, so we need to compile the anml object to create the automaton that is used to program the device or be used by the emulator to perform matches. A reference to the resulting automaton is returned in the second argument. A reference to the element map, used to map from element references to their IDs, is returned in the third argument. For now, we'll specify default values for the rest of the arguments.

ap_element_map_t elementMap;
AP_CompileAnml(anml, &fsm, &elementMap, 0, 0, 0, 0);
1 fsm,elementMap = anml.CompileAnml()
Pair<Automaton,ElementMap> pair = anml.compileAnml(0);
Automaton fsm = pair.first;
ElementMap elementMap = pair.second;

In order to test the automaton, we'll save it and its corresponding element map to files that can be used by the command-line emulator application to test the automata. Each file must be described by a file descriptor object, file_descriptor_t, provided by the SDK, which is created differently in Windows and Linux.

#include <micron/ap/sys/platform.h>
file_descriptor_t automataFd, elementMapFd;
automatonFd = open_file("simple_automata_network.fsm");
elementMapFd = open_file("simple_automata_network.map");
AP_Save(fam, automataFd);
AP_SaveElementMap(elementMap, elementMapFd);
1 fsm.Save('simple_automata_network.fsm')
2 elementMap.SaveElementMap('simple_automata_network.map')
fsm.save("simple_automata_network.fsm");
elementMap.saveElementMap("simple_automata_network.map");

Finally, we need to clean up by destroying the automaton, anml, and element map objects. This will release the resources used to hold the objects.

1 # force call of destructor
2 fsm, anml, elementMap = (None, None, None)
// force call of destructor
fsm = null;
elementMap = null;

In order to test the automaton, we'll use the AP Emulator that comes with the SDK. The -m argument specifies the element map, the second argument is the automaton, and the third argument is the test data. Here we attempt to match the data string, "abc", which will occur at offset 3 in the string, and the element that matches will have an ID of "ste3" as we specified above.

1 >apemulate -m simple_automata_network.map simple_automata_network.ap abc
2  Match result:
3  Offset 3 Reporting element: an1.ste3

The automaton was designed to start its search at the beginning of the data. This is known in the regular expression world as being "anchored." If we try to match data that does not start with 'a', we will get no matches.

1 >apemulate -m simple_automata_network.map simple_automata_network.ap xabc
2  No match.

There is no such restriction at the end of the data, so if we try to match data that has characters after "abc", we'll again match at offset 3 with element "ste3."

1 >apemulate -m simple_automata_network.map simple_automata_network.ap abcdefghi
2  Match result:
3  Offset 3 Reporting element: an1.ste3

How to create and use a simple macro

An ANML macro is a template of an automaton. It's important to understand that a macro is NOT an automaton in and of itself. In fact, it needs to be instantiated into an actual automaton by using a macro reference. We'll explain how all of this works in this page. Here, we describe how to create a macro that matches "abc", then create an automaton network that will contain an instance of this macro, so that the overall effect will be the same as the previous automaton network example.

Create the macro definition in an anml object as you did before with the automata network. With a macro definition, you must give it a name, in this case "md1". The name will be used later on when we instantiate the macro. The value that is returned is a reference to the macro definition.

ap_anml_t anml = 0;
ap_macro_def_t macroDef;
anml = AP_CreateAnml();
AP_CreateMacroDef(anml, &macroDef, "md1");
1 anml = Anml()
2 macroDef = anml.CreateMacroDef('md1')
Anml anml = new Anml();
MacroDef macroDef = anml.createMacroDef("md1");

Now, create the same content of the macro definition in the same way we did for the automaton network. This time, however, we'll use the reference to the macro definition instead of the automaton network, to create the content in the macro definition. Notice again that we specified a name for the element in the macro that will report a match.

ap_anml_element_ref_t element_a, element_b, element_c;
struct ap_anml_element element;
memset(&element, 0, sizeof(element);
element.res_type = RT_STE;
element.start = START_OF_DATA;
element.symbols = "a";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_a, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "b";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_b, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "c";
element.match = 1;
element.id = "ste3";
AP_AddAnmlElement(macroDef, &element_c, &element);
AP_AddAnmlEdge(macroDef, element_a, element_b, 0);
AP_AddAnmlEdge(macroDef, element_b, element_c, 0);
1 element_a = macroDef.AddSTE('a', startType=AnmlDefs.START_OF_DATA)
2 element_b = macroDef.AddSTE('b')
3 element_c = macroDef.AddSTE('c', match=True)
4 macroDef.AddAnmlEdge(element_a, element_b, 0)
5 macroDef.AddAnmlEdge(element_b, element_c, 0)
ElementRef element_a = macroDef.addSTE("a", AnmlDefs.START_OF_DATA);
ElementRef element_b = macroDef.addSTE("b");
ElementRef element_c = macroDef.addSTE("c", AnmlDefs.NO_START, null, 1, True);
macroDef.addAnmlEdge(element_a, element_b, 0);
macroDef.addAnmlEdge(element_b, element_c, 0);

At this point, the macro definition has been described in the anml object, but as was stated above, it must be instantiated before it can be used. We do this in the automata network. We need to first create the automata network in the anml object, then create a macro instance, by adding a macro reference to the automaton network. The value that is returned is a reference to the macro reference. Again, we give both the automata network and the macro reference IDs. This will be important with a surprising effect, later.

AP_CreateAutomataNetwork(anml, &anmlNet, "an1");
element.res_type = RT_MACRO_REF;
element.macro_ref = macroDef;
element.id = "u1";
AP_AddAnmlElement(anmlNet, &macroRef, &element);
1 anmlNet = anml.CreateAutomataNetwork('an1')
2 macroRef = anmlNet.AddMacroRef(macroDef, 'u1')
AnmlNetwork anmlNet = anml.createAutomataNetwork("an1");
ElementRef macroRef = anmlNet.addMacroRef(macroDef, "u1");

Now, the automata network really only contains a macro reference, but it is actually the instantiation of the macro. It is important to understand that a single macro definition can be instantiated many times in an automata network by adding macro references. This illustrates the power of the macro definition.

As before, we need to create the automaton and cleanup the objects.

#include <micron/ap/sys/platform.h>
file_descriptor_t automataFd, elementMapFd;
AP_CompileAnml(anml, &A, &E, 0, 0, 0, 0);
automatonFd = open_file("simple_automata_network.fsm");
elementMapFd = open_file("simple_automata_network.map");
AP_Save(A, automataFd);
AP_SaveElementMap(E, elementMapFd);
1 A,E = anml.CompileAnml()
2 A.Save('simple_automata_network.fsm')
3 E.SaveElementMap('simple_automata_network.map')
Pair<Automaton,ElementMap> P = anml.compileAnml(0);
P.first.save("simple_automata_network.fsm");
P.second.saveElementMap("simple_automata_network.map");

Now, we can test our new automaton, as before, with the data string, "abc".

1 >apemulate -m simple_macro_definition.map simple_macro_definition.ap abc
2  Match result:
3  Offset 3 Reporting element: an1.u1

Again, the match occurred at offset 3 in the data string, but notice that the reporting element is "an1.u1". Wait. Didn't we specify a name for the element that will report a match, which is ste3? We did, but we did it in the macro definition. A macro definition is not an element; it's a template. The actual element is the macro reference, "u1", that is in the automata network. In fact, it is this element that matches and therefore shown as the reporting element. It is important to note that the simple way in which we created the macro definition does not allow us to know exactly which element within the macro reports a match, only that a match was reported somewhere within the macro.

Adding ports to macros

In the last example, we showed how to create and instantiate a simple macro in an automata network. Here, we show a more useful usage of an ANML macro. A macro is most useful if its elements can be activated from outside the macro and/or its elements can activate other element outside the macro. To do this, we need to add "ports" to the macro. A port is not an element, per se. It is simply a means to create external edges to/from the macro. We'll create an automata network that contains a starting element that will activate an element in a macro, then have an element in the macro activate an element back in the automata network. The overall effect will be to match the string, "xabcy", where "x" and "y" will be matched in the automata network, and "abc" will be matched in the macro, as it did in the previous example.

We'll start by creating the macro definition, much as we did before, but slightly different. Here, we don't want the macro to have any elements that match at the start of data nor report any matches. In the version of the macro below, all elements have their start member set to NO_START and their match member set to "0."

ap_anml_t anml = 0;
ap_macro_def_t macroDef;
ap_anml_element_ref_t element_a, element_b, element_c;
struct ap_anml_element element;
memset(&element, 0, sizeof(element);
anml = AP_CreateAnml();
AP_CreateMacroDef(anml, &macroDef, "md1");
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "a";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_a, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "b";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_b, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "c";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_c, &element);
AP_AddAnmlEdge(macroDef, element_a, element_b, 0);
AP_AddAnmlEdge(macroDef, element_b, element_c, 0);
1 anml = Anml()
2 macroDef = anml.CreateMacroDef('md1')
3 element_a = macroDef.AddSTE('a')
4 element_b = macroDef.AddSTE('b')
5 element_c = macroDef.AddSTE('c')
6 macroDef.AddAnmlEdge(macroDef, element_a, element_b, 0)
7 macroDef.AddAnmlEdge(macroDef, element_b, element_c, 0)
Anml anml = new Anml();
MacroDef macroDef = anml.createMacroDef("md1");
ElementRef element_a = macroDef.addSTE("a");
ElementRef element_b = macroDef.addSTE("b");
ElementRef element_c = macroDef.addSTE("c");
macroDef.addAnmlEdge(macroDef, element_a, element_b, 0);
macroDef.addAnmlEdge(macroDef, element_b, element_c, 0);

The idea is to make the macro behave as an expression, with the first STE activated from outside the macro and the last STE activating an element outside the macro. We'll do this by having the STE that matches "a" be activated from the outside by the use of an "input" port, "p0", and by having the STE that matches "c" activate an element on the outside by the use of an "output" port, "p1". We need to create the ports in the macro definition. The value returned is a reference to the port.

AP_AddMacroPort(macroDef, &p0, "p0", PORT_IN);
AP_AddMacroPort(macroDef, &p1, "p1", PORT_OUT);
1 p0 = macroDef.AddMacroPort('p0', AnmlDefs.PORT_IN)
2 p1 = macroDef.AddMacroPort('p1', AnmlDefs.PORT_OUT)
int p0 = macroDef.addMacroPort("p0", AnmlDefs.PORT_IN);
int p1 = macroDef.addMacroPort("p1", AnmlDefs.PORT_OUT);

A word about ports: all elements, STEs, booleans, counters, as well as macro references, have the notion of both an input and output port. STEs and simple booleans only have one input port, whose value is 0, and one output port, whose value is also 0. Complex booleans, such as POS and SOP, as well as counters also have only one output port, 0, but they have many input ports. Macro references inherit the ports that are defined in the macro that they use, so they can have many input and output ports. It's all up to the design of the macro. In this case, any macro reference that uses this macro definition will have one input port, p0, and one output port, p1. This will become important when we connect the automata network to the macro reference elements.

Now, we need to attach the ports to the proper elements within the macro definition. In the case of element_a, we connect the input port to the input port of the element_a, whose value is 0. As well, we connect the output port to the output port of element_c, whose value is 0, as described above.

AP_AttachElementToPort(macroDef, p0, element_a, 0);
AP_AttachElementToPort(macroDef, p1, element_c, 0);
1 macroDef.AttachElementToPort(p0, element_a, 0)
2 macroDef.AttachElementToPort(p1, element_c, 0)
macroDef.attachElementToPort(p0, element_a, 0);
macroDef.attachElementToPort(p1, element_c, 0);

Next, we'll create the automata network much as we did in the last example, but this time there are only 2 STEs, one that will match 'x' at the start of data and one that will match 'y' then report a match. We'll specify an ID of "ste2" for the reporting STE, and we'll specify an ID of "u1" for the macro reference.

ap_anml_element_ref_t element_x, element_y;
AP_CreateAutomataNetwork(anml, &anmlNet, "an1");
element.res_type = RT_STE;
element.start = START_OF_DATA;
element.symbols = "x";
element.match = 0;
AP_AddAnmlElement(anmlNet, &element_x, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "y";
element.match = 1;
element.id = "ste2";
AP_AddAnmlElement(anmlNet, &element_y, &element);
element.res_type = RT_MACRO_REF;
element.macro_ref = macroDef;
element.id = "u1";
AP_AddAnmlElement(anmlNet, &macroRef, &element);
1 anmlNet = anml.CreateAutomataNetwork('an1')
2 element_x = anmlNet.AddSTE('x', startType=AnmlDefs.START_OF_DATA)
3 element_y = anmlNet.AddSTE('y', anmlId='ste2', match=True)
4 macroRef = anml.AddMacroRef(macroDef, 'u1')
AnmlNetwork anmlNet = anml.createAutomataNetwork("an1");
ElementRef element_x = anmlNet.addSTE('x', AnmlDefs.START_OF_DATA);
ElementRef element_y = anmlNet.addSTE('y', AnmlDefs.NO_START, "ste2", 1, true);
ElementRef macroRef = anml.addMacroRef(macroDef, "u1");

We want the STE that matches "x" to activate the first element of the macro. We do this via the macro reference that uses macro, m1. For this edge, we simply add an edge from the STE that matches "x" to the input port of the macro reference. In general, this is how any element would be connected to any macro input port. It is also important to point out that more than one element can be attached to a macro input port.

AP_AddAnmlEdge(anmlNet, element_x, macroRef, p0);
1 anmlNet.AddAnmlEdge(element_x, macroRef, p0)
anmlNet.addAnmlEdge(element_x, macroRef, p0);

We next want the last element of the macro to activate the STE that matches "y". Remember that we connected the last element of the macro to an output port with the ID, "p1". In this case, we need to not only specify the element and port of the target element, but we also must specify the element and port of the source, since the macro can have any number of arbitrarily named output ports. This is a little different and requires the use of an API which is an extension of the one that creates simple edges. In general, this is how any macro output port would be connected to any element's input. However, it is a limitation that a macro output port can only ever be connected to one element.

AP_AddAnmlEdgeEx(anmlNet, macroRef, p1, element_y, 0);
1 anmlNet.AddAnmlEdgeEx(macroRef, p1, element_y, 0)
anmlNet.addAnmlEdgeEx(macroRef, p1, element_y, 0);

Here, we specified that port "p1" of the macro reference is to activate port "0" of the STE that matches "y".

As before, we need to create the automaton and cleanup the objects.

#include <micron/ap/sys/platform.h>
file_descriptor_t automataFd, elementMapFd;
AP_CompileAnml(anml, &A, &E, 0, 0, 0, 0);
automatonFd = open_file("simple_automata_network.fsm");
elementMapFd = open_file("simple_automata_network.map");
AP_Save(A, automataFd);
AP_SaveElementMap(E, elementMapFd);
1 A,E = anml.CompileAnml()
2 A.Save("simple_automata_network.fsm")
3 E.SaveElementMap("simple_automata_network.map")
Pair<Automaton,ElementMap> P = anml.CompileAnml(0);
P.first.save("simple_automata_network.fsm");
P.second.saveElementMap("simple_automata_network.map");

Now, we can test our new automaton, as before, but this time, the input that will match is "xabcy" where the "x" is matched in the application, the "abc" is matched in the macro, and finally the "y" whose ID is "ste2" is matched back in the application.

1 >apemulate -m macro_with_ports.map macro_with_ports.ap xabcy
2  Match result:
3  Offset 5 Reporting element: an1.ste2

Note that the reporting element is the actual element that reports a match. Again, this level of resolution is possible since the reporting element is in the automata network and not in the macro.

Adding parameters to macros

In the last example, we showed how to create a macro with ports, but the macro that was created was of fixed function, that is, the only thing it could do is match the string, "abc". Here, we show how macros can be even more useful by "parameterizing" the elements within the macro, so that the macro can be customized each time it is used. We will add parameters to the macro, associate them with the elements of the macro, then show how to change the values, which is done when a macro reference instantiates a macro. We'll modify the macro definition in the last example to allow the symbol sets matched by the STEs to be parameterized. The macro reference in the automata network from the last example will be modified so that it will match a different string. The overall effect will be to match the string, "xijk", by substituting the symbol sets, "i", "j", and "k", for the value of the three STEs in the macro.

We'll start by creating the macro definition, much as we did before, but we'll call the elements by more generic names, like element_1, element_2, and element_3. Notice that we still set the symbols for each STE with the same values as before. These are the values that the STEs will match if no substitutions are present in the macro reference. They are the "default" values of the elements.

ap_anml_t anml = 0;
ap_macro_def_t macroDef;
ap_anml_element_ref_t element_1, element_2, element_3;
struct ap_anml_element element;
memset(&element, 0, sizeof(element);
anml = AP_CreateAnml();
AP_CreateMacroDef(anml, &macroDef, "md1");
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "a";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_1, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "b";
element.match = 0;
AP_AddAnmlElement(macroDef, &element_2, &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "c";
element.match = 1;
element.match = "ste3";
AP_AddAnmlElement(macroDef, &element_3, &element);
AP_AddAnmlEdge(macroDef, element_1, element_2, 0);
AP_AddAnmlEdge(macroDef, element_2, element_3, 0);
AP_AddMacroPort(macroDef, &p0, "p0", PORT_IN);
AP_AttachElementToPort(macroDef, p0, element_1, 0);
1 anml = Anml()
2 macroDef = anml.CreateMacroDef('md1')
3 element_1 = macroDef.AddSTE('a')
4 element_2 = macroDef.AddSTE('b')
5 element_3 = macroDef.AddSTE('c', anmlId='ste3', match=True)
6 macroDef.AddAnmlEdge(element_1, element_2, 0)
7 macroDef.AddAnmlEdge(element_2, element_3, 0)
8 
9 p0 = macroDef.AddMacroPort('p0', AnmlDefs.PORT_IN)
10 macroDef.AttachElementToPort(p0, element_1, 0)
int p0;
Anml anml = new Anml();
macroDef = anml.createMacroDef("md1");
ElementRef element_1 = macroDef.addSTE("a");
ElementRef element_2 = macroDef.addSTE("b");
ElementRef element_3 = macroDef.addSTE("c", AnmlDFefs.NO_START, "ste3", 1, true);
macroDef.addAnmlEdge(element_1, element_2, 0);
macroDef.addAnmlEdge(element_2, element_3, 0);
p0 = macroDef.AddMacroPort('p0', AnmlDefs.PORT_IN);
macroDef.AttachElementToPort(p0, element_1, 0);

Now, we'll add a parameter that will be used to substitute new values for each of the elements symbols. Currently, the only things that can be parameterized are the symbols of STEs and the count values of counters. The value that is returned is a reference to the macro parameter.

ap_macro_param_ref_t param1, param2, param3;
AP_AddMacroParam(macroDef, &param1, "%p1", element_1);
AP_AddMacroParam(macroDef, &param2, "%p2", element_2);
AP_AddMacroParam(macroDef, &param3, "%p3", element_3);
1 param1 = macroDef.AddMacroParam('%p1', element_1)
2 param2 = macroDef.AddMacroParam('%p2', element_2)
3 param3 = macroDef.AddMacroParam('%p3', element_3)
int param1 = macroDef.addMacroParam("%p1", element_1);
int param2 = macroDef.addMacroParam("%p2", element_2);
int param3 = macroDef.addMacroParam("%p3", element_3);

Next, let's create the automata network, but this time we'll only have one STE that matches on "x", "ste1", and two macro references, "u1" and "u2", that both use macroDef. We'll connect ste1 to input port "p0" of both macro references.

AP_CreateAutomataNetwork(anml, &anmlNet, "an1");
element.res_type = RT_STE;
element.start = START_OF_DATA;
element.symbols = "x";
element.match = 0;
element.id = "ste1";
AP_AddAnmlElement(anmlNet, &element_x, &element);
element.res_type = RT_MACRO_REF;
element.macro_ref = macroDef;
element.id = "u1";
AP_AddAnmlElement(anmlNet, &macroRef1, &element);
AP_AddAnmlEdge(anmlNet, element_x, macroRef1, p0);
element.res_type = RT_MACRO_REF;
element.macro_ref = macroDef;
element.id = "u2";
AP_AddAnmlElement(anmlNet, &macroRef2, &element);
AP_AddAnmlEdge(anmlNet, element_x, macroRef2, p0);
1 anmlNet = anml.CreateAutomataNetwork('an1')
2 element_x = anmlNet.AddSTE('x', startType=AnmlDefs.START_OF_DATA, anmlId='ste1')
3 macroRef1 = anmlNet.AddMacroRef(macroDef, 'u1')
4 anmlNet.AddAnmlEdge(element_x, macroRef1, p0)
5 
6 macroRef2 = anmlNet.AddMacroRef(macroDef, 'u2')
7 anmlNet.AddAnmlEdge(element_x, macroRef2, p0)
ElementRef macroRef1, macroRef2;
AnmlNetwork = anmlNet = anml.createAutomataNetwork("an1");
ElementRef element_x = anmlNet.addSTE('x', AnmlDefs.START_OF_DATA, "ste1");
macroRef1 = anmlNet.addMacroRef(macroDef, "u1");
anmlNet.addAnmlEdge(element_x, macroRef1, p0);
macroRef1 = anmlNet.addMacroRef(macroDef, "u2");
anmlNet.AddAnmlEdge(element_x, macroRef2, p0);

At this point, if we did nothing more, the automaton would match, "xabc" in both macro references, since that is the default behavior of the macro on which each is based, and we've not made any substitutions to the parameters. Let's do that now to the second macro reference. In order to make substitutions in the macro reference, we'll need to acquire a "substitution holder" from the macro definition, which is a container that holds the current value of a parameter, modify its value, then set a new, substitution value to the macro definition. We'll do this for each parameter, changing the three STEs to match on "i", "j", and "k" respectively.

struct ap_substitution_holder substHolder;
AP_GetMacroParamSubstitutionHolder(macroDef, &substHolder, param1);
substHolder.set.new_symbols = "i";
AP_SetMacroParamSubstitution(anmlNet, macroRef2, &substHolder);
AP_GetMacroParamSubstitutionHolder(macroDef, &substHolder, param2);
substHolder.ste.new_symbols = "j";
AP_SetMacroParamSubstitution(anmlNet, macroRef2, &substHolder);
AP_GetMacroParamSubstitutionHolder(macroDef, &substHolder, param3);
substHolder.ste.new_symbols = "k";
AP_SetMacroParamSubstitution(anmlNet, macroRef2, &substHolder);
1 substHolder = macroDef.GetMacroParamSubstitutionHolder(param1)
2 substHolder.ste.new_symbols = 'i'
3 anmlNet.SetMacroParamSubstitution(macroRef2, substHolder);
4 
5 substHolder = macroDef.GetMacroParamSubstitutionHolder(param2)
6 substHolder.ste.new_symbols = 'j'
7 anmlNet.SetMacroParamSubstitution(macroRef2, substHolder);
8 
9 substHolder = macroDef.GetMacroParamSubstitutionHolder(param3)
10 substHolder.ste.new_symbols = 'k'
11 anmlNet.SetMacroParamSubstitution(macroRef2, substHolder)
SubstitutionHolder substHolder = macroDef.getMacroParamSubstitutionHolder(param1);
substHolder.ste.setSymbols("i");
anmlNet.setMacroParamSubstitution(macroRef2, substHolder);
substHolder = macroDef.getMacroParamSubstitutionHolder(param2);
substHolder.ste.setSymbols("j");
anmlNet.setMacroParamSubstitution(macroRef2, substHolder);
substHolder = macroDef.getMacroParamSubstitutionHolder(param3);
substHolder.ste.setSymbols("k");
anmlNet.setMacroParamSubstitution(macroRef2, substHolder);

As before, we need to create the automaton and cleanup the objects.

#include <micron/ap/sys/platform.h>
file_descriptor_t automataFd, elementMapFd;
ap_element_map_t elementMap;
AP_CompileAnml(anml, &a, &elementMap, 0, 0, 0, 0);
automatonFd = open_file("simple_automata_network.fsm");
elementMapFd = open_file("simple_automata_network.map");
AP_Save(a, automataFd);
AP_SaveElementMap(elementMap, elementMapFd);
1 A,E = anml.CompileAnml()
2 A.Save("simple_automata_network.fsm")
3 E.SaveElementMap("simple_automata_network.map")
Pair<Automaton,ElementMap> A = anml.CompileAnml(0);
pair.first.save("simple_automata_network.fsm");
pair.second.saveElementMap("simple_automata_network.map");

Now, we can test our new automaton. This time, two input strings, "xabc" and "xijk" will match, each in a different macro reference. In both cases, the "x" is matched in the application. However, this time the pattern that is matched in "u1" is "abc" and the pattern that is matched in "u2" is "ijk" instead of "abc" which is how the original macro was designed to match.

1 >apemulate -m macro_with_parameters.map macro_with_parameters.ap xabc
2  Match result:
3  Offset 4 Reporting element: an1.u1
4 
5 >apemulate -m macro_with_parameters.map macro_with_parameters.ap xijk
6  Match result:
7  Offset 4 Reporting element: an1.u2

Adding report aliases to macros

In previous examples, we showed how to create and instantiate a macro that reported a match. Unfortunately, we could only check to see if a match occurred in a macro reference, but not which match occurred. Here, we again show a more useful usage of an ANML macro. A macro can represent any arbitrary set of expressions, possibly containing many elements that report matches. It is most useful if we can tell which expression or set of expressions in the macro caused the match. To do this, we need to add "report aliases" to the macro. A report alias is like a port, in that although it is not an element, per se, it is a means to identify which elements caused a match in a macro by querying the automata network that contains the macro instance. Here, we'll create an automata network that contains a starting element that is connected to an input port of a macro that is, in turn, connected to 2 sets of 2 STEs that each report a match on a different pattern. The overall effect will be to match the strings, "xab" OR "xij," where "x" will be matched in the automata network, and either "ab" or "ij" will be matched in the macro. The difference is that with the use of report aliases, we'll be able to tell exactly which set of STEs in the macro matched the pattern.

We'll start by creating the macro definition that matches the 2 possible patters, "ab" or "ij". These element pairs are both connected to a single input port.

ap_anml_t anml = 0;
ap_macro_def_t macroDef;
ap_anml_element_ref_t elementRefs[4];
struct ap_anml_element element;
memset(&element, 0, sizeof(element);
anml = AP_CreateAnml();
AP_CreateMacroDef(anml, &macroDef, "md1");
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "a";
element.match = 0;
AP_AddAnmlElement(macroDef, &elementRefs[0], &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "b";
element.match = 1;
AP_AddAnmlElement(macroDef, &elementRefs[1], &element);
AP_AddAnmlEdge(macroDef, elementRefs[0], elementRefs[1], 0);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "i";
element.match = 0;
AP_AddAnmlElement(macroDef, &elementRefs[2], &element);
element.res_type = RT_STE;
element.start = NO_START;
element.symbols = "j";
element.match = 1;
AP_AddAnmlElement(macroDef, &elementRefs[3], &element);
AP_AddAnmlEdge(macroDef, elementRefs[2], elementRefs[3], 0);
AP_AddMacroPort(macroDef, &p0, "p0", PORT_IN);
AP_AttachElementToPort(macroDef, p0, elementRefs[0], 0);
AP_AttachElementToPort(macroDef, p0, elementRefs[2], 0);
1 anml = Anml()
2 macroDef = anml.CreateMacroDef("md1")
3 elementRefs = (ap_anml_element_ref_t * 4)()
4 
5 elementRefs[0] = macroDef.AddSTE("a")
6 elementRefs[1] = macroDef.AddSTE("b", match=True)
7 macroDef.AddAnmlEdge(elementRefs[0], elementRefs[1], 0)
8 
9 elementRefs[2] = macroDef.AddSTE("i")
10 elementRefs[3] = macroDef.AddSTE("j", match=True)
11 macroDef.AddAnmlEdge(elementRefs[2], elementRefs[3], 0)
12 
13 p0 = macroDef.AddMacroPort("p0", AnmlDefs.PORT_IN)
14 macroDef.AttachElementToPort(p0, elementRefs[0], 0)
15 macroDef.AttachElementToPort(p0, elementRefs[2], 0)
Anml anml = new Anml();
MacroDef macroDef = anml.createMacroDef("md1");
ElementRef [] elementRefs = new ElementRef[4];
int p0;
elementRefs[0] = macroDef.cddSTE("a");
elementRefs[1] = macroDef.addSTE("b", AnmlDefs.NO_START, 1, true);
macroDef.addAnmlEdge(elementRefs[0], elementRefs[1], 0);
elementRefs[2] = macroDef.addSTE("i");
elementRefs[3] = macroDef.addSTE("j", AnmlDefs.NO_START, 1, true);
macroDef.addAnmlEdge(elementRefs[2], elementRefs[3], 0);
p0 = macroDef.addMacroPort("p0", AnmlDefs.PORT_IN);
macroDef.attachElementToPort(p0, elementRefs[0], 0);
macroDef.attachElementToPort(p0, elementRefs[2], 0);

Notice that we did not assign any IDs to any of the STEs in the macro because, well, it doesn't help to determine which STE reported a match anyway. In order to do that, we need to create the report ports that will alias the actual elements in the macro that report a match.

AP_AddMacroPort(macroDef, &r0, "r0", PORT_REPORT);
AP_AddMacroPort(macroDef, &r1, "r1", PORT_REPORT);
1 r0 = macroDef.AddMacroPort("r0", AnmlDefs.PORT_REPORT)
2 r1 = macroDef.AddMacroPort("r1", AnmlDefs.PORT_REPORT)
int r0 = macroDef.addMacroPort("r0", AnmlDefs.PORT_REPORT);
int r1 = macroDef.addMacroPort("r1", AnmlDefs.PORT_REPORT);

Next, we'll attach the report ports to the proper elements within the macro definition. We connect report port, "r0", to the element that reports a match on "b", then connect report port, "r1", to the element that reports a match on "j".

AP_AttachElementToPort(macroDef, r0, elementRefs[1], 0);
AP_AttachElementToPort(macroDef, r1, elementRefs[3], 0);
1 macroDef.AttachElementToPort(r0, elementRefs[1], 0)
2 macroDef.AttachElementToPort(r1, elementRefs[3], 0)
macroDef.attachElementToPort(r0, elementRefs[1], 0);
macroDef.attachElementToPort(r1, elementRefs[3], 0);

Next, we'll create the automata network with 1 STE that will attempt to constantly match "x" then activate the input port of a macro reference, whose ID is "u1".

AP_CreateAutomataNetwork(anml, &anmlNet, "an1");
element.res_type = RT_STE;
element.start = ALL_INPUT;
element.symbols = "x";
element.match = 0;
AP_AddAnmlElement(anmlNet, &element_x, &element);
element.res_type = RT_MACRO_REF;
element.macro_ref = macroDef;
element.id = "u1";
AP_AddAnmlElement(anmlNet, &macroRef, &element);
AP_AddAnmlEdge(anmlNet, element_x, macroRef, p0);
1 anmlNet = anml.CreateAutomataNetwork("an1")
2 element_x = anmlNet.AddSTE("x", startType=AnmlDefs.ALL_INPUT)
3 macroRef = anmlNet.AddMacroRef(macroDef, "u1")
4 anmlNet.AddAnmlEdge(element_x, macroRef, p0)
AnmlNetwork anmlNet = anml.createAutomataNetwork("an1");
ElementRef element_x = anmlNet.addSTE("x", AnmlDefs.ALL_INPUT);
ElementRef macroRef = anmlNet.addMacroRef(macroDef, "u1");
anmlNet.addAnmlEdge(element_x, macroRef, p0);

As before, we need to create the automaton and cleanup the objects.

#include <micron/ap/sys/platform.h>
file_descriptor_t automataFd, elementMapFd;
AP_CompileAnml(anml, &A, &E, 0, 0, 0, 0);
automatonFd = open_file("macro_with_report_aliases.fsm");
elementMapFd = open_file("macro_with_report_aliases.map");
AP_Save(A, automataFd);
AP_SaveElementMap(E, elementMapFd);
1 A,E = anml.CompileAnml()
2 A.Save("macro_with_report_aliases.fsm")
3 E.SaveElementMap("macro_with_report_aliases.map")
Pair<Automaton,ElementMap> P = anml.CompileAnml(0);
P.first.save("macro_with_report_aliases.fsm");
P.second.saveElementMap("macro_with_recd port_aliases.map");

Now, we can test our new automaton. This time, the input is "xabxij". Again, the "x" is matched in the application. However, this time the pattern that is matched in "u1" is either "ab" or "ij". Since the report ports are present and connected to the two reporting STEs in the macro, when either reports a match, the reference that is returned is that of the report port that is connected to the reporting STE. In this way, we will see two different matches to the input data, one at offset 3 at report port "r0" and the next at offset 6 at report port r1.

1 >apemulate -m macro_with_report_aliases.map macro_with_report_aliases.ap xabxij
2  Match result:
3  Offset 3 Reporting element: an1.u1:r0
4  Offset 6 Reporting element: an1.u1:r1

Now we really are seeing exactly which elements inside the macro are reporting. We know that "r0" is associated with the sub-expression "ab" and that "r1" is associated with the sub-expression "ij".