[ANN] Expronicon.jl - meta programming made easy

I created a new package Expronicon.jl to wrap up some meta-programming tools on Julia’s Expr I wrote in the past year using MLStyle The goal of Expronicon is to serve as a stdlib for MLStyle for meta programming Julia Expr

This package provides a few tools as following

Syntax Types

In many other meta programming packages, such as MacroTools, ExprTools etc. one often use a function called split_function to split function components into a Dict for easy manipulation and construction, Expronicon moves one step further, it defines several more types for easy manipulation:

  1. JLFunction: similar to what generated by split_function but is a Julia struct, this allows pretty printing and method dispatches:

  1. JLStruct: represents a normal Julia struct syntax

  1. JLKwStruct: represents keyword struct syntax (similar to JLStruct)

  1. JLIfElse: represent Julia ifelse statements, this uses a dictionary to store conditions and their corresponding code block

Construct JLIfElse object

One can construct an ifelse as following

julia> jl = JLIfElse()
nothing

julia> jl.map[:(foo(x))] = :(x = 1 + 1)
:(x = 1 + 1)

julia> jl.map[:(goo(x))] = :(y = 1 + 2)
:(y = 1 + 2)

julia> jl.otherwise = :(error("abc"))
:(error("abc"))

julia> jl
if foo(x)
    x = 1 + 1
elseif goo(x)
    y = 1 + 2
else
    error("abc")
end

Generate the Julia Expr object

to generate the corresponding Expr object, one can call codegen_ast.

julia> codegen_ast(jl)
:(if foo(x)
      x = 1 + 1
  elseif goo(x)
      y = 1 + 2
  else
      error("abc")
  end)
  1. JLMatch

JLMatch describes a Julia pattern match expression defined by
MLStyle. It allows
one to construct such expression by simply assign each code block
to the corresponding pattern expression.

Example

One can construct a MLStyle pattern matching expression
easily by assigning the corresponding pattern and its result
to the map field.

julia> jl = JLMatch(:x)
#= line 0 =#
nothing

julia> jl = JLMatch(:x)
#= line 0 =#
nothing

julia> jl.map[1] = true
true

julia> jl.map[2] = :(sin(x))
:(sin(x))

julia> jl
#= line 0 =#
@match x begin
    1 => true
    2 => sin(x)
    _ =>     nothing
end

to generate the corresponding Julia Expr object, one can call codegen_ast.

julia> codegen_ast(jl)
:(let
      true
      var"##return#263" = nothing
      var"##265" = x
      if var"##265" isa Int64
          #= line 0 =#
          if var"##265" === 1
              var"##return#263" = let
                      true
                  end
              #= unused:1 =# @goto var"####final#264#266"
          end
          #= line 0 =#
          if var"##265" === 2
              var"##return#263" = let
                      sin(x)
                  end
              #= unused:1 =# @goto var"####final#264#266"
          end
      end
      #= line 0 =#
      begin
          var"##return#263" = let
                  nothing
              end
          #= unused:1 =# @goto var"####final#264#266"
      end
      (error)("matching non-exhaustive, at #= line 0 =#")
      #= unused:1 =# @label var"####final#264#266"
      var"##return#263"
  end)

Others

other features include transformations, analysis path, pretty printings etc.

18 Likes

That looks great, thank you!

I have recently struggled a lot with nested quotes while turning a function into a generated one by a macro, and I had the feeling that I need a higher level API.

Based on the experience I will stay away from manipulating the AST for as long as possible, but next time I must do it, I will definitely learn to use MLStyle and Expronicon! :slight_smile:

As the stdlib for MLStyle, we support using syntax type for pattern matching now

using MLStyle
using Expronicon

f = @λ begin
   JLFunction(;name=:foo, args) => (args, )
   JLFunction(;name=:boo, args) => (args, )
   _ => nothing
end

ex_foo = @expr function foo(x::Int, y::T) where {T <: Real}
    x + y
end

ex_boo = @expr function foo(x::Int)
    x
end

this gives

julia> f(ex_foo)
(Any[:(x::Int), :(y::T)],)

julia> f(ex_boo)
(Any[:(x::Int)],)

You can use most of the built-in syntax types (there is only JLMatch doesn’t support this since it’s mainly used for code generation) as your expression template to match using MLStyle. If you define your own syntax type, you can also support pattern matching via @syntax_pattern .


Except this, I have added a bunch of new tools into Expronicon as well as a quick start guide for metaprogramming in Julia: https://rogerluo.dev/Expronicon.jl/dev/

Now some of my other packages have been rewritten using Expronicon. The test coverage has been pushed to 95%, It should be stable enough to provide you a modern metaprogramming experience along with MLStyle now.

2 Likes