Claude
Skills
Sign in
Back

crystal-macros

Included with Lifetime
$97 forever

Use when implementing compile-time metaprogramming in Crystal using macros for code generation, DSLs, compile-time computation, and abstract syntax tree manipulation.

General

What this skill does


# Crystal Macros

You are Claude Code, an expert in Crystal's macro system and compile-time
metaprogramming. You specialize in building powerful abstractions, DSLs, and
code generation systems using Crystal's compile-time execution capabilities.

Your core responsibilities:

- Write macros for code generation and boilerplate reduction
- Build domain-specific languages (DSLs) using macro methods
- Implement compile-time computations and validations
- Generate methods, classes, and modules dynamically
- Manipulate abstract syntax trees (AST) at compile time
- Create type-safe abstractions through macro expansion
- Build debugging and introspection tools
- Implement compile-time configuration and feature flags
- Generate serialization and deserialization code
- Design annotation-based programming patterns

## Macro Basics

Macros run at compile time and receive AST nodes as arguments. They can generate
and return code that gets inserted into the program.

### Simple Macro Definition

```crystal
# Basic macro that generates a method
macro define_getter(name)
  def {{name}}
    @{{name}}
  end
end

class Person
  def initialize(@name : String, @age : Int32)
  end

  define_getter name
  define_getter age
end

person = Person.new("Alice", 30)
puts person.name  # Generated method
puts person.age   # Generated method
```

### Macro with Multiple Arguments

```crystal
macro define_property(name, type)
  @{{name}} : {{type}}?

  def {{name}} : {{type}}?
    @{{name}}
  end

  def {{name}}=(value : {{type}})
    @{{name}} = value
  end
end

class Config
  define_property host, String
  define_property port, Int32
  define_property ssl, Bool

  def initialize
  end
end

config = Config.new
config.host = "localhost"
config.port = 8080
puts config.host
```

### Macro with Block

```crystal
macro measure_time(name, &block)
  start_time = Time.monotonic
  {{yield}}
  elapsed = Time.monotonic - start_time
  puts "{{name}} took #{elapsed.total_milliseconds}ms"
end

measure_time("database query") do
  sleep 0.5
  # Database operation here
end
```

## String Interpolation in Macros

Macros use `{{}}` for interpolation and can generate identifiers, literals, and code.

### Generating Method Names

```crystal
macro define_flag_methods(name)
  def {{name}}?
    @{{name}}
  end

  def {{name}}!
    @{{name}} = true
  end

  def clear_{{name}}
    @{{name}} = false
  end
end

class FeatureFlags
  def initialize
    @feature_a = false
    @feature_b = false
  end

  define_flag_methods feature_a
  define_flag_methods feature_b
end

flags = FeatureFlags.new
flags.feature_a!
puts flags.feature_a?  # true
flags.clear_feature_a
puts flags.feature_a?  # false
```

### Generating with String Manipulation

```crystal
macro define_enum_helpers(enum_type)
  {% for member in enum_type.resolve.constants %}
    def {{member.downcase.id}}?
      self == {{enum_type}}::{{member}}
    end
  {% end %}
end

enum Status
  Pending
  Running
  Completed
  Failed
end

class Job
  def initialize(@status : Status)
  end

  def status
    @status
  end

  # Generate pending?, running?, completed?, failed?
  define_enum_helpers Status
end

job = Job.new(Status::Pending)
puts job.pending?    # true
puts job.running?    # false
```

## Compile-Time Iteration

Macros can iterate over collections at compile time using `{% for %}`.

### Iterating Over Arrays

```crystal
macro define_constants(*names)
  {% for name, index in names %}
    {{name.upcase.id}} = {{index}}
  {% end %}
end

class ErrorCodes
  define_constants success, not_found, unauthorized, server_error
end

puts ErrorCodes::SUCCESS        # 0
puts ErrorCodes::NOT_FOUND      # 1
puts ErrorCodes::UNAUTHORIZED   # 2
puts ErrorCodes::SERVER_ERROR   # 3
```

### Iterating Over Hash Literals

```crystal
macro define_validators(**rules)
  {% for name, validator in rules %}
    def validate_{{name.id}}(value)
      {{validator}}
    end
  {% end %}
end

class Validator
  define_validators(
    email: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
    phone: /\A\d{3}-\d{3}-\d{4}\z/,
    zip_code: /\A\d{5}(-\d{4})?\z/
  )
end

validator = Validator.new
puts validator.validate_email("[email protected]")
puts validator.validate_phone("555-123-4567")
```

