Elixir quote record(Turn it into a tuple) and preserve data? - macros

When quoted using quote do: records aren't converted to tuples containing the record fields:
iex(1)> quote do: is_bitstring("blah")
{:is_bitstring, [context: Elixir, import: Kernel], ["blah"]}
iex(2)> quote do: Computer.new("Test")
{{:., [], [{:__aliases__, [alias: false], [:Computer]}, :new]}, [], [[name: "Test"]]}
iex(3)> quote do: Computer.new("Test")
{{:., [], [{:__aliases__, [alias: false], [:Computer]}, :new]}, [], [[name: "Test"]]}
iex(4)> c = Computer.new("Test")
Computer[name: "Test", type: nil, processor: nil, hard_drives: []]
iex(5)> c
Computer[name: "Test", type: nil, processor: nil, hard_drives: []]
iex(6)> quote do: c
{:c, [], Elixir}
Also, when I try doing this in my code:
defmacro computer([do: code]) do
# macro login here
# build computer record based on macro logic
computer = Computer.new(params)
quote do: unquote computer
end
I get an error:
** (CompileError) elixir/test/lib/computer_dsl_test.exs: tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: Computer[name: "", type: nil, processor: nil, hard_drives: []]
I thought that records were just tuples with wrappers functions of some sort. The Elixir Getting Started guide states "A record is simply a tuple where the first element is the record module name." Is there something I am missing? Is there a function I can call on a record to get the tuple representation? I am aware of the raw: true option but I am not sure how to use that on an existing record.
Any insights?

