Skip to content

Commit

Permalink
Macros: rename argify to splat. Added double_splat method. Fixes #3630
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Dec 3, 2016
1 parent 10ad407 commit d440045
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 47 deletions.
40 changes: 30 additions & 10 deletions spec/compiler/macro/macro_methods_spec.cr
Expand Up @@ -564,15 +564,15 @@ describe "macro methods" do
assert_macro "", %({{[1, 2, 3].last}}), [] of ASTNode, "3"
end

it "executes argify" do
assert_macro "", %({{[1, 2, 3].argify}}), [] of ASTNode, "1, 2, 3"
it "executes splat" do
assert_macro "", %({{[1, 2, 3].splat}}), [] of ASTNode, "1, 2, 3"
end

it "executes argify with symbols and strings" do
assert_macro "", %({{[:foo, "hello", 3].argify}}), [] of ASTNode, %(:foo, "hello", 3)
it "executes splat with symbols and strings" do
assert_macro "", %({{[:foo, "hello", 3].splat}}), [] of ASTNode, %(:foo, "hello", 3)
end

it "executes argify with splat" do
it "executes splat with splat" do
assert_macro "", %({{*[1, 2, 3]}}), [] of ASTNode, "1, 2, 3"
end

Expand Down Expand Up @@ -730,6 +730,14 @@ describe "macro methods" do
it "executes double splat" do
assert_macro "", %({{**{1 => 2, 3 => 4}}}), [] of ASTNode, "1 => 2, 3 => 4"
end

it "executes double splat" do
assert_macro "", %({{{1 => 2, 3 => 4}.double_splat}}), [] of ASTNode, "1 => 2, 3 => 4"
end

it "executes double splat with arg" do
assert_macro "", %({{{1 => 2, 3 => 4}.double_splat(", ")}}), [] of ASTNode, "1 => 2, 3 => 4, "
end
end

describe "named tuple literal methods" do
Expand Down Expand Up @@ -784,6 +792,14 @@ describe "macro methods" do
it "executes double splat" do
assert_macro "", %({{**{a: 1, "foo bar": 2}}}), [] of ASTNode, %(a: 1, "foo bar": 2)
end

it "executes double splat" do
assert_macro "", %({{{a: 1, "foo bar": 2}.double_splat}}), [] of ASTNode, %(a: 1, "foo bar": 2)
end

it "executes double splat with arg" do
assert_macro "", %({{{a: 1, "foo bar": 2}.double_splat(", ")}}), [] of ASTNode, %(a: 1, "foo bar": 2, )
end
end

describe "tuple methods" do
Expand Down Expand Up @@ -867,15 +883,19 @@ describe "macro methods" do
assert_macro "", %({{ {1, 2, 3}.last }}), [] of ASTNode, "3"
end

it "executes argify" do
assert_macro "", %({{ {1, 2, 3}.argify }}), [] of ASTNode, "1, 2, 3"
it "executes splat" do
assert_macro "", %({{ {1, 2, 3}.splat }}), [] of ASTNode, "1, 2, 3"
end

it "executes splat with arg" do
assert_macro "", %({{ {1, 2, 3}.splat(", ") }}), [] of ASTNode, "1, 2, 3, "
end

it "executes argify with symbols and strings" do
assert_macro "", %({{ {:foo, "hello", 3}.argify }}), [] of ASTNode, %(:foo, "hello", 3)
it "executes splat with symbols and strings" do
assert_macro "", %({{ {:foo, "hello", 3}.splat }}), [] of ASTNode, %(:foo, "hello", 3)
end

it "executes argify with splat" do
it "executes splat with splat" do
assert_macro "", %({{ *{1, 2, 3} }}), [] of ASTNode, "1, 2, 3"
end

Expand Down
34 changes: 22 additions & 12 deletions src/compiler/crystal/macros.cr
Expand Up @@ -477,7 +477,12 @@ module Crystal::Macros

# Returns a `MacroId` with all of this array's elements joined
# by commas.
def argify : MacroId
#
# If *trailing_string* is given, it will be appended to
# the result unless this array is empty. This lets you
# splat an array and optionally write a trailing comma
# if needed.
def splat(trailing_string : StringLiteral = nil) : MacroId
end

# Similar to `Array#empty?`
Expand Down Expand Up @@ -608,6 +613,16 @@ module Crystal::Macros
# This refers to the part before brackets in `MyHash{'a' => 1, 'b' => 2}`
def type : Path | Nop
end

# Returns a `MacroId` with all of this hash elements joined
# by commas.
#
# If *trailing_string* is given, it will be appended to
# the result unless this hash is empty. This lets you
# splat a hash and optionally write a trailing comma
# if needed.
def double_splat(trailing_string : StringLiteral = nil) : MacroId
end
end

