Groovy Name-and-Value Macro Support Proposal - Code

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
MG
Reply | Threaded
Open this post in threaded view
|

Groovy Name-and-Value Macro Support Proposal - Code

MG
Below is the source for the proposed NV etc macros + an explanation how to use macros with IntelliJ IDE.

To build and use macros inside the same IntelliJ project:
  1. In a Groovy 2.5 project, create a new module for the macro code (e.g. groovy_macro) and one for the macro usage (e.g. groovy_macro_use).
  2. In groovy_macro\src (folder marked as Sources Root)
    1. Add \main\groovy\groovyx\macro\NameAndValueMacros.groovy (see below)
    2. Add \main\groovy\groovyx\NameAndValue.groovy
  3. In groovy_macro\resources (folder marked as Resources Root)
    1. Add resources\META-INF\groovy\org.codehaus.groovy.runtime.ExtensionModule
  4. Add a depency to groovy_macro to groovy_macro_use
  5. Use NV/NVL in code in Groovy code in groovy_macro module (example: See NameAndValueMacrosTest.groovy below)

Note: As Paul already pointed out, compiling the macro code in the same step as the code that uses it does not work - the compiled macro code already needs to be on the classpath during compilation, which is why we need two seperate modules inside the same project. Of course the macro code can also be compiled independently and the resulting JAR added to the "groovy_macro_use" module.

Cheers,
mg


// resources\META-INF\groovy\org.codehaus.groovy.runtime.ExtensionModule

moduleName=groovy-macro-ext
moduleVersion=0.1.0
extensionClasses=main.groovy.groovyx.macro.NameAndValueMacros


// src\main\groovy\groovyx\NameAndValue.groovy

package main.groovy.groovyx

class NameAndValue {
  final String name
  final val

  NameAndValue(String name, val) {
    this.name = name
    this.val = val
  }

  @Override
  public String toString() {
    final String valStr = ((val instanceof String) || (val instanceof  GString)) ? "\"$val\"" : val.toString()
    return "$name=$valStr"
  }
}


// src\main\groovy\groovyx\macro\NameAndValueMacros.groovy

package main.groovy.groovyx.macro

import main.groovy.groovyx.NameAndValue
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.GStringExpression
import org.codehaus.groovy.ast.expr.ListExpression
import org.codehaus.groovy.macro.runtime.Macro
import org.codehaus.groovy.macro.runtime.MacroContext

import static org.codehaus.groovy.ast.tools.GeneralUtils.*

class NameAndValueMacros {

  // Expose through GeneralUtils ?
  static ClassNode classNode(final Class clazz) {
    ClassHelper.makeCached(clazz)
  }

  // NVL: NameAndValueList
  @Macro
  public static Expression NVL(MacroContext ctx, Expression... exps) {
    final List nvExpList = exps.collect { NV(ctx,it) }
    new ListExpression(nvExpList)
  }

  // NV: NameAndValue
  @Macro
  public static Expression NV(MacroContext ctx, Expression exp) {
    ctorX( classNode(NameAndValue), args(constX(exp.text), exp) )
  }

  // NVGS: NameAndValue-GString
  @Macro
  public static Expression NVGS(MacroContext ctx, Expression... exps) {
    final List<Expression> expList = Arrays.asList(exps)
    int i=-1
    final List<Expression> nameList = expList.collect { i++; constX((i > 0 ? ", " : "") + it.text + "=") }

    final quoteCharExp = constX('"')

    final List<Expression> quoteIfStringExpList = expList.collect { final Expression exp ->
      final quotedExp = new GStringExpression('verbatimExp',[quoteCharExp,quoteCharExp],[exp])
      ternaryX(orX(isInstanceOfX(exp, classNode(String)),isInstanceOfX(exp, classNode(GString))), quotedExp, exp)
    }

    new GStringExpression('', nameList, quoteIfStringExpList)
  }
}



// NameAndValueMacrosTest.groovy

package main.groovy.groovyx.macro

import groovy.transform.CompileStatic
import main.groovy.groovyx.NameAndValue
import org.junit.Ignore
import org.junit.Test

class NameAndValueMacrosTest {
  @Test
  @Ignore
  @CompileStatic
  void NVL_LongVarNameTest() {
    final ageOfTree = 124
    final towerHeight = 987.654
    final String visitorName = "abc"
    final s1 = "DEFGH"
    final gs0 = "val0:$ageOfTree"
    final GString gs1 = "TheTower($towerHeight)"
    final List names = [ "Peter", "Ann", "Raymond" ]

    println "var0=$var0, var1=$var0"

    println "single variables: ${NV(ageOfTree)} and ${NV(gs0)} and ${NV(visitorName)} and ${NV(s1)} and ${NV(gs1)} and also ${NV(names)})}"
    println "variable list: ${NVL(ageOfTree, gs0, visitorName, s1, towerHeight, gs1, names)}"
  }
}


MG
Reply | Threaded
Open this post in threaded view
|

Re: Groovy Name-and-Value Macro Support Proposal - Code - Erratum

MG
The "macros in IntelliJ project" section should read:
  1. In a Groovy 2.5 project, create a new module for the macro code (e.g. groovy_macro) and one for the macro usage (e.g. groovy_macro_use).
  2. In groovy_macro\src (folder marked as Sources Root)
    1. Add \main\groovy\groovyx\macro\NameAndValueMacros.groovy (see below)
    2. Add \main\groovy\groovyx\NameAndValue.groovy
  3. In groovy_macro\resources (folder marked as Resources Root)
    1. Add resources\META-INF\groovy\org.codehaus.groovy.runtime.ExtensionModule
  4. Add a depency to groovy_macro to groovy_macro_use
  5. Use NV/NVL in code in Groovy code in groovy_macro module (example: See NameAndValueMacrosTest.groovy below)
