Skip to content

Commit

Permalink
Merge pull request #2724 from crystal-lang/feature/macros_in_lib
Browse files Browse the repository at this point in the history
Compiler: allow using macros inside lib declarations. Part of #2710
jhass committed Jun 2, 2016

Verified

This commit was signed with the committer’s verified signature. The key has expired.
2 parents 5fcfdd7 + 6d540a7 commit 93ac92f
Showing 6 changed files with 108 additions and 21 deletions.
6 changes: 6 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
@@ -644,6 +644,12 @@ describe "Parser" do

it_parses "fun foo(x : Int32) : Int64\nx\nend", FunDef.new("foo", [Arg.new("x", restriction: "Int32".path)], "Int64".path, body: "x".var)

it_parses "lib LibC; {{ 1 }}; end", LibDef.new("LibC", body: [MacroExpression.new(1.int32)] of ASTNode)
it_parses "lib LibC; {% if 1 %}2{% end %}; end", LibDef.new("LibC", body: [MacroIf.new(1.int32, MacroLiteral.new("2"))] of ASTNode)

it_parses "lib LibC; struct Foo; {{ 1 }}; end; end", LibDef.new("LibC", body: StructDef.new("Foo", Expressions.from([MacroExpression.new(1.int32)] of ASTNode)))
it_parses "lib LibC; struct Foo; {% if 1 %}2{% end %}; end; end", LibDef.new("LibC", body: StructDef.new("Foo", Expressions.from([MacroIf.new(1.int32, MacroLiteral.new("2"))] of ASTNode)))

it_parses "1 .. 2", RangeLiteral.new(1.int32, 2.int32, false)
it_parses "1 ... 2", RangeLiteral.new(1.int32, 2.int32, true)

26 changes: 26 additions & 0 deletions spec/compiler/type_inference/lib_spec.cr
Original file line number Diff line number Diff line change
@@ -819,4 +819,30 @@ describe "Type inference: lib" do
LibFoo.foo
)) { |mod| mod.nil }
end

it "can use macros inside lib" do
assert_type(%(
lib LibFoo
{% begin %}
fun foo : Int32
{% end %}
end
LibFoo.foo
)) { int32 }
end

it "can use macros inside struct" do
assert_type(%(
lib LibFoo
struct Foo
{% begin %}
x : Int32
{% end %}
end
end
LibFoo::Foo.new.x
)) { int32 }
end
end
20 changes: 18 additions & 2 deletions src/compiler/crystal/macros/macros.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module Crystal
# How code generated by macro should be parsed
enum MacroExpansionMode
Normal = 0
Lib = 1
StructOrUnion = 2
end

class Program
def expand_macro(a_macro : Macro, call : Call, scope : Type, type_lookup : Type?)
macro_expander.expand a_macro, call, scope, type_lookup || scope
@@ -8,8 +15,17 @@ module Crystal
macro_expander.expand node, scope, type_lookup || scope, free_vars
end

def parse_macro_source(expanded_macro, the_macro, node, vars, inside_def = false, inside_type = false, inside_exp = false)
parse_macro_source expanded_macro, the_macro, node, vars, inside_def, inside_type, inside_exp, &.parse
def parse_macro_source(expanded_macro, the_macro, node, vars, inside_def = false, inside_type = false, inside_exp = false, mode : MacroExpansionMode = MacroExpansionMode::Normal)
parse_macro_source(expanded_macro, the_macro, node, vars, inside_def, inside_type, inside_exp) do |parser|
case mode
when .lib?
parser.parse_lib_body
when .struct_or_union?
parser.parse_struct_or_union_body
else
parser.parse
end
end
end

def parse_macro_source(expanded_macro, the_macro, node, vars, inside_def = false, inside_type = false, inside_exp = false)
13 changes: 10 additions & 3 deletions src/compiler/crystal/semantic/base_type_visitor.cr
Original file line number Diff line number Diff line change
@@ -571,17 +571,24 @@ module Crystal
true
end

def expand_macro(the_macro, node)
def expand_macro(the_macro, node, mode = nil)
begin
expanded_macro = yield
rescue ex : Crystal::Exception
node.raise "expanding macro", ex
end

mode ||= if @lib_def_pass > 0
MacroExpansionMode::Lib
else
MacroExpansionMode::Normal
end