Records are tuples. The output you see on the console is just formatted for easier inspection. You can check that records are tuples if you inspect them with raw: true:
iex(1)> defrecord X, a: 1, b: 2
iex(2)> x = X.new
X[a: 1, b: 2] # This is formatted output. x is really a tuple
iex(3)> IO.inspect x, raw: true
{X, 1, 2}
As can be seen, a record instance is really a tuple. You can also pattern match on it (although I don't recommend this):
iex(4)> {a, b, c} = x
iex(8)> a
X
iex(9)> b
1
iex(10)> c
2
The quote you are mentioning serves completely different purpose. It turns an Elixir expression into AST representation that can be injected into the rest of the AST, most often from the macro. Quote is relevant only in compile time, and as such, it can't even know what is in your variable. So when you say:
quote do: Computer.new("Test")
The result you get is AST representation of the call of the Computer.new function. But the function is not called at this point.

Just reading the error message and the elixir "getting stated" on macro definition it appears that the result of a quote has the form:
In general, each node (tuple) above follows the following format:
{ tuple | atom, list, list | atom }
The first element of the tuple is an atom or another tuple in the same representation;
The second element of the tuple is an list of metadata, it may hold information like the node line number;
The third element of the tuple is either a list of arguments for the function call or an atom. When an atom,
it means the tuple represents a variable.
Besides the node defined above, there are also five Elixir literals that when quoted return themselves (and not a tuple). They are:
:sum #=> Atoms
1.0 #=> Numbers
[1,2] #=> Lists
"binaries" #=> Strings
{key, value} #=> Tuples with two elements
My guess is that the unquote is the reverse function of quote, and so it expects as argument one of the above forms. This is not the case for the computer record.
I think the unquote is not necessary there (although I didn't try to understand the intent of your code...) and that
defmacro computer([do: code]) do %% why do you need this argument?
quote do: Computer.new
end
should be ok.

Related

What happened when invoking locals() in python3?

I'm using Python3.7.2
The code which makes me confused looks like this:
def foo(cond):
if cond:
z = 1
return z
else:
loc = locals()
x = 1
locals()['y'] = 2
exec("z = x + y")
print(loc)
print(locals())
foo(False)
Here is the result it prints:
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2, 'z': 3}
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2}
The exec function changes the local namespace, but when I execute locals(), variable z disappeared.
However, if I change the code like this:
def foo(cond):
if cond:
return 1
else:
loc = locals()
x = 1
locals()['y'] = 2
exec("z = x + y")
print(loc)
print(locals())
foo(False)
The result will be:
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2, 'z': 3}
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2, 'z': 3}
I'm really confused. :(
In short: the dictionary returned by locals() is only a copy of the true local variables—which are stored in an array, not in a dictionary. You can manipulate the locals() dictionary, add or remove entries at will. Python does not care since that is merely a copy of the local variables it works with. However, each time you call locals(), Python copies the current values of all local variables into the dictionary, replacing anything else you or exec() put in before. So, if z is a proper local variable (as in the first, but not second version of your code), locals() will restore its current value, which happens to be "not set" or "undefined".
Indeed, conceptually Python stores local variables in a dictionary, which can be retrieved through the function locals(). However, internally, it really is two arrays. The names of the locals are an attribute of the code itself and therefore stored in the code object as code.co_varnames. Their values, however, are stored in the frame as fast (which is inaccessible from Python code). You can find more on co_varnames etc. in inspect and dis modules, respectively.
The built-in function locals() actually calls a method fastToLocals() on the current frame object. If we were to write the frame with its fastToLocals method in Python, it might look like this (I am leaving out a lot of details here):
class frame:
def __init__(self, code):
self.__locals_dict = None
self.f_code = code
self.__fast = [0] * len(code.co_varnames)
def fastToLocals(self):
if self.__locals_dict is None:
self.__locals_dict = {}
for (key, value) in zip(self.f_code.co_varnames, self.__fast):
if value is not null: # Remember: it's actually C-code
self.__locals_dict[key] = value
else:
del self.__locals_dict[key] # <- Here's the magic
return self.__locals_dict
def locals():
frame = sys._getframe(1)
frame.fastToLocals()
In plain English: the dictionary you get when calling locals() is cached in the current frame. Calling locals() repeatedly will give you the same dictionary (__locals_dict in the code above). However, each time, you call locals(), the frame will update the entries in this dictionary with the current values that local variables have at that time. As noted here, when a local variable is not set, the entry in the __locals_dict is deleted. And that's what it is all about.
In your first version of the code, the third line says z = 1, which makes z a local variable. In the else branch, however, that local variable z is not set (i.e. would raise a UnboundLocalError) and is therefore removed from the __locals_dict. In the second version of your code, there is no assignment to z, so it is not a local variable and the locals() function does not care about it.
The set of local variables is actually fixed by the compiler. This means that you cannot add or remove local variables at runtime. This is a problem for exec(), as you are clearly using exec() here to define a local variable z. Python's way out is to say that exec() stores z as a local variable in the _locals_dict dictionary, although it cannot put it into the arrays behind this dictionary.
Conclusion: The values of local variables are actually stored in an array, not in the dictionary returned by locals(). When calling locals(), the dictionary is updated with the true current values taken from the dictionary. exec(), on the other hand, can only store its local variables in the dictionary, not in the array. If z is a proper local variable, it will be overwritten by a call to locals() with its current value (which is "does not exist"). If z is not a proper local variable, it will stay in the dictionary untouched.
Python's specifications say that any variable that you assign to inside a function is a local variable. You can change this default through the use of global or nonlocal, of course. But as soon as you write z = ..., z becomes a local variable. If, on the other hand, you only have x = z in your code, the compiler assumes that z is rather a global variable. That's why the line z = 1 makes all the difference: it marks z as a local variable that receives its place in the fast array.
Concerning exec(): in general, there is no way the compiler could really know what code exec() is going to execute (in your case with a string literal it could, but since this is a are rare and uninteresting case, it never tries, anyway). The compiler can therefore not know what local (or global) variables the code in exec() might access, and cannot include that in its calculation of how large the array of local variables should be.
By the way: that local variables are managed in arrays instead of proper dictionaries is the reason why there might be raised an UnboundLocalError instead of an NameError. In case of local variables, the Python interpreter actually recognises the name and knows exactly where its value is to be found (inside the fast array mentioned above). But if that entry is null, it cannot return something meaningful and therefore raises the UnboundLocalError. For global names, however, Python goes really searched for a variable with the given name in the globals and built-in dictionaries. In that case, a variable of the requested name might truly not exist.
Compare your code with the following:
def foo(cond):
if cond:
return 1
else:
loc = locals().copy()
# ——————————————^
x = 1
locals()['y'] = 2
exec("z = x + y")
print(loc)
print(locals())
foo(False)
It produces
{'cond': False}
{'cond': False, 'loc': {'cond': False}, 'x': 1, 'y': 2, 'z': 3}
as you might expect. The reason is that locals() is a reference, not a value.