i.e. the "META-INF" folder comes directly under the IntelliJ "resources" folder. While this looks like an obvious typo, you evidently do not get any meaningful error feedback if any of the paths is incorrect, but only e.g. "could not find method NV", since Groovy has no indication that you are trying to apply a @Macro annotated method, if said method cannot be found in compiled form during compilation ;-)
Cheers,
mg


On 27.06.2018 22:16, MG wrote:
Below is the source for the proposed NV etc macros + an explanation how to use macros with IntelliJ IDE.

To build and use macros inside the same IntelliJ project:
  1. In a Groovy 2.5 project, create a new module for the macro code (e.g. groovy_macro) and one for the macro usage (e.g. groovy_macro_use).
  2. In groovy_macro\src (folder marked as Sources Root)
    1. Add \main\groovy\groovyx\macro\NameAndValueMacros.groovy (see below)
    2. Add \main\groovy\groovyx\NameAndValue.groovy
  3. In groovy_macro\resources (folder marked as Resources Root)
    1. Add resources\META-INF\groovy\org.codehaus.groovy.runtime.ExtensionModule
  4. Add a depency to groovy_macro to groovy_macro_use
  5. Use NV/NVL in code in Groovy code in groovy_macro module (example: See NameAndValueMacrosTest.groovy below)

Note: As Paul already pointed out, compiling the macro code in the same step as the code that uses it does not work - the compiled macro code already needs to be on the classpath during compilation, which is why we need two seperate modules inside the same project. Of course the macro code can also be compiled independently and the resulting JAR added to the "groovy_macro_use" module.

Cheers,
mg


// resources\META-INF\groovy\org.codehaus.groovy.runtime.ExtensionModule

moduleName=groovy-macro-ext
moduleVersion=0.1.0
extensionClasses=main.groovy.groovyx.macro.NameAndValueMacros


// src\main\groovy\groovyx\NameAndValue.groovy

package main.groovy.groovyx

class NameAndValue {
  final String name
  final val

  NameAndValue(String name, val) {
    this.name = name
    this.val = val
  }

  @Override
  public String toString() {
    final String valStr = ((val instanceof String) || (val instanceof  GString)) ? "\"$val\"" : val.toString()
    return "$name=$valStr"
  }
}


// src\main\groovy\groovyx\macro\NameAndValueMacros.groovy

package main.groovy.groovyx.macro

import main.groovy.groovyx.NameAndValue
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.GStringExpression
import org.codehaus.groovy.ast.expr.ListExpression
import org.codehaus.groovy.macro.runtime.Macro
import org.codehaus.groovy.macro.runtime.MacroContext

import static org.codehaus.groovy.ast.tools.GeneralUtils.*

class NameAndValueMacros {

  // Expose through GeneralUtils ?
  static ClassNode classNode(final Class clazz) {
    ClassHelper.makeCached(clazz)
  }

  // NVL: NameAndValueList
  @Macro
  public static Expression NVL(MacroContext ctx, Expression... exps) {
    final List nvExpList = exps.collect { NV(ctx,it) }
    new ListExpression(nvExpList)
  }

  // NV: NameAndValue
  @Macro
  public static Expression NV(MacroContext ctx, Expression exp) {
    ctorX( classNode(NameAndValue), args(constX(exp.text), exp) )
  }

  // NVGS: NameAndValue-GString
  @Macro
  public static Expression NVGS(MacroContext ctx, Expression... exps) {
    final List<Expression> expList = Arrays.asList(exps)
    int i=-1
    final List<Expression> nameList = expList.collect { i++; constX((i > 0 ? ", " : "") + it.text + "=") }

    final quoteCharExp = constX('"')

    final List<Expression> quoteIfStringExpList = expList.collect { final Expression exp ->
      final quotedExp = new GStringExpression('verbatimExp',[quoteCharExp,quoteCharExp],[exp])
      ternaryX(orX(isInstanceOfX(exp, classNode(String)),isInstanceOfX(exp, classNode(GString))), quotedExp, exp)
    }

    new GStringExpression('', nameList, quoteIfStringExpList)
  }
}



// NameAndValueMacrosTest.groovy

package main.groovy.groovyx.macro

import groovy.transform.CompileStatic
import main.groovy.groovyx.NameAndValue
import org.junit.Ignore
import org.junit.Test

class NameAndValueMacrosTest {
  @Test
  @Ignore
  @CompileStatic
  void NVL_LongVarNameTest() {
    final ageOfTree = 124
    final towerHeight = 987.654
    final String visitorName = "abc"
    final s1 = "DEFGH"
    final gs0 = "val0:$ageOfTree"
    final GString gs1 = "TheTower($towerHeight)"
    final List names = [ "Peter", "Ann", "Raymond" ]

    println "var0=$var0, var1=$var0"

    println "single variables: ${NV(ageOfTree)} and ${NV(gs0)} and ${NV(visitorName)} and ${NV(s1)} and ${NV(gs1)} and also ${NV(names)})}"
    println "variable list: ${NVL(ageOfTree, gs0, visitorName, s1, towerHeight, gs1, names)}"
  }
}