# A named tuple literal.
Expand Down Expand Up @@ -636,6 +651,10 @@ module Crystal::Macros
def map : ArrayLiteral
end

# Similar to `HashLiteral#double_splat`
def double_splat(trailing_string : StringLiteral = nil) : MacroId
end

# Similar to `NamedTuple#[]`
def [](key : ASTNode) : ASTNode
end
Expand Down Expand Up @@ -683,18 +702,9 @@ module Crystal::Macros
end

# A tuple literal.
#
# It's macro methods are the same as `ArrayLiteral`
class TupleLiteral < ASTNode
# Similar to `Tuple#empty?`
def empty? : BoolLiteral
end

# Similar to `Tuple#size`
def size : NumberLiteral
end

# Similar to `Tuple#[]`
def [](index : NumberLiteral) : ASTNode
end
end

# A fictitious node representing a variable or instance
Expand Down
25 changes: 2 additions & 23 deletions src/compiler/crystal/macros/interpreter.cr
Expand Up @@ -427,34 +427,13 @@ module Crystal

def visit(node : Splat)
node.exp.accept self
@last = @last.interpret("argify", [] of ASTNode, nil, self)
@last = @last.interpret("splat", [] of ASTNode, nil, self)
false
end

def visit(node : DoubleSplat)
node.exp.accept self

last = @last
case last
when HashLiteral
@last = MacroId.new(
last.entries.join(", ") do |entry|
"#{entry.key} => #{entry.value}"
end
)
when NamedTupleLiteral
@last = MacroId.new(
last.entries.join(", ") do |entry|
if Symbol.needs_quotes?(entry.key)
"#{entry.key.inspect}: #{entry.value}"
else
"#{entry.key}: #{entry.value}"
end
end
)
else
node.raise "argument to ** must be HashLiteral or NamedTuple, not #{last.class_desc}"
end
@last = @last.interpret("double_splat", [] of ASTNode, nil, self)
false
end

Expand Down
68 changes: 66 additions & 2 deletions src/compiler/crystal/macros/methods.cr
Expand Up @@ -686,6 +686,24 @@ module Crystal
interpreter.accept block.body
end
}
when "double_splat"
case args.size
when 0
to_double_splat
when 1
interpret_one_arg_method(method, args) do |arg|
if entries.empty?
to_double_splat
else
unless arg.is_a?(Crystal::StringLiteral)
arg.raise "argument to double_splat must be a StringLiteral, not #{arg.class_desc}"
end
to_double_splat(arg.value)
end
end
else
wrong_number_of_arguments "double_splat", args.size, 0..1
end
when "[]"
case args.size
when 1
Expand Down Expand Up @@ -721,6 +739,12 @@ module Crystal
super
end
end

private def to_double_splat(trailing_string = "")
MacroId.new(entries.join(", ") do |entry|
"#{entry.key} => #{entry.value}"
end + trailing_string)
end
end

class NamedTupleLiteral
Expand Down Expand Up @@ -751,6 +775,24 @@ module Crystal
interpreter.accept block.body
end
}
when "double_splat"
case args.size
when 0
to_double_splat
when 1
interpret_one_arg_method(method, args) do |arg|
if entries.empty?
to_double_splat
else
unless arg.is_a?(Crystal::StringLiteral)
arg.raise "argument to double_splat must be a StringLiteral, not #{arg.class_desc}"
end
to_double_splat(arg.value)
end
end
else
wrong_number_of_arguments "double_splat", args.size, 0..1
end
when "[]"
case args.size
when 1
Expand Down Expand Up @@ -803,6 +845,16 @@ module Crystal
super
end
end

private def to_double_splat(trailing_string = "")
MacroId.new(entries.join(", ") do |entry|
if Symbol.needs_quotes?(entry.key)
"#{entry.key.inspect}: #{entry.value}"
else
"#{entry.key}: #{entry.value}"
end
end + trailing_string)
end
end

class TupleLiteral
Expand Down Expand Up @@ -1760,9 +1812,21 @@ private def intepret_array_or_tuple_method(object, klass, method, args, block, i
interpreter.accept(block.body).truthy?
end)
end
when "argify"
object.interpret_argless_method(method, args) do
when "splat"
case args.size
when 0
Crystal::MacroId.new(object.elements.join ", ")
when 1
object.interpret_one_arg_method(method, args) do |arg|
if object.elements.empty?
Crystal::MacroId.new("")
else
unless arg.is_a?(Crystal::StringLiteral)
arg.raise "argument to splat must be a StringLiteral, not #{arg.class_desc}"
end
Crystal::MacroId.new((object.elements.join ", ") + arg.value)
end
end
end
when "empty?"
object.interpret_argless_method(method, args) { Crystal::BoolLiteral.new(object.elements.empty?) }
Expand Down

0 comments on commit d440045

Please sign in to comment.