crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.42k stars 1.62k forks source link

Macro: Parsing string fail when `#{{...}}` is inside the string #5287

Open anykeyh opened 6 years ago

anykeyh commented 6 years ago

Parser got wrong while parsing macro with string

macro do_something(a, b)
  "{{a.id}}#{{b.id}}"
end

puts do_something("1", "2") #Expected: 1#2

This will fail to compile, as macro will ignore #{{b.id}}

However I'm not sure I'm doing it right. I've tried with different position of backslashes \ to escaping it, but nothing is working.

maiha commented 6 years ago

@anykeyh It would be fixed in next release. As a workaround you can use this.

  "%s#%s" % [{{a.id}}, {{b.id}}]
makenowjust commented 6 years ago

Better workaround for performance:

macro do_something(a, b)
  "#{{{a.id}}}##{{{b.id}}}"
end

puts do_something("1", "2") # puts "1#2"
bew commented 6 years ago

Better workaround for performance again! (nothing at runtime):

macro do_something(a, b)
  "{{a.id}}{{'#'.id}}{{b.id}}"
end

puts do_something "1", "2" # => "1#2"
straight-shoota commented 6 years ago

Having stumbled across this issue, I find it only intuitive that "#{{foo}}" is interpreted as a macro expansion prefixed by # and not string interpolation of a tuple. Macro expressions are on a higher level than other code so they should have precedence.

So #5288 is IMO wrong as it provides a workaround for what should be valid by default.

straight-shoota commented 6 years ago

Why is this closed? It's still a valid issue.

anykeyh commented 6 years ago

Sorry, mistake of mine, I thought it has been fixed long time ago 👎

straight-shoota commented 4 years ago

There is another solution: We could completely disable macro expansion inside literals. I don't think that is a necessary feature. There doesn't even seem to be a spec for that, so maybe it's something that just happens to be but nobody really thought about it?

The thing is: There are currently two types of expansions in string literals: string interpolation (#{}) and macro expansion ({{}}). The latter is rarely talked about and probably many developers are not aware of it. It is still applied in some occasions in stdlib and probably also in shards.

If macro expansions were removed from literals, it would make the language a bit simpler. They can easily be replaced by interpolation, either as a macro or at runtime.

# macro expansion
"foo {{ bar.id }}"
# macro string interpolation
{{ "foo #{bar.id}" }}
# runtime interpolation
"foo #{ {{ bar }} }"

The latter has a small performance impact because the string is composed at runtime. But that can be mitigated by using a macro string interpolation. Apart from that, all three variants should be equivalent.