Search code examples
scalascala-macrosscala-3

Can a Scala 3 macro introduce an identifier?


In Scala 3 (3.1.3 right now, if it matters), I'm writing a parser that requires some rule/grammar processing and I'd like to write a macro that lets me define the alternates for a non-terminal and define the non-terminal so that it's available for use in other rules. Everything is written now with the non-terminals as Strings and the terminals as Chars, so a set of rules for a grammar of tuples of as might be:

new Grammar() {
  val rules = Set(
    Rule("tuple", List('(', "as", ')')),
    Rule("as", List('a', "more")),
    Rule("as", Nil),
    Rule("more", List(',', "as")),
    Rule("more", Nil),
  )

What I'd like to do instead is use some macro magic to make it more like:

new Grammar() {
  rule(tuple, List('(', as, ')')
  rule(as, List('a', more), Nil)
  rule(more, List(',', as), Nil)
}

where, instead of using Strings for non-terminals, I could use identifiers, and at compile time, the rule macro would do something like turn the second into

new Grammar() {
  val rules = mutable.Set()

  val tuple = NonTerminal("tuple")
  val as = NonTerminal("as")
  val more = NonTerminal("more")
  rules.add(Rule(tuple, List('(', as, ')')))
  rules.add(Rule(as, List('a', more)))
  rules.add(Rule(as, Nil))
  rules.add(Rule(more, List(',', as)))
  rules.add(Rule(more, Nil)
}

Is such a thing possible with Scala 3 macros in their current state, or is it not possible for a macro to take a not-yet-defined identifier as an argument and provide a definition for it?


Solution

  • No, the argument you pass to the macro must be a correct Scala code.

    You can have a 'god-prefix' for non-terminals like (by using Dynamic):

    new Grammar() {
      rule(?.tuple, List('(', ?.as, ')')
      rule(?.as, List('a', ?.more), Nil)
      rule(?.more, List(',', ?.as), Nil)
    }
    

    But I'm not sure if that's better than Strings.