In this blog, I will present some tips that can be very useful when you write e macros. We will see which kind of macro we should use for our purposes, and what options we can use to better define our macro.
Let's begin by looking at the following simple example. Assume that you want to define a method that computes an expression of any type and assigns its value to two variables. Because the expression type is unknown, you cannot use a real method; instead, you can define a pseudo-method, presumably using a macro.
At the first glance, it seems that you can easily accomplish your goal by means of the following define-as macro:
define <double_assign'action>
"double_assign\(<lsh1'exp>,<lsh2'exp>,<rhs'exp>\)" as {
<lsh1'exp> = <rsh'exp>;
<lsh2'exp> = <lsh1'exp>;
};
However, it is not so simple after all. When you try to load the above code, you get the following load error:
*** Error: Between '\(' and '\)' in the match expression, there should only be a syntactic argument (with or without repetition)
Indeed, as you probably know, there are certain limitations on the syntax of macro match expressions. In particular, within parentheses or brackets you can only use either of the following:
- A single syntactic argument; for example, <lsh1'exp> - which denotes an expression (the prefix lsh1 is a unique tag given to this syntactic argument).
- A syntactic argument repetition, denoted by a separator character followed by an ellipsis; for example, \(<exp>,...\) - which denotes any number of expressions separated by a comma.
However, in the above example, the parentheses contain three syntactic arguments and two commas.
So, how can we define a macro that will match the desired syntax? The solution is to use a more general match expression, and check the specific syntax procedurally inside the macro body. Also, a define-as macro only allows you to specify a fixed template for the replacement code; it does not allow applying conditions on the original code. So, you need to use a define-as-computed macro.
In our example, as now modified (below), the match expression will match any sequence of <exp>s, and it will procedurally check that their number is exactly 3. Notice that when a repetition is used in a define-as-computed macro (<exp>,... in this example), it is treated as a list of strings, where each repetition element is represented as a separate string. Notice also that the repetition is referenced inside the macro body in the plural form (<exps> in this example).
define <double_assign'action> "double_assign[ ]\(<exp>,...\)" as computed {
if <exps>.size() != 3 then {
error("double_assign() must get exactly 3 parameters");
};
var lsh1: string = <exps>[0];
var lsh2: string = <exps>[1];
var rsh: string = <exps>[2];
return append(lsh1, "=", rsh, ";", lsh2, "=", lsh1);
};
If double_assign()is called with a number of parameters other than 3, an error message will appear at the time of load or compilation.
So far, so good. But what happens if there also exists a real method called double_assign(), and this method gets a different number of parameters? Now we have a problem. Any call to this method will now be matched by the macro we just introduced, and result in the error message.
So now the question is: Can we have both the method and the macro work without interfering each other? The answer is yes, provided that we use the predefined routine, reject_match(). When this routine is called from within a define-as-computed macro, its effect is the same as if the macro match expression did not match the code in the first place. The parser will then make other attempts to match the code, and eventually it will recognize it as a real method call.
To achieve this solution, the above macro should be rewritten as follows:
define <double_assign'action> "double_assign[ ]\(<exp>,...\)" as computed {
if <exps>.size() != 3 then {
reject_match();
};
var lsh1: string = <exps>[0];
var lsh2: string = <exps>[1];
var rsh: string = <exps>[2];
return append(lsh1, "=", rsh, ";", lsh2, "=", lsh1);
};
Before concluding our blog tips, let's examine another case where using reject_match() can be useful.
Assume that you want to introduce the following syntax to declare several cover items at once:
item a, b;
This seems to be easily done with the following macro:
define <multi'cover_item> "item <name>,..." as computed {
for each in <names> do {
result = append(result, "item ", it, ";");
};
};
However, after this macro is loaded, any cover item declaration causes a load time error message that suggests an infinite recursion in macro expansion. The reason is that in addition to matching the above syntax, the macro also matches the original cover item declaration syntax with a single item. The result is that its expansion code is exactly the same, and the macro continues matching the same code infinitely.
This problem can be easily solved by using reject_match() to reject cases with one item only:
define <multi'cover_item> "item <name>,..." as computed {
if (<names>).size() == 1 then {
reject_match();
};
for each in <names> do {
result = append(result, "item ", it, ";");
};
};
To summarize, when writing e macros where the basic match expression syntax is not sufficient, the following tips can be very useful:
- Write a more general match expression, and perform additional checks procedurally.
- Wherever useful, use reject_match(), especially if there is a possibility that another macro or built-in construct will match.
Yuri Tsoglin
e Language team, Specman R&D