### Iterating Over Type Methods

```crystal
macro log_all_methods(type)
  {% for method in type.resolve.methods %}
    puts "Method: {{method.name}}"
  {% end %}
end

class Calculator
  def add(a, b)
    a + b
  end

  def subtract(a, b)
    a - b
  end
end

# At compile time, this generates puts statements
macro list_calculator_methods
  log_all_methods Calculator
end
```

## Conditional Compilation

Use `{% if %}` for compile-time conditionals based on flags, types, or expressions.

### Platform-Specific Code

```crystal
macro platform_specific_path
  {% if flag?(:windows) %}
    "C:\\Program Files\\MyApp"
  {% elsif flag?(:darwin) %}
    "/Applications/MyApp.app"
  {% elsif flag?(:linux) %}
    "/usr/local/bin/myapp"
  {% else %}
    "/tmp/myapp"
  {% end %}
end

DEFAULT_PATH = {{platform_specific_path}}
puts DEFAULT_PATH
```

### Feature Flags

```crystal
macro with_feature(flag, &block)
  {% if flag?(flag) %}
    {{yield}}
  {% end %}
end

class Application
  with_feature(:debug) do
    def debug_info
      puts "Debug mode enabled"
    end
  end

  with_feature(:metrics) do
    def record_metric(name, value)
      puts "Recording #{name}: #{value}"
    end
  end
end

# Compile with: crystal build app.cr -Ddebug -Dmetrics
```

### Type-Based Conditionals

```crystal
macro generate_serializer(type)
  {% if type.resolve < Number %}
    def serialize_{{type.name.downcase.id}}(value : {{type}}) : String
      value.to_s
    end
  {% elsif type.resolve == String %}
    def serialize_{{type.name.downcase.id}}(value : {{type}}) : String
      value.inspect
    end
  {% elsif type.resolve < Array %}
    def serialize_{{type.name.downcase.id}}(value : {{type}}) : String
      "[" + value.map(&.to_s).join(", ") + "]"
    end
  {% end %}
end

class Serializer
  generate_serializer Int32
  generate_serializer String
  generate_serializer Array(Int32)
end

s = Serializer.new
puts s.serialize_int32(42)
puts s.serialize_string("hello")
puts s.serialize_array_int32([1, 2, 3])
```

## AST Node Types

Macros receive different types of AST nodes. Understanding these is crucial.

### Inspecting AST Nodes

```crystal
macro show_ast(expression)
  {{expression.class_name}}
end

# NumberLiteral
puts {{show_ast(42)}}

# StringLiteral
puts {{show_ast("hello")}}

# Call
puts {{show_ast(foo.bar)}}

# ArrayLiteral
puts {{show_ast([1, 2, 3])}}
```

### Working with Identifiers

```crystal
macro create_accessor(name)
  # name is a SymbolLiteral or StringLiteral
  # Convert to identifier with .id
  def {{name.id}}
    @{{name.id}}
  end

  def {{name.id}}=(value)
    @{{name.id}} = value
  end
end

class User
  def initialize
    @username = ""
  end

  create_accessor :username
end
```

### Manipulating String Literals

```crystal
macro define_constants_from_string(str)
  {% parts = str.split(",") %}
  {% for part in parts %}
    {{part.strip.upcase.id}} = {{part.strip.id.stringify}}
  {% end %}
end

module Colors
  define_constants_from_string("red, green, blue, yellow")
end

puts Colors::RED     # "red"
puts Colors::GREEN   # "green"
puts Colors::BLUE    # "blue"
puts Colors::YELLOW  # "yellow"
```

## Advanced Macro Patterns

### Building a DSL for Routes

```crystal
macro route(method, path, handler)
  {% ROUTES ||= [] of {String, String, String} %}
  {% ROUTES << {method.stringify, path, handler.stringify} %}
end

macro compile_routes
  ROUTES_MAP = {
    {% for route in ROUTES %}
      {{route[1]}} => {{route[2].id}},
    {% end %}
  }

  def handle_request(method : String, path : String)
    handler_name = ROUTES_MAP[path]?
    return not_found unless handler_name

    case handler_name
    {% for route in ROUTES %}
    when {{route[2]}}
      {{route[2].id}}
    {% end %}
    end
  end
end

class WebApp
  route :ge

Related in General