名称: crystal-macros 用户可调用: false 描述: 当在Crystal中使用宏实现编译时元编程时使用,用于代码生成、DSL、编译时计算和抽象语法树操作。 允许工具: [Bash, Read]
Crystal 宏
你是Claude Code,Crystal宏系统和编译时元编程的专家。你专门使用Crystal的编译时执行能力构建强大的抽象、DSL和代码生成系统。
你的核心职责:
- 编写用于代码生成和减少样板代码的宏
- 使用宏方法构建领域特定语言(DSL)
- 实现编译时计算和验证
- 动态生成方法、类和模块
- 在编译时操作抽象语法树(AST)
- 通过宏扩展创建类型安全的抽象
- 构建调试和自省工具
- 实现编译时配置和功能标志
- 生成序列化和反序列化代码
- 设计基于注解的编程模式
宏基础
宏在编译时运行并接收AST节点作为参数。它们可以生成并返回被插入到程序中的代码。
简单宏定义
# 生成方法的基本宏
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 # 生成的方法
puts person.age # 生成的方法
带多个参数的宏
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 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
# 数据库操作在这里
end
宏中的字符串插值
宏使用 {{}} 进行插值,可以生成标识符、字面量和代码。
生成方法名
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
使用字符串操作生成
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
# 生成 pending?, running?, completed?, failed?
define_enum_helpers Status
end
job = Job.new(Status::Pending)
puts job.pending? # true
puts job.running? # false
编译时迭代
宏可以使用 {% for %} 在编译时迭代集合。
迭代数组
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
迭代哈希字面量
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("test@example.com")
puts validator.validate_phone("555-123-4567")
迭代类型方法
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
# 在编译时,这生成 puts 语句
macro list_calculator_methods
log_all_methods Calculator
end
条件编译
使用 {% if %} 基于标志、类型或表达式进行编译时条件判断。
平台特定代码
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
功能标志
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
# 使用编译:crystal build app.cr -Ddebug -Dmetrics
基于类型的条件
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 节点类型
宏接收不同类型的AST节点。理解这些至关重要。
检查AST节点
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])}}
使用标识符
macro create_accessor(name)
# name 是 SymbolLiteral 或 StringLiteral
# 使用 .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
操作字符串字面量
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"
高级宏模式
构建路由DSL
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 :get, "/", :index
route :get, "/about", :about
route :post, "/users", :create_user
def index
"Home Page"
end
def about
"About Page"
end
def create_user
"Create User"
end
def not_found
"404 Not Found"
end
compile_routes
end
自动JSON序列化
macro json_serializable(*fields)
def to_json(builder : JSON::Builder)
builder.object do
{% for field in fields %}
builder.field {{field.stringify}} do
@{{field.id}}.to_json(builder)
end
{% end %}
end
end
def self.from_json(parser : JSON::PullParser)
instance = allocate
{% for field in fields %}
{{field.id}} = nil
{% end %}
parser.read_object do |key|
case key
{% for field in fields %}
when {{field.stringify}}
{{field.id}} = typeof(instance.@{{field.id}}).from_json(parser)
{% end %}
end
end
{% for field in fields %}
instance.@{{field.id}} = {{field.id}}.not_nil!
{% end %}
instance
end
end
class User
def initialize(@name : String, @age : Int32, @email : String)
end
json_serializable name, age, email
end
user = User.new("Alice", 30, "alice@example.com")
json = user.to_json
puts json
编译时配置
macro configure(&block)
{% begin %}
{% config = {} of String => ASTNode %}
{{yield}}
{% for key, value in config %}
{{key.upcase.id}} = {{value}}
{% end %}
{% end %}
end
macro set(key, value)
{% config[key.stringify] = value %}
end
configure do
set :app_name, "MyApp"
set :version, "1.0.0"
set :max_connections, 100
set :debug, true
end
puts APP_NAME # "MyApp"
puts VERSION # "1.0.0"
puts MAX_CONNECTIONS # 100
puts DEBUG # true
宏方法
宏方法在类型上调用,可以访问编译时类型信息。
从类型信息生成方法
class Model
macro inherited
# 当类继承自 Model 时调用
def self.table_name : String
{{@type.name.underscore.id.stringify}}
end
def self.column_names : Array(String)
[
{% for ivar in @type.instance_vars %}
{{ivar.name.stringify}},
{% end %}
]
end
end
end
class User < Model
def initialize(@name : String, @email : String, @age : Int32)
end
end
puts User.table_name # "user"
puts User.column_names # ["name", "email", "age"]
属性自省
class Base
macro generate_initializer
def initialize(
{% for ivar in @type.instance_vars %}
@{{ivar.name}} : {{ivar.type}},
{% end %}
)
end
def to_s(io : IO)
io << "{{@type.name}}("
{% for ivar, index in @type.instance_vars %}
{% if index > 0 %}
io << ", "
{% end %}
io << "{{ivar.name}}="
@{{ivar.name}}.inspect(io)
{% end %}
io << ")"
end
end
end
class Person < Base
@name : String
@age : Int32
@city : String
generate_initializer
end
person = Person.new("Bob", 25, "NYC")
puts person # Person(name="Bob", age=25, city="NYC")
方法委托
macro delegate(*methods, to target)
{% for method in methods %}
def {{method.id}}(*args, **kwargs)
@{{target.id}}.{{method.id}}(*args, **kwargs)
end
def {{method.id}}(*args, **kwargs, &block)
@{{target.id}}.{{method.id}}(*args, **kwargs) { |*yield_args| yield *yield_args }
end
{% end %}
end
class UserRepository
def find(id : Int32)
"User #{id}"
end
def all
["User 1", "User 2"]
end
def create(name : String)
"Created #{name}"
end
end
class UserService
def initialize
@repository = UserRepository.new
end
delegate find, all, create, to: repository
end
service = UserService.new
puts service.find(1)
puts service.all
调试宏
编译时打印
macro debug_print(value)
{{puts value}}
{{value}}
end
# 这会在编译时打印
result = {{debug_print(42 + 8)}}
# 在编译时打印类型信息
macro show_type_info(type)
{% puts "Type: #{type.resolve}" %}
{% puts "Instance vars: #{type.resolve.instance_vars.map(&.name)}" %}
{% puts "Methods: #{type.resolve.methods.map(&.name)}" %}
end
class Example
@x : Int32 = 0
@y : String = ""
def foo
end
def bar
end
end
{{show_type_info(Example)}}
宏扩展检查
# 使用 --no-codegen 标志查看宏扩展
# crystal build --no-codegen app.cr
macro verbose_property(name, type)
{{puts "Generating property #{name} of type #{type}"}}
@{{name}} : {{type}}?
def {{name}} : {{type}}?
{{puts "Generating getter for #{name}"}}
@{{name}}
end
def {{name}}=(value : {{type}})
{{puts "Generating setter for #{name}"}}
@{{name}} = value
end
end
class Config
verbose_property timeout, Int32
verbose_property host, String
end
何时使用此技能
在需要时使用crystal-macros技能:
- 通过代码生成减少样板代码
- 为配置或业务逻辑构建领域特定语言(DSL)
- 生成重复的方法、类或模块
- 实现编译时验证和类型检查
- 创建具有自定义行为的属性定义
- 生成序列化/反序列化代码
- 构建基于注解的编程模式
- 实现自动委托或代理
- 创建编译时配置系统
- 从模式定义生成数据库模型
- 构建具有自定义断言的测试框架
- 实现编译时依赖注入
- 创建类型安全的构建器模式
- 从规范生成API客户端
- 实现面向切面编程模式
最佳实践
- 保持宏简单:将复杂宏分解为更小、可组合的部分
- 记录宏行为:解释宏生成什么代码以及原因
- 使用有意义的名称:宏名称应清晰指示其生成内容
- 验证输入:尽可能在编译时检查宏参数
- 优先使用宏方法:对类型特定逻辑使用宏方法而非顶层宏
- 使用
{{yield}}:将块传递给宏以实现灵活的代码生成 - 用
{{puts}}调试:在宏开发期间打印AST节点和值 - 测试生成代码:验证宏生成的代码按预期工作
- 避免过度使用:仅当益处超过复杂性时使用宏
- 使用类型信息:利用
@type和反射实现强大抽象 - 处理边缘情况:考虑nil值、空集合和类型变化
- 保持可读性:生成代码应与手写代码一样可读
- 谨慎版本控制:宏更改可能破坏下游代码;适当版本化
- 使用条件编译:利用标志进行平台特定或功能特定代码
- 记录扩展:在宏文档中展示扩展代码示例
常见陷阱
- 忘记
.id转换:字面量必须使用.id转换为标识符 - 字符串与符号混淆:知道何时使用 stringify 与字面量插值
- 无限宏递归:递归宏必须具有适当的终止条件
- 作用域问题:宏作用域中的变量与生成代码作用域可能冲突
- 类型解析时机:某些类型信息在早期编译时不可用
- 缺少Nil检查:生成代码可能未正确处理nil
- 硬编码假设:宏假设特定类型结构可能发生变化
- 错误消息不佳:生成代码中的编译错误难以调试
- 过度使用全局状态:宏中的类变量可能导致意外行为
- 未处理空集合:迭代空数组/哈希而未检查
- 模板中的语法错误:宏主体中的无效Crystal语法导致混淆错误
- 类型不匹配:生成代码不匹配预期类型
- 命名空间污染:在全局作用域中生成过多方法或常量
- 平台依赖:未处理宏逻辑中的平台差异
- 循环依赖:宏依赖于依赖于相同宏的类型