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 String
s and the terminals as Char
s, so a set of rules for a grammar of tuples of a
s 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 String
s 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?
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.