Child pages
  • Scripting IDE for DSL awareness

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Contributors are entities which consumes consume contexts as initialization parameters and answer to the question ``What?''. Contributors provide new properties and methods according to given contexts. We provide a set of utility methods to augment existing types with new behavior, which may be used in a contributor application as regular Groovy methods. New contributor may be defined by an invocation of a method contributor(), taking two parameters:
1. A list of contexts to be used to contribute a new behavior
2. One-argument closure, taking a contexts and adding new behavior to the reference expression ``in the focus'' of a context

Th The code fragment below shows the definition of a contributor, taking a list of three contexts, defined earlier, namely ctx1 and ctx2 and one closure, which adds unconditionally the method foo with a parameter of type java.lang.String and return type int to the target expression of a context.

Code Block
contributor [ctx1, ctx2], {
 method name: "foo", 
        params: [s: "java.lang.String"], 
        type: "int"
}

Extending GroovyDSL.

The core of GroovyDSL functionality is concentrated in a body of closures, passed to the contributor() method as a second parameter. All invocations of functions and properties inside of it are calls to wrappers around internal IntelliJ IDEA's PSI. There are two kinds of such wrappers. The first kind is unqualified ones, such as findClass(). The second kind is methods, added to the original PSI classes via the mix-in categories mechanism. For example, methods property is added externally by a GroovyDSL environment to the PsiClass class.

Both of these sets of functions may be extended by new ones using a mechanism of plugins for IntelliJ IDEA. To provide new unqualified methods one should provide a class, implementing the GdslMembersProvider interface, and register it by a plugin descriptor. All methods of such a newly added components will be used as possible delegates for unqualified method calls. All methods of GdslMembersProvider implementation must take an instance of GdslMembersHolderConsumer as a last parameter to get an information about a context by it.

Code Block

public class GroovyDslDefaultMembers implements GdslMembersProvider {

  @Nullable
  public PsiClass findClass(String fqn, GdslMembersHolderConsumer consumer) {
    final JavaPsiFacade facade = JavaPsiFacade.getInstance(consumer.getProject());
    final PsiClass clazz = facade.findClass(fqn, GlobalSearchScope.allScope(consumer.getProject()));
    return clazz;
  }

...

}

New methods and properties may be added to PSI by implementing the PsiEnhancerCategory interface. Its registered implementations will be used as categories and ``wrapped around'' a body of a contributor's closure in a moment of its execution.

Code Block

public class PsiClassCategory implements PsiEnhancerCategory {

  @Nullable
  public static String getQualName(PsiClass clazz) {
    return clazz.getQualifiedName();
  }
// Other category methods
...
}

Describing GroovyDSL internal language in its own terms

Since GroovyDSL language is freely extensible in terms of IDEA PSI, here we list all GroovyDSL methods, available to invoke on the top-level of scripts or in the body of a closure, passes to contributor as a parameter. The code below is an actual script, describing the semantics of GroovyDSL build-up atop of IDEA PSI.

Code Block
def gdslScriptContext = context(scope: scriptScope(), filetypes: ["gdsl"])

contributor([gdslScriptContext]) {
  method name: "context", params: [args: [:]], type: "java.lang.Object"
  method name: "contributor", params: [contexts: "java.util.List", body: {}], type: void

  // scopes
  property name: "closureScope", type: {}
  property name: "scriptScope", type: {}
}

def contributorBody = context(scope: closureScope(isArg: true))

contributor([contributorBody]) {
  if (enclosingCall("contributor")) {
    method name: "method", type: "void", params: [args: [:]]
    method name: "property", type: "void", params: [args: [:]]

    method name: "add", type: "void", params: [member: "com.intellij.psi.PsiMember"]
    method name: "findClass", type: "com.intellij.psi.PsiClass", params: [name: "java.lang.String"]
    method name: "delegatesTo", type: "void", params: [clazz: "com.intellij.psi.PsiClass"]

    method name: "delegatesTo",
           type: "void",
           params: [expr: "org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression"]

    method name: "enclosingCall",
           type: "com.intellij.psi.PsiElement",
           params: [methodName: "java.lang.String"]

    property name: "place", type: "com.intellij.psi.PsiElement"   // Reference to the place in the focus of a context
    property name: "classType", type: "com.intellij.psi.PsiClass" // Class type to be augmented
  }
}

def psiClassContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiClass")
contributor([psiClassContext]) {
  method name: "getMethods", type: "java.util.Collection"
  method name: "getQualName", type: "java.lang.String"
  method name: "hasAnnotation", type: "boolean"
  method name: "getAnnotation", type: "com.intellij.psi.PsiAnnotation"
}

def psiElementContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiElement")
contributor([psiElementContext]) {
  method name: "bind", type: "com.intellij.psi.PsiElement"
}

def expressionContext = context(scope: closureScope(isArg: true),
                                ctype: "org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression")
contributor([expressionContext]) {
  method name: "getArguments", type: "java.util.Collection"
  method name: "getClassType", type: "com.intellij.psi.PsiClass"
}