generated_nodes = @mod.parse_macro_source(expanded_macro, the_macro, node, Set.new(@vars.keys),
inside_def: !!@typed_def,
inside_type: !current_type.is_a?(Program),
inside_exp: @exp_nest > 0,
mode: mode,
)

if node_doc = node.doc
@@ -633,7 +640,7 @@ module Crystal
expand_inline_macro node
end

def expand_inline_macro(node)
def expand_inline_macro(node, mode = nil)
if expanded = node.expanded
begin
expanded.accept self
@@ -645,7 +652,7 @@ module Crystal

the_macro = Macro.new("macro_#{node.object_id}", [] of Arg, node).at(node.location)

generated_nodes = expand_macro(the_macro, node) do
generated_nodes = expand_macro(the_macro, node, mode: mode) do
@mod.expand_macro node, (@scope || current_type), @type_lookup, @free_vars
end

6 changes: 6 additions & 0 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
@@ -1002,6 +1002,12 @@ module Crystal
false
end

def visit(node : MacroIf | MacroFor | MacroExpression)
@type_inference.expand_inline_macro(node, mode: MacroExpansionMode::StructOrUnion)
node.expanded.not_nil!.accept self
false
end

def visit(node : ASTNode)
true
end
58 changes: 42 additions & 16 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
@@ -872,19 +872,9 @@ module Crystal
when :"{"
parse_hash_or_tuple_literal
when :"{{"
macro_exp = parse_macro_expression
check_macro_expression_end
next_token
MacroExpression.new(macro_exp)
parse_percent_macro_expression
when :"{%"
macro_control = parse_macro_control(@line_number, @column_number)
if macro_control
check :"%}"
next_token_skip_space
macro_control
else
unexpected_token_in_atomic
end
parse_percent_macro_control
when :"::"
parse_ident_or_global_call
when :"->"
@@ -2693,6 +2683,13 @@ module Crystal
end
end

def parse_percent_macro_expression
macro_exp = parse_macro_expression
check_macro_expression_end
next_token
MacroExpression.new(macro_exp)
end

def parse_macro_expression
next_token_skip_space_or_newline
parse_expression_inside_macro
@@ -2705,6 +2702,17 @@ module Crystal
check :"}"
end

def parse_percent_macro_control
macro_control = parse_macro_control(@line_number, @column_number)
if macro_control
check :"%}"
next_token_skip_space
macro_control
else
unexpected_token_in_atomic
end
end

def parse_macro_control(start_line, start_column, macro_state = Token::MacroState.default)
next_token_skip_space_or_newline

@@ -3405,9 +3413,9 @@ module Crystal
def parse_ifdef_body(mode)
case mode
when :lib
parse_lib_body
parse_lib_body_expressions
when :struct_or_union
parse_struct_or_union_body
parse_struct_or_union_body_expressions
else
parse_expressions
end
@@ -4553,7 +4561,7 @@ module Crystal
name_column_number = @token.column_number
next_token_skip_statement_end

body = parse_lib_body
body = parse_lib_body_expressions

check_ident :end
next_token_skip_space
@@ -4562,6 +4570,11 @@ module Crystal
end

def parse_lib_body
next_token_skip_statement_end
Expressions.from(parse_lib_body_expressions)
end

private def parse_lib_body_expressions
expressions = [] of ASTNode
while true
skip_statement_end
@@ -4630,6 +4643,10 @@ module Crystal

skip_statement_end
ExternalVar.new(name, type, real_name)
when :"{{"
parse_percent_macro_expression
when :"{%"
parse_percent_macro_control
else
unexpected_token
end
@@ -4813,14 +4830,19 @@ module Crystal
next_token_skip_space_or_newline
name = check_const
next_token_skip_statement_end
body = parse_struct_or_union_body
body = parse_struct_or_union_body_expressions
check_ident :end
next_token_skip_space

klass.new name, Expressions.from(body)
end

def parse_struct_or_union_body
next_token_skip_statement_end
Expressions.from(parse_struct_or_union_body_expressions)
end

private def parse_struct_or_union_body_expressions
exps = [] of ASTNode

while true
@@ -4843,6 +4865,10 @@ module Crystal
else
parse_struct_or_union_fields exps
end
when :"{{"
exps << parse_percent_macro_expression
when :"{%"
exps << parse_percent_macro_control
when :";", :NEWLINE
skip_statement_end
else

0 comments on commit 93ac92f

Please sign in to comment.