I think if I just copy bits of the current code in it might be the easiest. I have the following macro:
macro grammar(name, definitions)
parsers = [:+, :*, :?, :|, :-, :^, :list, :integer, :float]
mapped_parsers = map_symbol_to_function(parsers)
return parseGrammar(name, definitions, ParserData(mapped_parsers))
end
where parseGrammar goes through the definitions and converts them into actual code. Its output is an Expr tree. The key part here, is that per line, another function `parseDefinition` is recursively called. The simplified version of it is:
function parseDefinition(name::String, ex::Expr, pdata::ParserData)
# ...
# look up the symbol in the expression tree to get the function
parser = get(pdata.parsers, ex.args[1], nothing)
if parser !== nothing
# call function on the rest of the expression
rule = parser(name, pdata, ex.args[2:end])
end
end
Thus, all the parsers listed above will be called appropriately if their symbol is encountered. This used to be a monolithic function, but it seemed a much better design to keep each parser as its own thing. The parsers themselves are not complex. Here's the definition of the or-rule:
function|(name::String, pdata::ParserData, args::Array)
left = parseDefinition("$(name)_1", args[1], pdata)
right = parseDefinition("$(name)_2", args[2], pdata)
return OrRule(name, value)
end
The `map_symbol_to_function` thus traverses the original `parsers` list created and maps the symbol to this function.
So, all of this currently works (and is viewable to anyone who is interested to see the full versions on PEGParser's github).
The issue is that currently `parsers` is a fixed list. I'm trying to figure out how to let the users of the library add their own. For example, suppose and `integer` parser wasn't yet made. Someone could create their own:
function integer(name::String, pdata::ParserData, args::Array)
return IntegerRule(name)
end
and then pass it into the macro somehow in so when `parseDefinition` is called, it will be used.
Originally, the macro was written to allow custom parsers to be passed in with the grammar definition:
@grammar foo [integer] begin
start = list(integer, r",[ \t\n]*")
end
Thus, the `grammar` macro would just append whatever you listed to the `parsers` list. However, the mapping of symbols to functions will fail because of the namespace issue.
Maybe, as others have argued, that's okay, because I'm trying to do too much in a macro. That's fine, so the next logical conclusion is that I should just generate code that does the mapping.
Also fine. BUT, don't forget that this mapping is there to support the function of macro. Specifically, the `parsers` list is used in the `parseDefinition` function for transforming a grammar definition into code. Thus, that means I would need a macro that generates code for another macro, bringing us to the original question posed.
One possible way around this might be that the macro doesn't actually have to generate another macro (which I think is probably the more elegant solution), but rather you could generate variables which the second macro can use. For example:
@set_parsers(integer)
# creates a parsers = [...] list
@grammar foo begin
# checks if `parsers` list exists, if not creates its own
end
However, this approach seems dangerous, as it introduces variables that the user is unaware of.