How to define a reusable function for use in macros?

I have a class where I need to format a macro (StringLiteral) 3 times in one file. I was trying to figure out how to DRY this up

For example:

def constantize(macroid)
  macroid.split(' ').map(&.capitalize).join("").id
end
puts {{ constantize "some text" }} # => "SomeText"

https://carc.in/#/r/6da1

{% begin %}
{% def constantize(macroid)
  macroid.split(' ').map(&.capitalize).join("").id
 end %}
puts {{ constantize "some text" }} # => "SomeText"

https://carc.in/#/r/6da3

macro constantize(macroid)
  {{macroid.split(' ').map(&.capitalize).join("").id}}
end

{% for text in ["example text"] %}
  puts "{{ constantize text }}"
{% end %}

https://carc.in/#/r/6dad

All of the above give the same error: Undefined macro method constantize. The following appears to properly expand to ExampleText, however, placing that inside of {% %}s or {{ }}s gives errors about being unable to next macro methods:

macro constantize(macroid)
  {{macroid.split(' ').map(&.capitalize).join("").id}}
end

{% for text in ["example text"] %}
  puts constantize {{text}}
{% end %}

https://carc.in/#/r/6daj

In conclusion…

…is there any way to get a simple, reusable function in macro-land?

To be explicit, I’m trying to define an Enum of every language and a method with a case...when to associate each language with it’s ISO 639-1 code, all from a NamedTuple macro variable that contains each enumeration. So the 3 instances are in the original enum definition (I.E. Languages::English), the #language_code method, and the .from_language_code class method.

You cannot define methods in macro-land.

However, the macro inside output code of a macro will be called, as seen in you last example.

The behavior of your last example is normal behavior by the way: you transform your text to a constant (e.g. classname). However the constant is just not found, as it expends to:

 puts ExampleTest

I don’t understand clearly what would be the expected output code, can you give a proper example?

Here is my actual source for the thing I’m trying to do

Lines 199, 206, and 218 contain the lengthy chain I’m trying to DRY up.

I got two out of 3 working in this commit, but L199 raises unexpected token: constantize:

  • You can create a variable, as long as the macro is belonging between {% begin %} and {% end %}
   {% begin %}
   {% my_var = xxx.gsub(...).split.map(&.capitalize).join %} 
  #...
  {{my_var}}
  #...
  {% end %}

However, in you case, you may want to save into an hash the values once for all. Here some example:

  • You can use constants into the meta-language. You can even use an Hash of Nil => Nil to store any kind of value (this one looks like a hack but I use it in the shards I wrote).

MY_MAP = {} of Nil => Nil

# ... later ...
# register something:

macro register_something(key, value1, value2)
  MY_MAP[key] = {v1: value1, v2: value2}
end

macro finalize # Called at the end of the definition of the object
  do_something_with_my_map
end

macro do_something_with_my_map
  {% for k, v in MY_MAP %}
     # ...
  {% end %}
end

In your case, I would recommend to loop once for all and save the transformation in a hash with key lang[:name]. Then use it later :smile:

{% begin %}
  LANG_DATA = {} of Nil => Nil

  {% for code, lang in language_data %}
  {% LANG_DATA[lang] = lang[:name].gsub #... %}
  {% end %}
# later

   {{ LANG_DATA[lang] }}
{% end %}

I’m not sure if it should work as-it, but I guess it would give you enough tool to ride in macro-land. Happy coding :smile:

1 Like