Macros support in custom language

Hi,

We are trying to develop plugin for our custom language, and we need to implement macros support (like in C, but a lot more restrictive). For example

META f(x) {

     aa##x(a) = g(a) + 5;

}

@f(bb);


is equivalent to:

aabb(a) = g(a) + 5;


Because I see no preprocessor support in current IDEA architecture, the only way I see to do it, is to physically "inline" meta code after its usage, like that:

META f(x) {

     aa##x(a) = g(a) + 5;

}

@f(bb) {
     aabb(a) = g(a) + 5;
};


Of course "inlined" part also must be included in grammar, and that way it will be included in filebased indexes, i.e. participate in find usages, go to declaration and so on.
In that case plugin has to track all meta code changes, and properly update "inlined code". It's not a problem, but i wonder if there is any way to make "inlined code" read-only (except when it is entirely deleted \ updated)? I've found only addEditReadOnlyListener in DocumentEx interface, but the listener does not have any "feedback" interface to forbid changes, so it won't do.

7 comments

No, there is no way to make any part of the code smaller than a complete file read-only.

0

Thanks.

Then one more question. As I told before, we are going to track meta code changes and "inline code" in background. As far as I understand the best way to do it, is to use addDocumentListener (where i can understand if the change is in meta code or its usage), and addPsiTreeChangeListener (for optimizing if meta code psi elements are alive or already dead), but i can't do the main job in these methods, because they are executed in main UI thread. So I want to create the daemon process for that, but i can't find any example for that (except DaemonCodeAnalyzer, but it is quite complicated :( ). Maybe you can advise for example some other open-source plugin to look into.

0

The simplest way to execute code in a background thread is to use Application.executeOnPooledThread().

0

Thanks.

One more question then, not concerning macros.

Can you explain me why pin cannot be calculated out of grammar? (or maybe it can :) ) I mean, if there are several options for parser, defining the pin, which will pick one of them, will automatically remove all other options, and i don't see why it could be useful. Of course there could be some complex cases (and it can be useful for optimization), but in 95% percent it's "monkey job".

0

1. The main is pin attribute not only allows parser to proceed in case of errors but also ensures constraints on AST.

For example:
java_class ::= modifier_list identifier body

Auto-calculated pin may be placed on modifier_list (pin=1) assuming there are no conflicting choices.
But as a developer I would like to ensure that the name of a class always be there in AST and hence my choice is identifier (pin=2).

2. Adding more complexity to the generator actually makes it harder to debug/understand the generated parser.
Good rule naming conventions and a number of global pin attributes with patterns help to save time on this work.

For example:
{
  pin(".*_definition")=".*_ref"
  pin(".*_clause")=1
  pin(".*_list(?:_\d.*)?")=1
  pin("parenthesized.*")="'\('"
  pin(".*_expression.*")=".*_op"
}


Taking these arguments into account I'm not convinced that adding something like pin=auto or Generate Default Pins intention
will do particular good though nothing stops one from investigating this in a Grammar-Kit fork. :)

1

Thanks.

Maybe you can help with one more thing.

In our language we have some implicit declarations (like in implicit type languages). What is the better way to implement this? As I see it, there are two ways: to make usage rule also a declaration rule (there will be problems because of lack of multiple inheritance in java, but they are solvable), or wrap one rule around each other (sort of aggregation). I mean what is the best practice in that case. And what can be done, if the order of usages is not evident (for example if you have keyword namespace that can be in several different files). Of course it's a matter of logic, not implementation, but maybe you can advise something :)

0

If I understand you correctly you have something like:

class moduleName.className { .. }

where moduleName implicitly defines the "module".

I don't think this should be solved on a grammar level.
The one approach I use is to parse all moduleNames as ordinary references that should try to resolve to some fake PSI elements.
This ensures the consistency of Find Usages/Highlight Usages/Rename refactoring.
Navigation can leave the caret on a reference, or jump to the first declaring element.


A PSI file implementation (module, project or some other entry-point) should host something like:

public class MyPsiFileImpl extends PsiFileBase {

     private final CachedValue<List<Def>> myDefs; // Def class for example may extend RenameableFakePsiElement

     public MyPsiFileImpl(FileViewProvider viewProvider) {

         myDefs = CachedValuesManager.getManager(getProject()).createCachedValue(new CachedValueProvider<List<Def>>() {
                public Result<List<Def>> compute() {
                  return Result.create(calcAllDefs(), MyPsiFileImpl.this);  // calcAllDefs visits all elements in a file and collects required Defs
                }
         }, false);
     }


     public List<Def> getDefs() {   // call this method in referebce.resolve() or multiResolve()
         return myDefs.getValue();
     }
}


0

Please sign in to leave a comment.