Crystal: macro shared state

Created on 29 Nov 2016  路  4Comments  路  Source: crystal-lang/crystal

would be nice if macro can have shared variables, and callback after program readed and macro expanded (macro class_finished or macro program_finished).
this allow things like https://github.com/crystal-lang/crystal/issues/2987 or nice dsl, api generators, based on user usage:

shard

class Api
  macro mapping(tuple)
    {% $data ||= [] of NamedTupleLiteral %}
    {% $data << data %}
  end

  mapping({a : Int32, b : String})
end

macro program_finished
  class Api::Data
    JSON.mapping({
    {% for data in $data %}
       {{data}}
    {% end %}
    )}
  end
end

user program

class UserClass < Api
  mapping({c : Float64})
end
feature draft compiler

Most helpful comment

A long time ago I wasn't sure about this, having stuff change at compile time. However, Elixir does this exact same thing. Well, it's a bit different, they use attributes which are available at compile time, but one can override them and use their previous values to create new ones (I guess mostly because everything's immutable). And then they have __before_compile_.

In fact, in Crystal you can already have shared state. We are just missing the macro at_exit or a better name. For example, this works:

require "json"

class Foo
  MAPPING = {} of Nil => Nil

  macro mapping(**values)
    {% for key, value in values %}
      {% MAPPING[key] = value %}
    {% end %}
  end

  macro close_mapping
    JSON.mapping({{MAPPING}})
  end

  mapping x: Int32, y: Int32
end

class Foo
  mapping z: Int32?
end

class Foo
  close_mapping
end

p Foo.from_json(%<{"x": 1, "y": 2}>)
p Foo.from_json(%<{"x": 1, "y": 2, "z": 3}>)

play

We just need to define the name for this macro. It's a new macro hook, that will be executed, I think, right after all the top level declarations and methods are processed, right before instance vars types are checked, etc.

All 4 comments

A long time ago I wasn't sure about this, having stuff change at compile time. However, Elixir does this exact same thing. Well, it's a bit different, they use attributes which are available at compile time, but one can override them and use their previous values to create new ones (I guess mostly because everything's immutable). And then they have __before_compile_.

In fact, in Crystal you can already have shared state. We are just missing the macro at_exit or a better name. For example, this works:

require "json"

class Foo
  MAPPING = {} of Nil => Nil

  macro mapping(**values)
    {% for key, value in values %}
      {% MAPPING[key] = value %}
    {% end %}
  end

  macro close_mapping
    JSON.mapping({{MAPPING}})
  end

  mapping x: Int32, y: Int32
end

class Foo
  mapping z: Int32?
end

class Foo
  close_mapping
end

p Foo.from_json(%<{"x": 1, "y": 2}>)
p Foo.from_json(%<{"x": 1, "y": 2, "z": 3}>)

play

We just need to define the name for this macro. It's a new macro hook, that will be executed, I think, right after all the top level declarations and methods are processed, right before instance vars types are checked, etc.

I think before_compile is a good name to go.

There was an issue that since the constant can't change the value it reference to, then for value types we need to boxed them. But even with that limitation I think it's good to add this.

_Also someone can hack around with run command and save state in temp files and read them before compiling_ 馃槰 _... someone will eventually do it ... :-P_

Actually, a name in the past tense would be optimal, so it's on par with included, inherited, extended. Ideas?

Maybe compiled ?

Edit: actually finished works well too!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nabeelomer picture nabeelomer  路  3Comments

costajob picture costajob  路  3Comments

ArthurZ picture ArthurZ  路  3Comments

cjgajard picture cjgajard  路  3Comments

jhass picture jhass  路  3Comments