├── .gitignore ├── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── grasmin-core ├── build.gradle └── src │ ├── main │ └── groovy │ │ └── com │ │ └── athaydes │ │ └── grasmin │ │ ├── Grasmin.groovy │ │ ├── GrasminASTTransformation.groovy │ │ ├── JasminCode.groovy │ │ └── JasminTyper.groovy │ └── test │ ├── groovy │ └── com │ │ └── athaydes │ │ └── grasmin │ │ ├── GrasminASTTransformationTest.groovy │ │ ├── Hello.groovy │ │ └── JasminTyperSpec.groovy │ └── resources │ ├── AnnotatedClass.groovy │ └── Test1.groovy ├── grasmin-tests ├── build.gradle └── src │ └── test │ └── groovy │ └── grasmin │ ├── ArraysCodeTest.groovy │ ├── ClassLevelJasminCodeTest.groovy │ ├── GrasminTest.groovy │ ├── HandCodedLoopPerformanceTest.groovy │ ├── JavaGcd.java │ ├── PerformanceTest.java │ ├── PerformanceTestResultCollector.groovy │ └── test_target │ ├── ArraysCode.groovy │ ├── JasminCodeClass.groovy │ ├── JasminCodeClassComplex.groovy │ └── JavaArrays.java ├── libs └── jasmin.jar └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | build/ 3 | lib/ 4 | out/ 5 | target/ 6 | .gradle/ 7 | .idea/ 8 | *.class 9 | *.iml 10 | *.ipr 11 | *.iws 12 | 13 | !gradlew-wrapper.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grasmin 2 | > Write Jasmin (JVM assembly) code directly in your Groovy files 3 | 4 | Grasmin allows you to write [Jasmin](http://jasmin.sourceforge.net/) code (which is basically JVM assembly bytecode instructions) 5 | directly on your Groovy files by annotating methods, or even entire classes, with the `@JasminCode` annotation. 6 | 7 | This annotation is a Groovy AST Transformation, which allows manipulation of source code during the compilation process. 8 | 9 | For example, you could write `Hello World` as follows: 10 | 11 | ```groovy 12 | class Hello { 13 | 14 | @JasminCode 15 | static void main(args) { 16 | """ 17 | .limit stack 2 18 | getstatic java/lang/System/out Ljava/io/PrintStream; 19 | ldc "Hello World!" 20 | invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 21 | return 22 | """ 23 | } 24 | 25 | } 26 | ``` 27 | 28 | The `@JasminCode` annotation supports a single parameter, `outputDebugFile`. You may provide a value for it, with the 29 | path to a file, if you want Grasmin to write the contents of the Jasmin file used to generate the byte-code to it for 30 | debugging purposes. 31 | 32 | For example: 33 | 34 | ```groovy 35 | @JasminCode( outputDebugFile = 'jasmin-files/hello.j' ) 36 | ``` 37 | 38 | Most [JVM instructions](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html) should work with Jasmin code. 39 | Let me know if you have any issues. 40 | 41 | 42 | ## Using Grasmin 43 | 44 | Currently, you need to clone this repo and build it with Gradle: 45 | 46 | ``` 47 | gradlew install 48 | ``` 49 | 50 | > `gradlew` (Gradle wrapper) will download Gradle automatically if you don't already have it, then build Grasmin. 51 | 52 | This will put the following artifact in your local Maven repo: 53 | 54 | ``` 55 | group = 'com.athaydes.grasmin' 56 | name = 'grasmin-core' 57 | version = 0.1 58 | ``` 59 | 60 | If you don't use Maven and Gradle, just do `gradlew jar` to create a jar in the `build/libs` folder. 61 | 62 | The Jasmin Jar can be found on [SourceForge](https://sourceforge.net/projects/jasmin/files/). 63 | 64 | ### Annotating classes 65 | 66 | When you annotate a class with `@JasminCode`, everything 67 | in the class will be converted into a Jasmin file, which will then be used directly by Jasmin to generate the bytecode. 68 | 69 | That means that all methods in the class must be written in Jasmin code! 70 | 71 | > For better performance, this is the recommended approach. 72 | 73 | You can expect the generated bytecode to be effectively what you wrote, except for the following small convenient 74 | exceptions Grasmin provides: 75 | 76 | * class variables (including static variables) can be initialized directly as you would do in Java or Groovy: 77 | 78 | ```groovy 79 | String myString = "Hello Grasmin!" 80 | static final int myInt = 10 81 | ``` 82 | 83 | * you do not need to explicitly declare a default constructor. It will be automatically generated for you. 84 | 85 | > Notice that if you declare a default constructor, it is your responsibility to initialize non-static variables as 86 | Grasmin would have done it in the automatically generated default constructor otherwise. 87 | 88 | Here's a simple example of a class annotated with @JasminCode: 89 | 90 | ```groovy 91 | @JasminCode( outputDebugFile = 'exampleJasminClass.j' ) 92 | class ExampleJasminClass { 93 | 94 | private String name 95 | private int i 96 | 97 | public static final String staticString = 'a-string' 98 | public static final int staticInt = 123 99 | 100 | // default constructor is declared, so must initialize the instance variables here! 101 | ExampleJasminClass() { 102 | """\ 103 | .limit stack 2 104 | aload_0 105 | invokenonvirtual java/lang/Object/()V 106 | aload_0 107 | ldc "John" 108 | putfield grasmin/test_target/ExampleJasminClass/name Ljava/lang/String; 109 | aload_0 110 | bipush 55 111 | putfield grasmin/test_target/ExampleJasminClass/i I 112 | return""" 113 | } 114 | 115 | // concatenate two Strings, returning the result 116 | String concat( String a, String b ) { 117 | """\ 118 | .limit locals 3 119 | .limit stack 2 120 | aload_1 121 | aload_2 122 | invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; 123 | areturn""" 124 | // a.concat( b ) 125 | } 126 | 127 | // simple getter 128 | public String getName() { 129 | """\ 130 | aload_0 131 | getfield grasmin/test_target/ExampleJasminClass/name Ljava/lang/String; 132 | areturn""" 133 | } 134 | 135 | // int getter returns with ireturn 136 | public int getI() { 137 | """\ 138 | aload_0 139 | getfield grasmin/test_target/ExampleJasminClass/i I 140 | ireturn""" 141 | 111 // ignored 142 | } 143 | 144 | } 145 | ``` 146 | 147 | And the following is the output produced by `javap -c` on the class file generated by the above class declaration: 148 | 149 | ``` 150 | Compiled from "grasmin.test_target.ExampleJasminClass.j" 151 | public class grasmin.test_target.ExampleJasminClass { 152 | public static final java.lang.String staticString; 153 | 154 | public static final int staticInt; 155 | 156 | public static {}; 157 | Code: 158 | 0: ldc #15 // String a-string 159 | 2: putstatic #38 // Field staticString:Ljava/lang/String; 160 | 5: ldc #22 // int 123 161 | 7: putstatic #6 // Field staticInt:I 162 | 10: return 163 | 164 | public grasmin.test_target.ExampleJasminClass(); 165 | Code: 166 | 0: aload_0 167 | 1: invokespecial #35 // Method java/lang/Object."":()V 168 | 4: aload_0 169 | 5: ldc #14 // String John 170 | 7: putfield #7 // Field name:Ljava/lang/String; 171 | 10: aload_0 172 | 11: bipush 55 173 | 13: putfield #28 // Field i:I 174 | 16: return 175 | 176 | public java.lang.String concat(java.lang.String, java.lang.String); 177 | Code: 178 | 0: aload_1 179 | 1: aload_2 180 | 2: invokevirtual #10 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String; 181 | 5: areturn 182 | 183 | public java.lang.String getName(); 184 | Code: 185 | 0: aload_0 186 | 1: getfield #7 // Field name:Ljava/lang/String; 187 | 4: areturn 188 | 189 | public int getI(); 190 | Code: 191 | 0: aload_0 192 | 1: getfield #28 // Field i:I 193 | 4: ireturn 194 | } 195 | 196 | ``` 197 | 198 | Notice that because the class file is generated solely based on your declaration, no Groovy features can be used with 199 | annotated classes (in fact, you do not need to have Groovy in the classpath to run classes that do not directly use 200 | Groovy code in the JasminCode). 201 | 202 | ### Annotating methods 203 | 204 | Any method, in an otherwise normal Groovy class, can be annotated with `@JasminCode`. 205 | Annotated methods can have any signature. 206 | 207 | If your method needs to return a particular type, just add a dummy value as the last statement of your method to 208 | make your IDE compiler happy... during Groovy compilation, `Grasmin` will only use the first statement of your 209 | method (which should be a String or a property that evaluates to a String), ignoring any subsequent statements (which 210 | will not even be present in the compiled class file). 211 | 212 | The following method returns the sum of two integers **(notice the use of both `@JasminCode` and `@GroovyStatic` - 213 | you almost always want to use them together on methods to avoid the cost of calling a method via Groovy's normal 214 | dynamic method dispatch)**: 215 | 216 | ```groovy 217 | class Hello { 218 | @CompileStatic 219 | @JasminCode 220 | int sum( int a, int b ) { 221 | """ 222 | .limit stack 2 223 | .limit locals 2 224 | iload_0 225 | iload_1 226 | iadd 227 | ireturn 228 | """ 229 | 0 // ignored, but makes the IDE happy 230 | } 231 | } 232 | ``` 233 | 234 | > Keep reading below to see exactly what the generated byte-code is for this example. 235 | 236 | 237 | ## How it works 238 | 239 | ### Annotated classes 240 | 241 | When you annotate a class with `@JasminCode`, during the semantics analysis phase, Grasmin will basically replace the 242 | normal Groovy class file which would have normally been generated by `groovyc` with one created by Jasmin based on a j 243 | file created by **interpreting only the first statement of each method as being Jasmin code**, ie. a String (or an 244 | expression which evaluates to a String) containing Jasmin code. 245 | 246 | As mentioned earlier, Grasmin will also: 247 | 248 | * initialize any static variables declared in the normal Groovy syntax. 249 | * initialize instance variables which have an initial value in an automatically generated default constructor, 250 | if no default constructor was declared. 251 | 252 | Notice that, because you actually declare the class in a normal Groovy file, you can use it in your Java or Groovy project 253 | with full support from the IDE, even though at run-time the actual implementation of the class will be turned into the 254 | byte-code instructions provided by the Strings in the first statement of each method. 255 | 256 | ### Annotated methods 257 | 258 | Grasmin turns any method annotated with `@JasminCode` into a call to a static method (called `run`) of a class created by 259 | **Jasmin** from the Jasmin code provided in the annotated method. 260 | 261 | The example above (in the `sum` function) produces the following class, as output by `javap`: 262 | 263 | ``` 264 | Compiled from "Hello.groovy" 265 | public class com.athaydes.grasmin.Hello implements groovy.lang.GroovyObject { 266 | 267 | // ... lots of Java/Groovy boilerplate 268 | 269 | public int sum(int, int); 270 | Code: 271 | 0: iload_1 272 | 1: iload_2 273 | 2: invokestatic #121 // Method com_athaydes_grasmin_Hello_sum.run:(II)I 274 | 5: ireturn 275 | 6: ldc #35 // int 0 276 | 8: ireturn 277 | 278 | // ... more boilerplate 279 | } 280 | ``` 281 | 282 | > the two last lines in `sum` are dead-code, probably introduced by the Groovy compiler as a default return value 283 | 284 | And the new class produced with Jasmin to hold the implementation of `sum`: 285 | 286 | ``` 287 | Compiled from "com_athaydes_grasmin_Hello_sum.j" 288 | public class com_athaydes_grasmin_Hello_sum { 289 | public static int run(int, int); 290 | Code: 291 | 0: iload_0 292 | 1: iload_1 293 | 2: iadd 294 | 3: ireturn 295 | } 296 | ``` 297 | 298 | The above is the whole output of `javap` (without the *verbose* key). 299 | The `j` file mentioned in the first line is the temporary file used by Grasmin as input for Jasmin. 300 | 301 | ## Future work and performance 302 | 303 | Unfortunately, delegating a method call to a static method of an external class does not seem to be very efficient for 304 | a short algorithm, at least, so gains in performance cannot be guaranteed! However, I am sure there would be cases where 305 | handcrafted Assembly cannot be beaten either by `javac` or `JIT` optimizations. I would love to hear of any examples. 306 | 307 | There are some performance tests in [this directory](grasmin-tests/src/test/groovy/grasmin/) which show that, for example, 308 | writing the simple GCD Euclidean algorithm in Jasmin is actually less efficient than just writing the non-recursive algorithm 309 | in either Java or Groovy (Groovy with @CompileStatic actually runs consistently faster than Java). 310 | 311 | Here are some results: 312 | 313 | > each row represents 10_000 runs of the GCD algorithm with a pair of random integers, run 100 times with each implementation 314 | (values are average time taken in nano-seconds) 315 | 316 | Java | Groovy @CompileStatic | Grovy @TailRecursive | @JasminCode 317 | -------------|-----------------------|----------------------|------------ 318 | 2.794.043,06 | 2.497.728,88 | 2.870.736,07 | 3.249.961,17 319 | 2.915.521,62 | 2.684.497,9 | 2.813.709,1 | 3.455.433,63 320 | 2.926.648,15 | 2.634.187,88 | 2.935.640,63 | 3.904.040,23 321 | 322 | Implementations: 323 | 324 | * `Java` - non-recursive algorithm written in Java 325 | * `Groovy @CompileStatic` - non-recursive algorithm written in Groovy, annotated with @CompileStatic 326 | * `Groovy @TailRecursive` - recursive algorithm written in Groovy, annotated with both @CompileStatic and @TailRecursive 327 | * `@JasminCode` - non-recursive algorithm written in Jasmin assembly via Grasmin 328 | 329 | Note: in the last run, the order in which the algorithms were run was modified to: Java, @JasminCode, 330 | Groovy@CompileStatic, Groovy@TailRecursive. This did not seem to impact on the relative results. 331 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatoathaydes/Grasmin/b2484af3d49a0694f501925051c5cbb19e77b263/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 28 23:29:37 CET 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /grasmin-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'maven' 3 | 4 | group = 'com.athaydes.grasmin' 5 | version = 0.1 6 | 7 | sourceCompatibility = 1.8 8 | targetCompatibility = 1.8 9 | 10 | repositories { 11 | mavenLocal() 12 | jcenter() 13 | } 14 | 15 | def jasminJar = rootProject.file( 'libs/jasmin.jar' ) 16 | assert jasminJar?.exists() 17 | 18 | dependencies { 19 | compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.8' 20 | compile files( jasminJar ) 21 | testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-3" 22 | } 23 | -------------------------------------------------------------------------------- /grasmin-core/src/main/groovy/com/athaydes/grasmin/Grasmin.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import groovy.transform.CompileStatic 4 | import jasmin.Main 5 | import org.codehaus.groovy.ast.AnnotationNode 6 | import org.codehaus.groovy.ast.ClassNode 7 | import org.codehaus.groovy.ast.ConstructorNode 8 | import org.codehaus.groovy.ast.FieldNode 9 | import org.codehaus.groovy.ast.MethodNode 10 | import org.codehaus.groovy.ast.Parameter 11 | import org.codehaus.groovy.ast.expr.ConstantExpression 12 | import org.codehaus.groovy.ast.expr.Expression 13 | import org.codehaus.groovy.ast.expr.PropertyExpression 14 | import org.codehaus.groovy.ast.stmt.BlockStatement 15 | import org.codehaus.groovy.ast.stmt.ExpressionStatement 16 | import org.codehaus.groovy.ast.stmt.Statement 17 | 18 | import java.lang.reflect.Modifier 19 | import java.util.logging.Logger 20 | 21 | @CompileStatic 22 | class Grasmin { 23 | 24 | static final Logger log = Logger.getLogger( Grasmin.name ) 25 | static final File dir = File.createTempDir( 'Grasmin', '' ) 26 | static final List fieldNamePrefixesToSkip = [ '__$', '$staticClassInfo', 'metaClass' ].asImmutable() 27 | 28 | private JasminTyper typer = new JasminTyper() 29 | 30 | Class createJasminClass( String assemblerMethodBody, File targetDir, String className, MethodNode methodNode ) { 31 | def dir = targetDir ?: dir 32 | dir.mkdirs() 33 | def classFile = dir.toPath().resolve( "${className}.class" ).toFile() 34 | 35 | withTempFile( className ) { File tmpJFile -> 36 | tmpJFile.write jasminFileContents( className, assemblerMethodBody, methodNode ) 37 | Main jasmin = new Main() 38 | jasmin.run( '-d', dir.absolutePath, tmpJFile.absolutePath ) 39 | } 40 | 41 | try { 42 | def loader = new GroovyClassLoader() 43 | loader.addClasspath( classFile.parent ) 44 | return loader.loadClass( className ) 45 | } catch ( e ) { 46 | throw new RuntimeException( "Unable to create assembler due to compilation errors", e ) 47 | } finally { 48 | if ( !targetDir ) classFile.deleteOnExit() 49 | } 50 | 51 | } 52 | 53 | void createJasminClass( ClassNode classNode, File targetDir ) { 54 | targetDir = targetDir ?: dir 55 | log.info( "Checking class ${classNode.name}" ) 56 | 57 | def methodBodies = classNode.methods.findResults { method -> 58 | def methodBody = extractJasminMethodBody( method.code ) 59 | if ( !methodBody ) { 60 | log.warning( "Method ${method.name} does not contain an Expression with JasminCode" ) 61 | return null 62 | } 63 | log.info( "-- Checking method ${method.name} from ${method.typeDescriptor}" ) 64 | """|.method ${typer.modifiersString( method.modifiers )} ${typer.typeDescriptorOf( method )} 65 | |${methodBody} 66 | |.end method 67 | |""".stripMargin() 68 | } 69 | 70 | def modifiers = typer.modifiersString classNode.modifiers 71 | def fields = fieldsOf( classNode ) 72 | 73 | String classDeclaration = """ 74 | |.class ${modifiers} ${classNode.name} 75 | |.super ${typer.className( classNode.superClass.name )} 76 | |${fields.collect { it.descriptor }.join( '\n' )} 77 | |${staticClassInitializer( classNode, fields )} 78 | |${constructorsFor( classNode, fields )} 79 | | 80 | |""".stripMargin() + methodBodies.join( '\n' ) 81 | 82 | log.fine classDeclaration 83 | 84 | withTempFile( classNode.name ) { File tmpJFile -> 85 | writeDebugFileFor( classNode.getAnnotations( new ClassNode( JasminCode ) ).first(), classDeclaration ) 86 | tmpJFile.write classDeclaration 87 | Main jasmin = new Main() 88 | jasmin.run( '-d', targetDir.absolutePath, tmpJFile.absolutePath ) 89 | } 90 | } 91 | 92 | private String staticClassInitializer( ClassNode classNode, Collection fields ) { 93 | def staticFields = fields.findAll { it.value != null && Modifier.isStatic( it.modifiers as int ) } 94 | if ( staticFields ) { 95 | """ 96 | |.method static public ()V 97 | | .limit stack ${staticFields.size() + 1} 98 | |${staticFields.collect { initializeStaticField( it, classNode ) }.join( '\n' )} 99 | | return 100 | |.end method 101 | """ 102 | } else { 103 | '' 104 | } 105 | } 106 | 107 | private String initializeStaticField( Map field, ClassNode classNode ) { 108 | """\ 109 | | ldc ${field.value} 110 | | putstatic ${typer.className( classNode.name )}/${field.name} ${field.fieldType}""".stripMargin() 111 | } 112 | 113 | private String constructorsFor( ClassNode classNode, Collection fields ) { 114 | def initializedFields = fields.findAll { it.value != null && !Modifier.isStatic( it.modifiers as int ) } 115 | if ( classNode.declaredConstructors || initializedFields ) { 116 | """\ 117 | |${createConstructors( classNode.declaredConstructors )} 118 | |${ 119 | classNode.declaredConstructors.any { it.parameters.size() == 0 } ? 120 | '' : defaultConstructorWith( initializedFields, classNode ) 121 | }""".stripMargin() 122 | } else { 123 | defaultConstructor 124 | } 125 | } 126 | 127 | private String defaultConstructorWith( Collection initializedFields, ClassNode classNode ) { 128 | """\ 129 | |.method public ()V 130 | | .limit stack ${initializedFields.size() + 1} 131 | | aload_0 132 | | invokenonvirtual java/lang/Object/()V 133 | |${initializedFields.collect { initializeField( it, classNode ) }.join( '\n' )} 134 | | return 135 | |.end method""" 136 | } 137 | 138 | private String createConstructors( List constructorNodes ) { 139 | constructorNodes.collect { constructor -> 140 | """ 141 | |.method ${typer.modifiersString( constructor.modifiers )} (${ 142 | constructor.parameters.collect { Parameter p -> 143 | typer.typeNameFor( p.originType.typeClass.name ) 144 | }.join( '' ) 145 | })V 146 | |${extractJasminMethodBody( constructor.code )} 147 | |.end method 148 | |""".stripMargin() 149 | }.join( '\n' ) 150 | } 151 | 152 | private String initializeField( Map field, ClassNode classNode ) { 153 | """\ 154 | | aload_0 155 | | ldc ${field.value} 156 | | putfield ${typer.className( classNode.name )}/${field.name} ${field.fieldType}""".stripMargin() 157 | } 158 | 159 | private Collection fieldsOf( ClassNode classNode ) { 160 | classNode.fields.findResults { FieldNode node -> 161 | if ( fieldNamePrefixesToSkip.any { prefix -> node.name.startsWith( prefix ) } ) { 162 | return null 163 | } 164 | def initialValue = null 165 | if ( node.hasInitialExpression() ) { 166 | initialValue = valueOfExpression( node.initialExpression ) 167 | if ( node.type.name == 'java.lang.String' ) { 168 | initialValue = '"' + initialValue + '"' 169 | } 170 | } 171 | def typeName = typer.typeNameFor( node.type.name ) 172 | def fieldDescriptor = ".field ${typer.modifiersString( node.modifiers )} ${node.name} ${typeName}" 173 | [ name : node.name, descriptor: fieldDescriptor, fieldType: typeName, 174 | value: initialValue, modifiers: node.modifiers ] 175 | } as Collection 176 | } 177 | 178 | private static void writeDebugFileFor( AnnotationNode annotation, String text ) { 179 | def outputFileMember = annotation?.getMember( 'outputDebugFile' ) 180 | String debugFileName = outputFileMember instanceof ConstantExpression ? 181 | ( ( ConstantExpression ) outputFileMember ).value : 182 | '' 183 | 184 | if ( debugFileName ) { 185 | log.fine( "Writing JasminCode to debug file: $debugFileName" ) 186 | try { 187 | def jFile = new File( debugFileName ) 188 | jFile.parentFile.mkdirs() 189 | jFile.write( text ) 190 | } catch ( e ) { 191 | log.warning( "Could not write to debug file '$debugFileName': $e" ) 192 | } 193 | } 194 | } 195 | 196 | private String jasminFileContents( String className, String methodBody, MethodNode methodNode ) { 197 | """ 198 | |.class public $className 199 | |.super java/lang/Object 200 | | 201 | |${defaultConstructor} 202 | | 203 | |.method public static ${typer.typeDescriptorOf( methodNode, 'run' )} 204 | | $methodBody 205 | |.end method""".stripMargin() 206 | } 207 | 208 | static String extractJasminMethodBody( Statement statement ) { 209 | def firstStatement = firstStatementOf( statement ) 210 | if ( firstStatement instanceof ExpressionStatement ) { 211 | def exprStatement = firstStatement as ExpressionStatement 212 | return valueOfExpression( exprStatement.expression ) 213 | } 214 | return null 215 | } 216 | 217 | private static String valueOfExpression( Expression expression ) { 218 | if ( expression instanceof PropertyExpression ) { 219 | return Eval.me( expression.text ) 220 | } else { 221 | return expression.text 222 | } 223 | 224 | } 225 | 226 | private static Statement firstStatementOf( Statement statement ) { 227 | if ( statement instanceof BlockStatement ) { 228 | statement.statements?.first() 229 | } else { 230 | statement 231 | } 232 | } 233 | 234 | private static void withTempFile( String fileName, Closure useFile ) { 235 | def tmpJFile = dir.toPath().resolve( fileName + '.j' ).toFile() 236 | try { 237 | useFile tmpJFile 238 | } finally { 239 | tmpJFile.delete() 240 | } 241 | } 242 | 243 | static final defaultConstructor = """ 244 | |.method private ()V 245 | | aload_0 246 | | invokenonvirtual java/lang/Object/()V 247 | | return 248 | |.end method""".stripMargin() 249 | 250 | } 251 | 252 | -------------------------------------------------------------------------------- /grasmin-core/src/main/groovy/com/athaydes/grasmin/GrasminASTTransformation.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import groovy.transform.CompileStatic 4 | import org.codehaus.groovy.ast.ASTNode 5 | import org.codehaus.groovy.ast.AnnotatedNode 6 | import org.codehaus.groovy.ast.ClassNode 7 | import org.codehaus.groovy.ast.MethodNode 8 | import org.codehaus.groovy.ast.expr.ArgumentListExpression 9 | import org.codehaus.groovy.ast.expr.ClassExpression 10 | import org.codehaus.groovy.ast.expr.MethodCallExpression 11 | import org.codehaus.groovy.ast.stmt.ExpressionStatement 12 | import org.codehaus.groovy.ast.stmt.Statement 13 | import org.codehaus.groovy.control.CompilePhase 14 | import org.codehaus.groovy.control.SourceUnit 15 | import org.codehaus.groovy.transform.ASTTransformation 16 | import org.codehaus.groovy.transform.GroovyASTTransformation 17 | 18 | import java.util.logging.Logger 19 | 20 | @GroovyASTTransformation( phase = CompilePhase.SEMANTIC_ANALYSIS ) 21 | @CompileStatic 22 | class GrasminASTTransformation implements ASTTransformation { 23 | 24 | Logger log = Logger.getLogger( GrasminASTTransformation.name ) 25 | static final Set processedSourceUnits = [ ] 26 | static final Grasmin grasmin = new Grasmin() 27 | 28 | void visit( ASTNode[] astNodes, SourceUnit sourceUnit ) { 29 | if ( !processedSourceUnits.add( sourceUnit ) ) return 30 | 31 | log.fine "Transforming sourceUnit ${sourceUnit.name}" 32 | 33 | if ( !sourceUnit || !sourceUnit.AST ) return 34 | 35 | def processNodes = { List nodes -> 36 | def annotatedNodes = nodes.findAll { AnnotatedNode node -> 37 | node.getAnnotations( new ClassNode( JasminCode ) ) 38 | } 39 | for ( AnnotatedNode node in annotatedNodes ) { 40 | switch ( node ) { 41 | case ClassNode: 42 | process( node as ClassNode, sourceUnit ) 43 | break 44 | case MethodNode: 45 | process( node as MethodNode, sourceUnit ) 46 | break 47 | default: 48 | throw new Exception( "Cannot annotate nodes of this type with @JasminCode: ${node?.class?.name}" ) 49 | } 50 | } 51 | } 52 | 53 | List classNodes = sourceUnit.getAST().getClasses() 54 | List methods = classNodes*.getMethods().flatten() as List 55 | 56 | processNodes classNodes 57 | processNodes methods 58 | } 59 | 60 | private void process( ClassNode classNode, SourceUnit sourceUnit ) { 61 | log.fine "Processing class $classNode.name" 62 | def targetDir = sourceUnit.configuration.targetDirectory 63 | 64 | grasmin.createJasminClass( classNode, targetDir ) 65 | 66 | sourceUnit.AST.classes.remove( classNode ) // replaced Groovy class with Jasmin class 67 | } 68 | 69 | private void process( MethodNode method, SourceUnit sourceUnit ) { 70 | if ( method.declaringClass.getAnnotations( new ClassNode( JasminCode ) ) ) { 71 | return 72 | } 73 | 74 | log.fine "Processing method $method.name" 75 | 76 | try { 77 | rewriteMethod( sourceUnit, method, grasmin.extractJasminMethodBody( method.code ) ) 78 | } catch ( Throwable e ) { 79 | log.warning e.toString() + ' ' + e.getCause()?.toString() 80 | } 81 | } 82 | 83 | private static void rewriteMethod( SourceUnit sourceUnit, MethodNode method, String assemblerText ) { 84 | if ( !assemblerText ) { 85 | throw new Exception( "No Jasmin code found in method ${method.name}" ) 86 | } 87 | def targetDir = sourceUnit.configuration.targetDirectory 88 | def className = classNameFor( method ) 89 | def jasminClass = grasmin.createJasminClass( assemblerText, targetDir, className, method ) 90 | def classNode = new ClassNode( jasminClass ) 91 | 92 | if ( !targetDir ) sourceUnit.getAST().addClass( classNode ) 93 | 94 | def jasminMethodCall = grassemblyStatement( classNode, method ) 95 | method.code = jasminMethodCall 96 | } 97 | 98 | static String classNameFor( MethodNode methodNode ) { 99 | methodNode.declaringClass.name.replace( '.', '_' ) + '_' + methodNode.name 100 | } 101 | 102 | private static Statement grassemblyStatement( ClassNode jasminClass, MethodNode methodNode ) { 103 | new ExpressionStatement( 104 | new MethodCallExpression( 105 | new ClassExpression( jasminClass ), 106 | "run", 107 | new ArgumentListExpression( methodNode.parameters ) 108 | ) 109 | ) 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /grasmin-core/src/main/groovy/com/athaydes/grasmin/JasminCode.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import org.codehaus.groovy.transform.GroovyASTTransformationClass 4 | 5 | import java.lang.annotation.ElementType 6 | import java.lang.annotation.Retention 7 | import java.lang.annotation.RetentionPolicy 8 | import java.lang.annotation.Target 9 | 10 | /** 11 | * Groovy AST Transform for specifying that the annotated element should be compiled as Jasmin code. 12 | * 13 | * A String (or simple expression that evaluates to a String) containing Jasmin code 14 | * (basically, JVM instructions) should be the first element of any annotated method. 15 | *

16 | * If a class is annotated, all of its methods will be considered as being annotated, and the whole 17 | * class will be turned into a single Jasmin 'j' file before being compiled to bytecode. 18 | *

19 | * If a method is annotated, a class will be generated to host a single method with the Jasmin code 20 | * provided. The annotated method will then delegate to the generated one, which incurs a runtime 21 | * cost. For this reason, prefer to annotate classes, as they do not incur this overhead. 22 | */ 23 | @Retention( RetentionPolicy.SOURCE ) 24 | @Target( [ ElementType.METHOD, ElementType.TYPE ] ) 25 | @GroovyASTTransformationClass( [ "com.athaydes.grasmin.GrasminASTTransformation" ] ) 26 | public @interface JasminCode { 27 | 28 | /** 29 | * path to a file where the Jasmin file contents should be output for debugging. 30 | * 31 | * @return path to debugging file 32 | */ 33 | String outputDebugFile() default "" 34 | 35 | } 36 | -------------------------------------------------------------------------------- /grasmin-core/src/main/groovy/com/athaydes/grasmin/JasminTyper.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import groovy.transform.CompileStatic 4 | import org.codehaus.groovy.ast.MethodNode 5 | import org.codehaus.groovy.ast.Parameter 6 | 7 | import java.lang.reflect.Modifier 8 | 9 | /** 10 | * Translates classes into Jasmin types (JVM types). 11 | */ 12 | @CompileStatic 13 | class JasminTyper { 14 | 15 | static String typeDescriptorOf( MethodNode methodNode, String newMethodName = null ) { 16 | def paramTypes = methodNode.parameters.collect { Parameter p -> typeNameFor( p.type.name ) }.join( '' ) 17 | def returnType = typeNameFor( methodNode.returnType.name ) 18 | "${newMethodName ?: methodNode.name}(${paramTypes})${returnType}" 19 | } 20 | 21 | static String className( String javaClassName ) { 22 | javaClassName.replace( '.', '/' ) 23 | } 24 | 25 | static String typeNameFor( String type ) { 26 | switch ( type ) { 27 | // primitive types 28 | case 'int': return 'I' 29 | case 'float': return 'F' 30 | case 'byte': return 'B' 31 | case 'char': return 'C' 32 | case 'double': return 'D' 33 | case 'long': return 'J' 34 | case 'short': return 'S' 35 | case 'boolean': return 'Z' 36 | // arrays of primitive types 37 | case 'int[]': return '[I' 38 | case 'float[]': return '[F' 39 | case 'byte[]': return '[B' 40 | case 'char[]': return '[C' 41 | case 'double[]': return '[D' 42 | case 'long[]': return '[J' 43 | case 'short[]': return '[S' 44 | case 'boolean[]': return '[Z' 45 | // void 46 | case 'void': 47 | case 'Void': return 'V' 48 | // references 49 | default: return nonPrimitiveTypeDescription( type ) 50 | } 51 | } 52 | 53 | static String nonPrimitiveTypeDescription( String type ) { 54 | type.startsWith( '[' ) ? 55 | // arrays already come with the correct type name 56 | type : 57 | 'L' + className( type ) + ';' 58 | } 59 | 60 | static String modifiersString( int modifiers ) { 61 | def result = Modifier.isPublic( modifiers ) ? 'public ' : '' 62 | result += Modifier.isPrivate( modifiers ) ? 'private ' : '' 63 | result += Modifier.isProtected( modifiers ) ? 'protected ' : '' 64 | result += Modifier.isAbstract( modifiers ) ? 'abstract ' : '' 65 | result += Modifier.isFinal( modifiers ) ? 'final ' : '' 66 | result += Modifier.isStatic( modifiers ) ? 'static ' : '' 67 | result += Modifier.isSynchronized( modifiers ) ? 'synchronized ' : '' 68 | result += Modifier.isVolatile( modifiers ) ? 'volatile ' : '' 69 | result += Modifier.isTransient( modifiers ) ? 'transient ' : '' 70 | result += Modifier.isStrict( modifiers ) ? 'strictfp' : '' 71 | result.trim() 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /grasmin-core/src/test/groovy/com/athaydes/grasmin/GrasminASTTransformationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import org.codehaus.groovy.control.CompilePhase 4 | import org.codehaus.groovy.tools.ast.TransformTestHelper 5 | 6 | class GrasminASTTransformationTest extends GroovyTestCase { 7 | 8 | void testTransformation() { 9 | def helper = new TransformTestHelper( 10 | new GrasminASTTransformation(), 11 | CompilePhase.SEMANTIC_ANALYSIS ) 12 | 13 | def test1 = helper.parse( this.class.getResource( '/Test1.groovy' ).path as File ) 14 | def inst = test1.newInstance() 15 | assert inst.exampleJasminCode() == null 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /grasmin-core/src/test/groovy/com/athaydes/grasmin/Hello.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import groovy.transform.CompileStatic 4 | import org.junit.Test 5 | 6 | class Hello { 7 | 8 | @JasminCode 9 | static void main( args ) { 10 | """ 11 | .limit stack 2 12 | getstatic java/lang/System/out Ljava/io/PrintStream; 13 | ldc "Hello World!" 14 | invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 15 | return 16 | """ 17 | } 18 | 19 | @Test 20 | void testSum() { 21 | assert sum( 2, 3 ) == 5 22 | assert sum( 6, 5 ) == 11 23 | } 24 | 25 | @CompileStatic 26 | @JasminCode 27 | int sum( int a, int b ) { 28 | """ 29 | .limit stack 2 30 | .limit locals 2 31 | iload_0 32 | iload_1 33 | iadd 34 | ireturn 35 | """ 36 | 5000 // ignored, but makes the IDE happy 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /grasmin-core/src/test/groovy/com/athaydes/grasmin/JasminTyperSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.athaydes.grasmin 2 | 3 | import groovy.transform.TypeChecked 4 | import groovy.transform.TypeCheckingMode 5 | import org.codehaus.groovy.ast.ClassNode 6 | import org.codehaus.groovy.ast.MethodNode 7 | import org.codehaus.groovy.ast.Parameter 8 | import org.codehaus.groovy.ast.stmt.Statement 9 | import spock.lang.Specification 10 | 11 | import java.lang.reflect.Modifier 12 | 13 | /** 14 | * 15 | */ 16 | @TypeChecked 17 | class JasminTyperSpec extends Specification { 18 | 19 | def "A Class object can be translated into a JVM type descriptor as per the class file format"( 20 | Class type, String expected ) { 21 | when: 'A class is translated to a JVM type descriptor' 22 | def result = JasminTyper.typeNameFor( type.name ) 23 | 24 | then: 'The result is as described in the class file format specification' 25 | result == expected 26 | 27 | where: 28 | type | expected 29 | boolean | 'Z' 30 | float | 'F' 31 | int | 'I' 32 | Boolean | 'Ljava/lang/Boolean;' 33 | String | 'Ljava/lang/String;' 34 | Thread | 'Ljava/lang/Thread;' 35 | } 36 | 37 | def "Type Descriptors can be correctly discovered from a MethodNode"( 38 | String methodName, Collection paramTypes, Class returnType, String newMethodName, String expected ) { 39 | given: 'A MethodNode for some example methods' 40 | def node = methodNode( methodName, paramTypes, returnType ) 41 | 42 | when: 'The type descriptor for a MethodNode is requested' 43 | def result = newMethodName ? 44 | JasminTyper.typeDescriptorOf( node, newMethodName ) : 45 | JasminTyper.typeDescriptorOf( node ) 46 | 47 | then: 'The result is as described in the class file format specification' 48 | result == expected 49 | 50 | where: 51 | methodName | paramTypes | returnType | newMethodName || expected 52 | 'a' | [ ] | void | null || 'a()V' 53 | 'xyz' | [ boolean ] | int | null || 'xyz(Z)I' 54 | 'm' | [ int, double, Thread ] | Object | 'run' || 'run(IDLjava/lang/Thread;)Ljava/lang/Object;' 55 | 56 | } 57 | 58 | final LinkedList paramNames = 'a'..'z' as LinkedList 59 | 60 | MethodNode methodNode( String methodName, Collection paramTypes, Class returnType ) { 61 | new MethodNode( methodName, 0, new ClassNode( returnType ), 62 | paramTypes.collect { new Parameter( new ClassNode( it ), paramNames.remove() ) } as Parameter[], 63 | [ ] as ClassNode[], new Statement() ) 64 | } 65 | 66 | // IntelliJ has trouble with int methods 67 | @TypeChecked( TypeCheckingMode.SKIP ) 68 | "Can collect all modifiers of a class"( int modifiers, String expected ) { 69 | when: "All modifiers of a class are requested" 70 | def result = JasminTyper.modifiersString( modifiers ) 71 | 72 | then: "The expected value is returned" 73 | result == expected 74 | 75 | where: 76 | modifiers | expected 77 | 0 | '' 78 | Modifier.PUBLIC | 'public' 79 | Modifier.PUBLIC.or( Modifier.ABSTRACT ) | 'public abstract' 80 | Modifier.PROTECTED.or( Modifier.FINAL ).or( Modifier.VOLATILE ) | 'protected final volatile' 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /grasmin-core/src/test/resources/AnnotatedClass.groovy: -------------------------------------------------------------------------------- 1 | import com.athaydes.grasmin.JasminCode 2 | 3 | @JasminCode 4 | final class AnnotatedClass { 5 | 6 | @JasminCode 7 | void annotatedMethodJasminCode() { 8 | """ 9 | .limit stack 2 10 | getstatic java/lang/System/out Ljava/io/PrintStream; 11 | ldc "Hello World!" 12 | invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 13 | return 14 | """ 15 | } 16 | 17 | void noAnnotationJasminCode() { 18 | """ 19 | .limit stack 2 20 | getstatic java/lang/System/out Ljava/io/PrintStream; 21 | ldc "Hello World!" 22 | invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 23 | return 24 | """ 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /grasmin-core/src/test/resources/Test1.groovy: -------------------------------------------------------------------------------- 1 | import com.athaydes.grasmin.JasminCode 2 | import groovy.transform.CompileStatic 3 | import org.codehaus.groovy.ast.ClassNode 4 | 5 | /** 6 | * See JVM instructions on http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html 7 | */ 8 | class Test1 { 9 | 10 | @JasminCode 11 | @CompileStatic 12 | void exampleJasminCode() { 13 | """ 14 | .limit stack 2 15 | getstatic java/lang/System/out Ljava/io/PrintStream; 16 | ldc "Hello World!" 17 | invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 18 | return 19 | """ 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /grasmin-tests/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | 3 | repositories { 4 | mavenLocal() 5 | jcenter() 6 | } 7 | 8 | sourceCompatibility = 1.8 9 | targetCompatibility = 1.8 10 | 11 | dependencies { 12 | compile project( ':grasmin-core' ) 13 | 14 | testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-3" 15 | } 16 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/ArraysCodeTest.groovy: -------------------------------------------------------------------------------- 1 | package grasmin 2 | 3 | import grasmin.test_target.ArraysCode 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | class ArraysCodeTest extends Specification { 8 | 9 | def "Methods that take String arrays can be compiled correctly"() { 10 | when: 'we call the main method of a Grasmin class' 11 | new ArraysCode().main( [ 'hello', 'world' ] as String[] ) 12 | 13 | then: 'no error occurs' 14 | noExceptionThrown() 15 | } 16 | 17 | @Unroll 18 | "Can use array argument"() { 19 | when: 'we ask for the length of an array' 20 | def result = new ArraysCode().len( array as boolean[] ) 21 | 22 | then: 'the correct answer is given' 23 | result == length 24 | 25 | where: 26 | array | length 27 | [ ] | 0 28 | [ true ] | 1 29 | [ true, true ] | 2 30 | [ true, false, true, false ] | 4 31 | } 32 | 33 | @Unroll 34 | "Can read and return arrays"() { 35 | when: 'we stringify a int[]' 36 | def result = new ArraysCode().stringify( ints as int[] ) 37 | 38 | then: 'the expected array is given' 39 | Arrays.equals( result, ( expectedArray as String[] ) ) 40 | 41 | where: 42 | ints | expectedArray 43 | [ ] | [ ] 44 | [ 1 ] | [ '1' ] 45 | [ 400, 257, 100 ] | [ '400', '257', '100' ] 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/ClassLevelJasminCodeTest.groovy: -------------------------------------------------------------------------------- 1 | package grasmin 2 | 3 | import grasmin.test_target.JasminCodeClass 4 | import grasmin.test_target.JasminCodeClassComplex 5 | import org.junit.Test 6 | 7 | /** 8 | * 9 | */ 10 | class ClassLevelJasminCodeTest { 11 | 12 | @Test 13 | void shouldCompileAllMethods() { 14 | def jasminCode = new JasminCodeClass() 15 | assert jasminCode.'10' == 10 16 | assert jasminCode.hello( 'abc' ) == 'Hello abc' 17 | } 18 | 19 | @Test 20 | void shouldBeAbleToSeeFields() { 21 | def jasminCode = new JasminCodeClass() 22 | assert jasminCode.getName() == 'Joda' 23 | } 24 | 25 | @Test 26 | void constructorWorks() { 27 | def instance = new JasminCodeClass( 'Spock', 42 ) 28 | assert instance.getName() == 'Spock' 29 | assert instance.getI() == 42 30 | } 31 | 32 | @Test 33 | void canAddIntegers() { 34 | def instance = new JasminCodeClass( 'Spock', 42 ) 35 | assert instance.ints == 5 36 | } 37 | 38 | @Test 39 | void canDeclareDefaultConstructor() { 40 | def instance = new JasminCodeClassComplex() 41 | assert instance.getName() == 'John' 42 | assert instance.getI() == 55 43 | } 44 | 45 | @Test 46 | void canDeclareStaticFinalFields() { 47 | assert JasminCodeClassComplex.staticString == 'a-string' 48 | assert JasminCodeClassComplex.staticInt == 123 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/GrasminTest.groovy: -------------------------------------------------------------------------------- 1 | package grasmin 2 | 3 | import com.athaydes.grasmin.JasminCode 4 | import groovy.transform.CompileStatic 5 | import groovy.transform.TailRecursive 6 | 7 | /** 8 | * 9 | */ 10 | class GrasminTest extends GroovyTestCase { 11 | 12 | void testGcd() { 13 | assert groovyGcd( 42, 56 ) == 14 14 | assert groovyStaticGcd( 42, 56 ) == 14 15 | assert tailRecursiveGcd( 42, 56 ) == 14 16 | assert grasmineGcd( 42, 56 ) == 14 17 | } 18 | 19 | void testReturnInt() { 20 | assert return500() == 500 21 | } 22 | 23 | int groovyGcd( int u, int v ) { 24 | if ( u < v ) { 25 | def tmp = u 26 | u = v 27 | v = tmp 28 | } 29 | if ( v == 0 ) { 30 | return u 31 | } 32 | return groovyGcd( v, u - v ) 33 | } 34 | 35 | @CompileStatic 36 | @TailRecursive 37 | int tailRecursiveGcd( int u, int v ) { 38 | if ( u < v ) { 39 | def tmp = u 40 | u = v 41 | v = tmp 42 | } 43 | if ( v == 0 ) { 44 | return u 45 | } 46 | return tailRecursiveGcd( v, u - v ) 47 | } 48 | 49 | @CompileStatic 50 | int groovyStaticGcd( int u, int v ) { 51 | int small = u 52 | int large = v 53 | while ( small != 0 ) { 54 | if ( large < small ) { 55 | def tmp = small 56 | small = large 57 | large = tmp 58 | } 59 | if ( small != 0 ) large -= small 60 | 61 | } 62 | return large 63 | } 64 | 65 | @JasminCode 66 | @CompileStatic 67 | int grasmineGcd( int a, int b ) { 68 | """ 69 | .limit stack 3 70 | .limit locals 3 71 | 72 | Begin: 73 | iload_0 74 | iload_1 ; result: var0 -> var1 75 | 76 | if_icmplt Swap ; if var0 < var1 Swap 77 | iload_0 78 | iload_1 ; result: var0 (large) -> var1 (small) 79 | dup 80 | istore_2 ; var2 = small 81 | goto A 82 | 83 | Swap: 84 | iload_1 85 | iload_0 ; result: var1 (large) -> var0 (small) 86 | dup 87 | istore_2 ; var2 = small 88 | 89 | A: 90 | ifeq Return ; if smaller == 0 Return 91 | iload_2 ; result: large -> small 92 | swap ; result: small -> large 93 | iload_2 ; result: small -> large -> small 94 | isub ; result: small -> large-small 95 | istore_0 96 | istore_1 97 | goto Begin 98 | 99 | Return: 100 | ireturn 101 | """ 102 | 0 103 | } 104 | 105 | @JasminCode 106 | @CompileStatic 107 | int return500() { 108 | """ 109 | .limit stack 1 110 | ldc 500 111 | ireturn 112 | """ 113 | 0 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/HandCodedLoopPerformanceTest.groovy: -------------------------------------------------------------------------------- 1 | package grasmin 2 | 3 | import grasmin.test_target.ArraysCode 4 | import grasmin.test_target.JavaArrays 5 | import groovy.transform.CompileStatic 6 | import javafx.util.Pair 7 | 8 | import java.util.function.Function 9 | 10 | class HandCodedLoopPerformanceTest { 11 | 12 | static final File reportFile = new File( 'loop-performance.csv' ) 13 | static final Random random = new Random() 14 | 15 | static void main( String[] args ) { 16 | def arrays = new ArraysCode() 17 | def java = new JavaArrays() 18 | 19 | run 100, [ 20 | new Pair>( 'ArraysCode', arrays.&stringify ), 21 | new Pair>( 'JavaArrays', java.&stringify ), 22 | ] 23 | } 24 | 25 | @CompileStatic 26 | static void run( int count, List>> tests ) { 27 | reportFile.delete() 28 | final writer = reportFile.newPrintWriter() 29 | 30 | writer.println( 'run,' + tests*.key.join( ',' ) ) 31 | 32 | count.times { int i -> 33 | def times = tests*.value.collect { action -> 34 | def input = ( 1..10000 ).collect { PerformanceTest.randomPositiveInt( random ) } as int[] 35 | withTimer { ( action as Function ).apply( input ) } 36 | } 37 | writer.println( "$i," + times.join( ',' ) ) 38 | } 39 | 40 | writer.close() 41 | } 42 | 43 | @CompileStatic 44 | static long withTimer( Runnable action ) { 45 | long result = -1 46 | long startTime = System.nanoTime() 47 | try { 48 | action.run() 49 | result = System.nanoTime() - startTime 50 | } catch ( e ) { 51 | e.printStackTrace() 52 | } 53 | 54 | return result 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/JavaGcd.java: -------------------------------------------------------------------------------- 1 | package grasmin; 2 | 3 | /** 4 | * 5 | */ 6 | public class JavaGcd { 7 | 8 | public int javaRecursiveGcd(int u, int v) { 9 | if (u < v) { 10 | int tmp = u; 11 | u = v; 12 | v = tmp; 13 | } 14 | if (v == 0) { 15 | return u; 16 | } 17 | return javaRecursiveGcd(v, u - v); 18 | } 19 | 20 | public int javaGcd(int u, int v) { 21 | int small = u; 22 | int large = v; 23 | while (small != 0) { 24 | if (large < small) { 25 | int tmp = small; 26 | small = large; 27 | large = tmp; 28 | } 29 | if (small != 0) large -= small; 30 | 31 | } 32 | return large; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/PerformanceTest.java: -------------------------------------------------------------------------------- 1 | package grasmin; 2 | 3 | import java.io.File; 4 | import java.lang.management.ManagementFactory; 5 | import java.lang.management.ThreadMXBean; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | /** 11 | * 12 | */ 13 | public class PerformanceTest { 14 | 15 | static final int MAX_INT = (int) Math.pow(2, 30); 16 | static final int MAX_DATA_POINTS_PER_RUN = 10000; 17 | 18 | abstract static class TestThread extends Thread { 19 | 20 | public final List results = new ArrayList(MAX_DATA_POINTS_PER_RUN); 21 | public volatile int[][] datapoints; 22 | public volatile long cpuTime; 23 | 24 | TestThread(String name) { 25 | super(name); 26 | } 27 | 28 | abstract int gcd(int a, int b); 29 | 30 | @Override 31 | public void run() { 32 | try { 33 | long start = System.nanoTime(); 34 | for (int[] point : datapoints) { 35 | results.add(gcd(point[0], point[1])); 36 | } 37 | cpuTime = System.nanoTime() - start; 38 | } catch (Throwable t) { 39 | System.out.println("Ignoring problem running thread '" + getName() + "': " + t); 40 | } 41 | } 42 | 43 | } 44 | 45 | final GrasminTest grasminTest = new GrasminTest(); 46 | final JavaGcd javaGcd = new JavaGcd(); 47 | final PerformanceTestResultCollector collector = new PerformanceTestResultCollector(); 48 | final File report = new File("/temp/grasmin-report.csv"); 49 | 50 | TestThread java() { 51 | return new TestThread("java") { 52 | @Override 53 | int gcd(int a, int b) { 54 | return javaGcd.javaGcd(a, b); 55 | } 56 | }; 57 | } 58 | 59 | TestThread groovyStatic() { 60 | return new TestThread("groovyStatic") { 61 | @Override 62 | int gcd(int a, int b) { 63 | return grasminTest.groovyStaticGcd(a, b); 64 | } 65 | }; 66 | } 67 | 68 | TestThread groovyTailRecursive() { 69 | return new TestThread("groovyTailRecursive") { 70 | @Override 71 | int gcd(int a, int b) { 72 | return grasminTest.tailRecursiveGcd(a, b); 73 | } 74 | }; 75 | } 76 | 77 | TestThread grasmine() { 78 | return new TestThread("grasmine") { 79 | @Override 80 | int gcd(int a, int b) { 81 | return grasminTest.grasmineGcd(a, b); 82 | } 83 | }; 84 | } 85 | 86 | void testPerformance() throws InterruptedException { 87 | 88 | // warmup 89 | runAll(java(), groovyStatic(), groovyTailRecursive(), grasmine()); 90 | 91 | // tests 92 | collector.writeHeaders(report, "java", "groovyStatic", "groovyTailRecursive", "grasmine"); 93 | for (int i = 0; i < 100; i++) { 94 | System.gc(); 95 | TestThread java = java(); 96 | TestThread groovyStatic = groovyStatic(); 97 | TestThread groovyTailRecursive = groovyTailRecursive(); 98 | TestThread grasmine = grasmine(); 99 | runAll(java, groovyStatic, groovyTailRecursive, grasmine); 100 | collector.writeResults(report, java, groovyStatic, groovyTailRecursive, grasmine); 101 | } 102 | 103 | } 104 | 105 | private void runAll(TestThread... threads) throws InterruptedException { 106 | for (TestThread t : threads) runTest(t); 107 | } 108 | 109 | private void runTest(TestThread thread) throws InterruptedException { 110 | thread.datapoints = randomIntPairs(); 111 | thread.start(); 112 | thread.join(); 113 | } 114 | 115 | public static void main(String[] args) throws InterruptedException { 116 | new PerformanceTest().testPerformance(); 117 | } 118 | 119 | static int[][] randomIntPairs() { 120 | Random random = new Random(); 121 | int[][] datapoints = new int[MAX_DATA_POINTS_PER_RUN][2]; 122 | for (int[] point : datapoints) { 123 | point[0] = randomPositiveInt(random); 124 | point[1] = randomPositiveInt(random); 125 | //System.out.print(Arrays.toString(point)); 126 | } 127 | //System.out.println(); 128 | return datapoints; 129 | } 130 | 131 | static int randomPositiveInt(Random random) { 132 | return random.nextInt(MAX_INT) + 1; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/PerformanceTestResultCollector.groovy: -------------------------------------------------------------------------------- 1 | package grasmin 2 | 3 | import grasmin.PerformanceTest.TestThread 4 | 5 | import static java.math.BigInteger.valueOf as Int 6 | 7 | /** 8 | * 9 | */ 10 | class PerformanceTestResultCollector { 11 | 12 | void writeHeaders( File location, String... names ) { 13 | if ( location.exists() ) location.delete() 14 | location << names.join( ',' ) + '\n' 15 | } 16 | 17 | void writeResults( File location, TestThread... threads ) { 18 | location << threads.collect { verify( it ) ? it.cpuTime : -1 }.join( ',' ) + '\n' 19 | } 20 | 21 | private boolean verify( TestThread testThread ) { 22 | try { 23 | assert testThread.results.size() == PerformanceTest.MAX_DATA_POINTS_PER_RUN 24 | assert testThread.datapoints.length == PerformanceTest.MAX_DATA_POINTS_PER_RUN 25 | for ( point in testThread.datapoints ) { 26 | assert gcd( *point ) == testThread.results.remove( 0 ) 27 | } 28 | return true 29 | } catch ( AssertionError e ) { 30 | println "Error! $e" 31 | return false 32 | } 33 | } 34 | 35 | private int gcd( int a, int b ) { 36 | verifyInts( a, b ) 37 | Int( a ).gcd( Int( b ) ) 38 | } 39 | 40 | private void verifyInts( int ... xs ) { 41 | xs.each { assert 0 < it && it < PerformanceTest.MAX_INT } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/test_target/ArraysCode.groovy: -------------------------------------------------------------------------------- 1 | package grasmin.test_target 2 | 3 | import com.athaydes.grasmin.JasminCode 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | @JasminCode( outputDebugFile = 'build/arrays-code.j' ) 8 | class ArraysCode { 9 | 10 | static void main( String[] args ) { 11 | """\ 12 | .limit stack 2 13 | getstatic java/lang/System/out Ljava/io/PrintStream; 14 | ldc "Hello World!" 15 | invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 16 | return 17 | """ 18 | } 19 | 20 | int len( boolean[] b ) { 21 | """\ 22 | .limit locals 2 23 | .limit stack 1 24 | aload_1 25 | arraylength 26 | ireturn 27 | """ 28 | 0 29 | } 30 | 31 | String[] stringify( int[] ints ) { 32 | """\ 33 | .limit locals 4 34 | .limit stack 3 35 | aload_1 36 | arraylength ; put the length of the array on the stack 37 | istore_2 ; store the array length on local variable 2 38 | iload_2 ; read the array length 39 | anewarray java/lang/String ; create a String array with the same length as the input array 40 | astore_3 ; store the result in the local variable 3 41 | Loop: 42 | iload_2 43 | ifeq End ; check if the remaining length is 0, go to End if so 44 | iinc 2 -1 ; decrement index 45 | aload_1 ; load the input array 46 | iload_2 ; load current index 47 | iaload ; read current value 48 | invokestatic java/lang/Integer.toString(I)Ljava/lang/String; 49 | aload_3 ; load the result array 50 | swap ; swap so the String value is on top of the stack 51 | iload_2 ; load current index 52 | swap ; swap so the stack has arrayRef, index, value 53 | aastore ; put the result String into the result array 54 | goto Loop ; loop 55 | End: 56 | aload_3 57 | areturn 58 | """ 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/test_target/JasminCodeClass.groovy: -------------------------------------------------------------------------------- 1 | package grasmin.test_target 2 | 3 | import com.athaydes.grasmin.JasminCode 4 | 5 | @JasminCode( outputDebugFile = 'build/the-jasmin-code-class.j' ) 6 | class JasminCodeClass extends Object { 7 | 8 | private String name = 'Joda' 9 | private int i 10 | 11 | JasminCodeClass( String name, int i ) { 12 | """\ 13 | .limit locals 3 14 | .limit stack 2 15 | aload_0 16 | invokenonvirtual java/lang/Object/()V 17 | aload_0 18 | aload_1 19 | putfield grasmin/test_target/JasminCodeClass/name Ljava/lang/String; 20 | aload_0 21 | iload_2 22 | putfield grasmin/test_target/JasminCodeClass/i I 23 | return""" 24 | } 25 | 26 | int get10() { 27 | """\ 28 | .limit stack 1 29 | ldc 10 30 | ireturn""" 31 | -1 32 | } 33 | 34 | String hello( String name ) { 35 | """\ 36 | .limit locals 2 37 | .limit stack 2 38 | ldc "Hello " 39 | aload_1 40 | invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; 41 | areturn""" 42 | //"Hello ".concat( "blha" ) 43 | } 44 | 45 | public String getName() { 46 | """\ 47 | aload_0 48 | getfield grasmin/test_target/JasminCodeClass/name Ljava/lang/String; 49 | areturn""" 50 | } 51 | 52 | public int getI() { 53 | """\ 54 | aload_0 55 | getfield grasmin/test_target/JasminCodeClass/i I 56 | ireturn""" 57 | 111 // ignored 58 | } 59 | 60 | int getInts() { 61 | """\ 62 | .limit stack 2 63 | iconst_2 64 | iconst_3 65 | iadd 66 | ireturn 67 | """ 68 | 0 // ignored 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/test_target/JasminCodeClassComplex.groovy: -------------------------------------------------------------------------------- 1 | package grasmin.test_target 2 | 3 | import com.athaydes.grasmin.JasminCode 4 | 5 | @JasminCode( outputDebugFile = 'build/the-jasmin-code-complex-class.j' ) 6 | class JasminCodeClassComplex extends Object { 7 | 8 | private String name = 'Joda' 9 | private int i 10 | 11 | public static final String staticString = 'a-string' 12 | public static final int staticInt = 123 13 | 14 | JasminCodeClassComplex() { 15 | """\ 16 | .limit stack 2 17 | aload_0 18 | invokenonvirtual java/lang/Object/()V 19 | aload_0 20 | ldc "John" 21 | putfield grasmin/test_target/JasminCodeClassComplex/name Ljava/lang/String; 22 | aload_0 23 | bipush 55 24 | putfield grasmin/test_target/JasminCodeClassComplex/i I 25 | return""" 26 | } 27 | 28 | String concat( String a, String b ) { 29 | """\ 30 | .limit locals 3 31 | .limit stack 2 32 | aload_1 33 | aload_2 34 | invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; 35 | areturn""" 36 | //"Hello ".concat( "blha" ) 37 | } 38 | 39 | public String getName() { 40 | """\ 41 | aload_0 42 | getfield grasmin/test_target/JasminCodeClassComplex/name Ljava/lang/String; 43 | areturn""" 44 | } 45 | 46 | public int getI() { 47 | """\ 48 | aload_0 49 | getfield grasmin/test_target/JasminCodeClassComplex/i I 50 | ireturn""" 51 | 111 // ignored 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /grasmin-tests/src/test/groovy/grasmin/test_target/JavaArrays.java: -------------------------------------------------------------------------------- 1 | package grasmin.test_target; 2 | 3 | public class JavaArrays { 4 | 5 | public String[] stringify( int[] ints ) { 6 | String[] result = new String[ ints.length ]; 7 | for (int i = 0; i < ints.length; i++) { 8 | result[ i ] = Integer.toString( ints[ i ] ); 9 | } 10 | return result; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /libs/jasmin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatoathaydes/Grasmin/b2484af3d49a0694f501925051c5cbb19e77b263/libs/jasmin.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'grasmin' 2 | 3 | include 'grasmin-core' 4 | include 'grasmin-tests' 5 | 6 | --------------------------------------------------------------------------------