Search code examples
rascal

What operations are possible in concrete syntax pattern matching?


I am trying to rewrite java code using only concrete syntax (no AST). Next code works:

CompilationUnit rewritePackage(CompilationUnit input) = 
    visit(input) {
        case (PackageDeclaration) `package <{Identifier "."}+ N>;` => 
            (PackageDeclaration) `package <{Identifier "."}+ N>.<Identifier s>;`
            when s:= [Identifier] "othertest"
    };  

Now I want to create {Identifier "."}+ in order to insert it into result of rewrite:

CompilationUnit rewritePackage(CompilationUnit input) = 
    visit(input) {
        case (PackageDeclaration) `package <{Identifier "."}+ N>;` => 
            (PackageDeclaration) `package <{Identifier "."}+ NUpdated>;`
            when NUpdated := [{Identifier "."}+] "a1.b2"
    };

and it does not work. I also tried with lists, no success.

  1. Is it possible somehow to create {Identifier "."}+? Or to convert list[Identifier] to it? How can I achieve mapper( , toUpperCase) on {Identifier "."}+ ?

  2. Is there a way to insert some str variable directly to concrete syntax?

  3. In docs I found how {Identifier "."}+ can be transformed to list. But is there any other direct operations on {Identifier "."}+. Would be nice to have pattern match in a form: [ *Identifier firstIds, (Identifier)someName, *Identifier otherIds ]


Solution

  • First some short answers to make things clearer:

    • The implementation of the [NonTerminal] "string" notation is unfinished. Currently it only supports named non-terminals, such as [Identifiers] xxx. This is a known TODO and its already tracked as an issue.
    • There is no short syntax yet for creating concrete lists. You have to construct them manually using loops or recursion. For now the advice is to:
    • introduce a non-terminal name for the list in the grammar, and
    • write a helper function to construct the list

    To create an {Identifier "."}+ list, use concrete syntax patterns like so:

    syntax Identifiers = {Identifier "."}+ elems;
    
    private {Identifier "."}+ insert(Identifier first, {Identifier "."}+ tail) 
      = (Identifiers) `<Identifier first>.<{Identifier "."} tail>`.elems;
    

    The append function first wraps the list in the Identifiers non-terminal to be able to use the (..).. notation, and then projects out the new nested list using the .elems field. The syntax trees you produce like this are statically correct. Unlike using the [..].. notation which calls a parser dynamically, this (..).. notation calls the parser statically and composes correct trees at run-time.

    Or perhaps you want to concatenate two identifier lists:

    {Identifier "."}+ append({Identifier "."}+ prefix, {Identifier "."}+ postfix) 
          = (Identifiers) `<{Identifier "."} prefix>.<{Identifier "."}+ postfix>`.elems;
    

    Sometimes you want to concatenate possibly empty sub-lists, this is allowed and the . separator will be removed automatically:

    {Identifier "."}+ append({Identifier "."}+ prefix, {Identifier "."}* postfix) 
          = (Identifiers) `<{Identifier "."} prefix>.<{Identifier "."}* postfix>`.elems;
    

    So to turn a list[Identifier] into a {Identifier "."} you might write this:

    {Identifier "."}+ identifiers([Identifier head]) = (Identifiers) `<Identifier head>`.elems;
    
    {Identifier "."}+ identifiers([Identifier head, Identifier sec, *Identifier tail])
        = append(insert(head, identifiers([sec, *tail]);
    

    Agreed that this is kind of clumsy and we should maybe prioritize the much easier concrete list syntax templates syntax. We're also open to suggestions for improvement.