/CodeSculptor.jl

Take Julia AST's apart and put them back together again

Primary LanguageJuliaMIT LicenseMIT

CodeSculptor

Split many kinds of expressions into detailed parts, play with them, and put them back together again. Think of it as a really souped-up version of splitdef() and splitarg() from MacroTools.jl:

using CodeSculptor

expr = :( f(i::Int) = i )
def = SplitFunction(expr)

# Make the function generic
def.args[1].type = :I
push!(def.where_params, SplitType(:( I<:Integer )))

# Modify metadata
def.doc_string = "Does something with any integer"
def.inline = true

# Put it back together and evaluate
eval(combine_expr(def))

# Generate a function call of f
def.body = nothing
def.args[1] = SplitArg(5, false)
@assert eval(combine_expr(def)) === 5

# Check syntax by looking for a null result
is_type_expr(e) = (SplitType(e) !== nothing)
@assert is_type_expr(:( C <: Integer ))
@assert is_type_expr(:D)
@assert !is_type_expr(:( 3 + 8 ))
  • SplitFunction for function calls and definitions
  • SplitMacro for macro invocations
  • SplitMeta for metadata around a definition, such as a docstring or @inline
  • SplitType for type declarations (like C{R, T<:Integer} <: B)
  • SplitArg for function arguments (like a::Int...)

Each field on the SplitX types is documented with comments.

Each split type can return nothing when constructed, if the expression fails to parse. The constructors can also run in a less strict mode by passing false into the constructor, for example to stop SplitFunction from verifying that a function name has valid syntax.

Each of them can be deep-copied by constructing with a source instance to copy from. Some of them offer extra flags to skip copying particular parts of the AST.

Helper Functions

  • is_scopable_name(expr) checks that the expression is either a Symbol or a series of Symbols separated by a . expression. Optionally allows type parameters at the end, like A.B{C}.
  • expr_deepcopy(e) works like deepcopy, but without copying special literals/interpolated references (like a Module).
  • visit_exprs(e) is like MacroTools.postwalk(), but without modifying anything and with extra provided data that tells you exactly where you are in the original AST.
  • unescape(e) and unescape_deep(e) help remove esc() from expressions.

Operators

There are some tools to map between operators and assignments (e.x. + and +=).

  • ASSIGNMENT_INNER_OP maps assignment to operator
  • ASSIGNMENT_WITH_OP maps operator to assignment
  • compute_op(s::Union{Symbol, Val}, a, b) computes an assignment as an operator
    • For example, compute_op(:+=, 3, 4) returns compute_op(Val(:+=), 3, 4) returns 7.

TODO

  • SplitField for struct fields (optionally supporting assigned initial values)
  • Break apart SplitFunction into SplitCall, SplitSignature, and SplitFuncDef.