systemverilog unpacked array concatenation

I'm trying to create an unpacked array like this:
logic [3:0] AAA[0:9];
I'd like to initialize this array to the following values:
AAA = '{1, 1, 1, 1, 2, 2, 2, 3, 3, 4};
For efficiency I'd like to use repetition constructs, but that's when things are falling apart.
Is this not possible, or am I not writing this correctly? Any help is appreciated.
AAA = { '{4{1}}, '{3{2}}, '{2{3}}, 4 };
Firstly, the construct you are using is actually called the replication operator. This might help you in future searches, for example in the SystemVerilog LRM.
Secondly, you are using an array concatenation and not an array assignment in your last block of code (note the missing apostrophe '). The LRM gives the following (simple) example in Section 10.10.1 (Unpacked array concatenations compared with array assignment patterns) to explain the difference:
int A3[1:3];
A3 = {1, 2, 3}; // unpacked array concatenation
A3 = '{1, 2, 3}; // array assignment pattern
The LRM says in the same section that
...unpacked array concatenations forbid replication, defaulting, and
explicit typing, but they offer the additional flexibility of
composing an array value from an arbitrary mix of elements and arrays.
int A9[1:9];
A9 = {9{1}}; // illegal, no replication in unpacked array concatenation
Lets also have a look at the alternative: array assignment. In the same section, the LRM mentions that
...items in an assignment pattern can be replicated using syntax, such as '{ n{element} }, and can be defaulted using the default: syntax. However, every element item in an array assignment pattern must be of the same type as the element type of the target array.
If transforming it to an array assignment (by adding an apostrophe), your code actually translates to:
AAA = '{'{1,1,1,1}, '{2,2,2}, '{3,3}, 4};
This means that the SystemVerilog interpreter will only see 4 elements and it will complain that too few elements were given in the assignment.
In Section 10.9.1 (Array assignment patterns), the LRM says the following about this:
Concatenation braces are used to construct and deconstruct simple bit vectors. A similar syntax is used to support the construction and deconstruction of arrays. The expressions shall match element for element, and the braces shall match the array dimensions. Each expression item shall be evaluated in the context of an assignment to the type of the corresponding element in the array.
[...]
A syntax resembling replications (see 11.4.12.1) can be used in array assignment patterns as well. Each replication shall represent an entire single dimension.
To help interprete the bold text in the quote above, the LRM gives the following example:
int n[1:2][1:3] = '{2{'{3{y}}}}; // same as '{'{y,y,y},'{y,y,y}}
You can't do arbitrary replication of unpacked array elements.
If your code doesn't need to be synthesized, you can do
module top;
typedef logic [3:0] DAt[];
logic [3:0] AAA[0:9];
initial begin
AAA = {DAt'{4{1}}, DAt'{3{2}}, DAt'{2{3}}, 4};
$display("%p",AAA);
end
endmodule
I had another solution but I'm not sure if it is synthesizable. Would a streaming operator work here? I'm essentially taking a packed array literal and streaming it into the data structure AAA. I've put it on EDA Playground
module tb;
logic [3:0] AAA[0:9];
initial begin
AAA = { >> int {
{4{4'(1)}},
{3{4'(2)}},
{2{4'(3)}},
4'(4)
} };
$display("%p",AAA);
end
endmodule
Output:
Compiler version P-2019.06-1; Runtime version P-2019.06-1; Mar 25 11:20 2020
'{'h1, 'h1, 'h1, 'h1, 'h2, 'h2, 'h2, 'h3, 'h3, 'h4}
V C S S i m u l a t i o n R e p o r t
Time: 0 ns
CPU Time: 0.580 seconds; Data structure size: 0.0Mb
Wed Mar 25 11:20:07 2020
Done

How to dynamically generate a Phoenix controller function with a map argument?

So I'm using Phoenix 1.3, and I created a macro to generate a function and inject it into a controller.
Based on the number I pass in, I want it to generate a map with that many parameters that are named "id1", "id2", etc. all the way up to "id#{number}". This map will be part of the argument list along with the usual Phoenix "conn".
So I want to generate a method like this to be pattern matched against and "some stuff" can be executed:
def index(conn, %{"id1" => id1, "id2" => id2}) do
# some stuff
end
when I call the macro create_some_function_by_number("index", 2).
My macro looks something like:
defmacro create_some_function_by_number(name, num) do
params =
for n <- 1..num, do: %{"id#{n}" => Macro.var(:"id#{n}", nil)}
|> Map.new
quote do
def unquote(:"#{name}")(unquote(Macro.escape(params)) do
# some code here for the index action
end
end
end
1) How do I inject the "conn" into the function head so it can be pattern matched against?
2) Is this the correct way to create the map to be pattern matched against?
While you can definitely use macros in this way, you probably should not. Here is a working solution with comments:
defmodule MyMacro do
defmacro create_some_function_by_number(name, num, do: block) do
params =
for n <- 1..num do
{"id#{n}", Macro.var(:"id#{n}", nil)}
end
# We can't call Macro.escape because it is for escaping values.
# In this case, we have a mixture of values "id#{n}" and
# expressions "Macro.var(...)", so we build the map AST by hand.
pattern =
{:%{}, [], params}
conn =
Macro.var(:conn, nil)
quote do
def unquote(:"#{name}")(unquote(conn), unquote(pattern)) do
unquote(block)
end
end
end
end
defmodule MyExample do
import MyMacro
create_some_function_by_number :index, 2 do
{conn, id1 + id2}
end
end
IO.inspect MyExample.index(:conn, %{"id1" => 1, "id2" => 2})
As you can see, macros can make the code harder to understand. If you can solve it at runtime, it should definitely be preferred.

Minimum arguments for variable parameters in freemarker macros

When you have variable parameters in a macro, for instance
<#macro m a b c...>
Do you have to pass a minimum of 3 arguments or 2 while calling the macro? Does the parameter c here have to have at least 1 value? Also is there any way to specify a parameter as null by default?
<#macro name param1 param2 ... paramN>
...
<#nested loopvar1, loopvar2, ..., loopvarN>
...
<#return>
...
</#macro>
Where:
name: name of macro variable. It's not an expression. It follows the
same syntax as like top-level variable references, like myMacro or
my-macro. However, it can also be written as a string literal, which
is useful if the macro name contains characters that can't be
specified in an identifier, for example <#macro "foo~bar">.... Note
that this string literal does not expand interpolations (as
"${foo}").
param1, param2, ...etc.: the name of the local variables store the
parameter values (not expression), optionally followed by = and the
default value (that's an expression). The default value can even be
another parameter, for example <#macro section title label=title>.
The parameter name uses the same syntax as like top-level variable
references, so the same features and restrictions apply.
paramN, the last parameter may optionally has 3 trailing dots (...),
which indicates that the macro takes a variable number of parameters
and the parameters that doesn't match any other parameters will be
collected in this last parameter (also called the catch-all
parameter). When the macro is called with named parameters, paramN
will be a hash containing all of the undeclared key/value pairs
passed to the macro. When the macro is called using positional
parameters, paramN will be the sequence of the extra parameter
values. (Inside the macro, to find out which was the case, you can
use myCatchAllParam?is_sequence.)
Therefore as you can see macro does not have any limitation to take N parameters.
This structure creates a macro variable (in the current namespace, if you know namespace feature). If you are new to macros and user-defined directives you should read the the tutorial about user-defined directives.
Macro variable stores a template fragment (called macro definition body) that can be used as user-defined directive. The variable also stores the name of allowed parameters to the user-defined directive. You must give value for all of those parameters when you use the variable as directive, except for parameters that has a default value. The default value will be used if and only if you don't give value for the parameter when you call the macro.
The variable will be created at the beginning of the template; it does not mater where the macro directive is placed in the template.
Example: Macro with parameters:
<#macro test foo bar baaz>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#-- call the macro: -->
<#test foo="a" bar="b" baaz=5*5-2/>
Output:
Test text, and the params: a, b, 23
Example: Macro with parameters and default parameter values:
<#macro test foo bar="Bar" baaz=-1>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#test foo="a" bar="b" baaz=5*5-2/>
<#test foo="a" bar="b"/>
<#test foo="a" baaz=5*5-2/>
<#test foo="a"/>
Output:
Test text, and the params: a, b, 23
Test text, and the params: a, b, -1
Test text, and the params: a, Bar, 23
Test text, and the params: a, Bar, -1
However, about last part of your question there is an explanation:
The null reference is by design an error in FreeMarker. Defining a custom null value - which is a string - is not a good idea for the reasons you mention. The following constructs should be used instead:
Macro and function parameters can have a default value, so the
callers can omit them
To check if a variable is null, you should use the ?? operator: <#if
(name??)>
When you use a variable that can be null, you should use the !
operator to specify a default value: name!"No name"
To check if a sequence (or a string) is empty, use the ?has_content
builtin: <#if (names?has_content)>
You can specify an empty sequence as default parameter value in a macro, and simply test whether it's empty.
When you have variable parameters in a macro, you don't have to pass a value for the last argument.
For example:
<#macro m a b c...>
a = ${a!}
b = ${b!}
<#list c?keys as attr>
${attr} = ${c[attr]}
</#list>
</#macro>
<#m a='A' b='B' />
<#m a='A' b='B' c='C' d='D'/>
Will output:
a = A
b = B
a = A
b = B
c = C
d = D

append if element in list

I'm trying to create a parser for program. For example,
I entered (what I want)
"(2+3)-4" it will become something like this "(minus, (plus, num 2, num 3),num 4)"
What I've done so far..
"(2+3)-4" I then split it and it becomes list Z = ["(","2","+","3",")","-","4"] then I compared if "-" is a member of Z, if true I append the element "-" into a new list ["-"]
I'm not sure if the way I'm doing is correct, I'm new to Er-lang and struggling quite a lot. If anyone is able to offer me some insight, thanks.
Consider the following, which returns a tuple-based representation of its input:
parse(Expr) ->
Elems = re:split(Expr, "([-+)(])", [{return,list}]),
parse(lists:filter(fun(E) -> E /= [] end, Elems), []).
parse([], [Result]) ->
Result;
parse([], [V2,{op,Op},V1|Tacc]) ->
parse([], [{Op,V1,V2}|Tacc]);
parse(["("|Tail], Acc) ->
parse(Tail, [open|Acc]);
parse([")"|Tail], [Op,open|TAcc]) ->
parse(Tail, [Op|TAcc]);
parse(["+"|Tail], Acc) ->
parse(Tail, [{op,plus}|Acc]);
parse(["-"|Tail], Acc) ->
parse(Tail, [{op,minus}|Acc]);
parse([V2|Tail], [{op,Op},V1|Tacc]) ->
parse(Tail, [{Op,V1,{num,list_to_integer(V2)}}|Tacc]);
parse([Val|Tail], Acc) ->
parse(Tail, [{num,list_to_integer(Val)}|Acc]).
The first function, parse/1, splits the expression along the + and - operators and parentheses, preserving these in the resulting list. It then filters that list to remove empty elements, and passes it with an empty accumulator to parse/2.
The parse/2 function has eight clauses, described below:
The first two handle the case when the parsed input list has been exhausted. The second of these handles the case where multiple elements in the accumulator need to be collapsed into a single tuple consisting of operator and operands.
The next two handle clauses parentheses. When we see an open parenthesis, we push an atom open into the accumulator. Upon seeing the matching close parenthesis, we expect to see an operation tuple and the atom open in the accumulator, and we replace them with just the tuple.
Clauses 5 and 6 handle + and - respectively. Each just pushes a {op,Operator} tuple into the accumulator, where Operator is either the atom plus or the atom minus.
The final two clauses handle values. The first one handles the case where the accumulator holds a value and an op tuple, which gets replaced with a full operation tuple consisting of the atom plus or minus followed by two num tuples each holding integer operands. The last clause just handles plain values.
Putting this in a module p, compiling it, and running it in an Erlang shell yields the following:
1> p:parse("2+3").
{plus,{num,2},{num,3}}
2> p:parse("(2+3)-4").
{minus,{plus,{num,2},{num,3}},{num,4}}