On Thu, Sep 12, 2013 at 06:09:41PM +0200, deadalnix wrote: > On Thursday, 12 September 2013 at 14:09:43 UTC, H. S. Teoh wrote: > >This can be handled by using an intermediate grammar rule. Reduce all > >(...) into an intermediate type, say ArgList, so the reduction > >happens something like this: > > > > int foo () () {} > > Type Ident ArgList ArgList ^ > > > >Then have the rule: > > > > CompileTimeArgs ::= ArgList > > RuntimeArgs ::= ArgList > > TemplateDecl ::= Type Ident CompileTimeArgs RuntimeArgs '{' ... > > FuncDecl ::= Type Ident RuntimeArgs '{' ... > > > >So first, all (...) gets parsed to ArgList, but it's not yet fixed > >whether they are compile-time arguments or runtime arguments. It's > >only after you see the next '(' or '{' that you decide whether > >ArgList should reduce to CompileTimeArgs or RuntimeArgs. > > > >ArgList itself, of course, will accept all possible parameters (both > >runtime and compile-time): types, expressions, symbols. Then when you > >reduce it to RuntimeArgs, you reject stuff that can't be interpreted > >as parameter declarations. > > > > And then you got to backtrack the parsing instead of the lexing. You > just moved the problem around. You'll have to create some temporary > ast nodes that then will fix into what they really are.
No. You can just use ArgListItem for both runtime args and compile-time args. Once you decided which one it is, wrong arguments are rejected at semantic time (which you have to do anyway). Let's take a concrete example. Say we're parsing this invalid code: int foo(alias A)(alias B) {} You'd go through these steps: 1) Parse initial prefix of declaration: int foo(alias A)(alias B) {} ^ AST: FuncDecl |--RetType: int |--Ident: foo \--[ being built ] 2) Parse first (...): int foo(alias A)(alias B) {} ^ AST: FuncDecl |--RetType: int |--Ident: foo |--ArgList | \-- AliasArg | \-- ident: A \--[ being built ] I'm skipping the intermediate steps here, it's obvious how to construct AliasArg from the usual parsing process. 3) Parse second (...): int foo(alias A)(alias B) {} ^ AST: FuncDecl |--RetType: int |--Ident: foo |--ArgList | \-- AliasArg | \-- ident: A |--ArgList | \-- AliasArg | \-- ident: B \--[ being built ] 4) At this point, you now know the first ArgList is CompileTimeArgList, and the second is RuntimeArgList, so you can just change the type fields (along with narrowing FuncDecl to TemplateFuncDecl): AST: TemplateFuncDecl (was: FuncDecl) |--RetType: int |--Ident: foo |--CompileTimeArgList (was: ArgList) | \-- AliasArg | \-- ident: A |--RuntimeArgList (was: ArgList) | \-- AliasArg | \-- ident: B \--[ being built ] Since you're still constructing FuncDecl, your current parsing context should still have a direct reference to the partially-constructed FuncDecl node, which in turn has a direct reference to both ArgList child nodes. So this is just dereferencing a couple of pointers. No backtracking. 5) Finish parsing the declaration: int foo(alias A)(alias B) {} ^ AST: TemplateFuncDecl |--RetType: int |--Ident: foo |--CompileTimeArgList (was: ArgList) | \-- AliasArg | \-- ident: A |--RuntimeArgList (was: ArgList) | \-- AliasArg | \-- ident: B \--FuncBody \-- CompoundStatement \-- [empty body] 6) Run semantic: - Create local symbol table for foo, etc.. - Run semantic on CompileTimeArgList: - Check AliasArg for validity - Run semantic on AliasArg: add A to function's local symbol table, etc. - Run semantic on RuntimeArgList: - Check AliasArg for validity: ERROR: cannot have alias parameter at runtime. - (Add B to local symbol table)(skipped due to previous error) - (Run semantic on FuncBody)(skipped due to previous error) - (Run semantic on RetType (verify return type match, etc.))(skipped due to previous error) - (Add function to parent scope symbol table)(skipped due to previous error) So, no backtracking is necessary. Of course, it sounds like DMD's parser doesn't work this way, but that's a limitation of DMD's parser, not an *inherent* need for backtracking. T -- I see that you JS got Bach.