How i can use temporary variables in macros for building code in the Crystal Lang. For examle, i have:
module Rule
macro included
{% rules = [] of TypeDeclaration %}
macro rule(declaration)
raise("Should be TypeDeclaration") unless \{{declaration.is_a?(TypeDeclaration)}}
\{% {{rules}} << declaration %}
end
macro finished
\{% for declaration in rules %}
puts \{{declaration}}
\{% end %}
end
end
end
class StringRule
include Rule
rule min : Int32 = 0
rule max : Int32 = 255
end
And i have compile error
> 6 | {% [] << declaration %}
^
Error: for empty arrays use '[] of ElementType'
I need store all rules for reusing it in a hook finished. Is it posible?
Variables in macro expressions are scoped to the respective macro context. So a variable defined in macro included is not visible in macro rule.
But you can use constants for this: RULES = [] of _. The _ underscore makes the array untyped, it is only to be used during compilation and not for actual code. If you refer to RULES outside a macro expression, the compiler will complain.
module Rule
macro included
RULES = [] of _
macro rule(declaration)
\{% raise("Should be TypeDeclaration") unless declaration.is_a?(TypeDeclaration) %}
\{% RULES << declaration %}
end
macro finished
\{% for declaration in RULES %}
puts \{{ declaration.stringify }}
\{% end %}
end
end
end
class StringRule
include Rule
rule min : Int32 = 0
rule max : Int32 = 255
end
Related
Consider these two macro definitions:
macro createTest1()
quote
function test(a = false)
a
end
end |> esc
end
macro createTest2()
args = :(a = false)
quote
function test($args)
a
end
end |> esc
end
According to the builtin Julia facilities they should both evaluate to the same thing when expanded:
println(#macroexpand #createTest1)
begin
function test(a=false)
a
end
end
println(#macroexpand #createTest2)
begin
function test(a = false)
a
end
end
Still I get a parse error when trying to evaluate the second macro:
#createTest2
ERROR: LoadError: syntax: "a = false" is not a valid function argument name
It is a space in the second argument list. However, that should be correct Julia syntax. My guess is that it interprets the second argument list as another Julia construct compared to the first. If that is the case how do I get around it?
The reason that the second macro is failing as stated in my question above. It looks correct when printed however args is not defined correctly and Julia interprets it as an expression which is not allowed. The solution is to instead define args according to the rules for function parameters. The following code executes as expected:
macro createTest2()
args = Expr(:kw, :x, false)
quote
function test($(args))
a
end
end |> esc
end
For example, i want to get looks-like-native "for" loop, which was implemented in crystal's embedded macro language, but it is absent (for reasons of principle) in main, "runtime" language:
for i in list do
end
for k, v in hash do
end
(Here "{}" is a simple block. I cant use do .. end syntax here anyway (maybe not)).
Will be very good to implement multi-inlcude directive, such as:
includes MixinX, MixinY, MixinZ
and so on...
As i know, macro (named "for" and "includes" in the provided snippets) can't accept "i in list" without double-quoting. So... is there only one way to do so - extending crystal's syntax/lexical parser and analyzer itself?
Maybe, you could use something like this:
module Foo
def foo
"foo"
end
end
module Bar
def bar
"bar"
end
end
class Object
macro includes(*mods)
{% for mod in mods %}
include {{ mod }}
{% end %}
end
end
class Baz
includes Foo, Bar
end
Baz.new.foo # => "foo"
Baz.new.bar # => "bar"
I have a macro which gets a module name as parameter and I want to call a function on that module to get some data in order to generate the quote block.
Example:
defmacro my_macro(module) do
data = apply(module, :config, [])
# do something with data to generate the quote do end
end
Obviously, this doesn't work because the parameter value is quoted. I could fetch the data inside the quote block and act accordingly but that would put the whole logic inside the module that uses my macro which is quite dirty. I want to inject as little code as possible.
You can extract the module out by pattern matching with its quoted form: {:__aliases__, _, list} where list is a list of atoms which when concatenated with a dot (use Module.concat/1) produces the full module name.
defmodule A do
defmacro my_macro({:__aliases__, _, list}) do
module = Module.concat(list)
module.foo()
end
end
defmodule B do
def foo do
quote do
42
end
end
end
defmodule C do
import A
IO.inspect my_macro B
end
Output:
42
Given the following:
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <--- Why?
quote do
IO.puts "#{unquote(fn_name)} called"
unquote(inner)
end
end
end
What's the reason for fn_name = unquote(fn_name)? If I omit this line, it's a compile error. What's the reason for this "double" unquoting?
Let's simplify the example a little bit:
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <--- Why?
quote do
{unquote(fn_name), unquote(inner)}
end
end
end
In the example above, because quote is returning a tuple with two unquoted elements, it is equivalent to:
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <--- Why?
{fn_name, inner}
end
end
Now it is easier to understand what happens if you don't unquote(fn_name) before: the variable fn_name simply wouldn't exist inside the macro definition. Remember that all defs (def, defp, defmacro, etc) start a new variable scope, so if you want to use fn_name inside, you need to define it somehow.
The other property we are seeing in this code is that Elixir will stop unquoting when it sees a quote. So in the quote above, unquote won't be unquoted when the macro is defined but rather when the macro is executed, which also explains why the variable is required to be defined inside the macro.
It's because of Hygiene.
Elixir has the concept of macro hygiene. Hygiene means that variables, imports, and aliases that you define in a macro do not leak into the caller’s own definitions.
for fn_name <- [:foo, :bar, :baz] do
defmacro unquote(fn_name)(do: inner) do
fn_name = unquote(fn_name) # <-- This is macro's context
quote do
IO.puts "#{unquote(fn_name)} called" # <-- This is caller's context
unquote(inner)
end
end
end
You should read Hygiene Protects the Caller’s Context from Chris McCord's Metaprogramming Elixir book
I'd like to declare a global variable that is scoped to only my rules file.
For example: variable $reUseMe is only declared once.
rule 1
$reUseMe : POJO(val = 1)
//other conditions
rule 2
$reUseMe > val
you can refer to globals in LHS via eval:
global SomeType variable
rule ...
when
...
eval(variable > something)
There are no scoped global variables, but in some cases rule inheritance helps.
rule "Rule 1"
when
$reUseMe :POJO( val == 1 )
then
end
rule "Rule 2" extends "Rule 1"
when
# You can use the variables from Rule 1
POJO( val > $reUseMe.val )
then
end
Only LHS is added. RHS from Rule 1 is ignored in Rule 2.