This article is the 17th English translation of Creating JVM Language. The inconsistency between the original code and the original code has been corrected in the new code repository. We suggest you refer to the new code repository.

The source code

Github

Bytecode – A generic description of the JVM language

All JVM-based programming languages are eventually compiled into bytecode, which is then loaded and interpreted by the virtual machine, meaning that the virtual machine does not know what language generated bytecode. As long as the class is on the classpath.

This provides concrete possibilities for Enkel to call Java libraries and other frameworks.

Find class methods and constructors

To be able to refer to other classes, there are two options:

  • The runtime trusts the programmer and does not verify the generated bytecode, which can cause the JVM to throw an exception if the CLASspath does not exist
  • Validation at compile time before bytecode generation. If validation fails, the compilation process is aborted.

In Enkel, I decided to use the second approach, mainly for security reasons. We can use the reflection API to do this:

public class ClassPathScope { public Optional<FunctionSignature> getMethodSignature(Type owner, String methodName, List<Type> arguments) { try { Class<? > methodOwnerClass = owner.getTypeClass(); Class<? >[] params = arguments.stream() .map(Type::getTypeClass).toArray(Class<? >[]::new); Method method = methodOwnerClass.getMethod(methodName,params);return Optional.of(ReflectionObjectToSignatureMapper.fromMethod(method));
     } catch (Exception e) {
         returnOptional.empty(); } } public Optional<FunctionSignature> getConstructorSignature(String className, List<Type> arguments) { try { Class<? > methodOwnerClass = Class.forName(className); Class<? >[] params = arguments.stream() .map(Type::getTypeClass).toArray(Class<? >[]::new); Constructor<? > constructor = methodOwnerClass.getConstructor(params);return Optional.of(ReflectionObjectToSignatureMapper.fromConstructor(constructor));
     } catch (Exception e) {
         returnOptional.empty(); }}}Copy the code

If the method or constructor does not exist, an exception is thrown and compilation terminates:

    //Scope.java
    return new ClassPathScope().getMethodSignature(owner.get(), methodName, argumentsTypes)
                    .orElseThrow(() -> new MethodSignatureNotFoundException(this,methodName,arguments));
Copy the code

It seems safer, but it’s also slower. All dependencies need to be resolved by reflection at compile time.

The sample

Call other Enkel classes

Here we call Library’s methods from the Client class:

 Client {
 
     start {
         print "Client: Calling my own 'Library' class:"Var addition = new Library() var addition = mylibrary.add (5,2)print "Client: Result returned from 'Library.add' = " + addition
     }
 
 }
Copy the code
Library {

    int add(int x,int y) {
        print "Library: add() method called"
        return x+y
    }

}
Copy the code

Here we need to compile Library first (we currently do not support simultaneous compilation of multiple files), otherwise the Client will not be able to compile.

kuba@kuba-laptop:~/repos/Enkel-JVM-language$ java -classpath The compiler/target/compiler - 1.0 - the SNAPSHOT - jar - with - dependencies. Jar: com.kubadziworski.compiler.Com piler EnkelExamples/ClassPathCalls/Library.enk kuba@kuba-laptop:~/repos/Enkel-JVM-language$ java -classpath The compiler/target/compiler - 1.0 - the SNAPSHOT - jar - with - dependencies. Jar: com.kubadziworski.compiler.Com piler EnkelExamples/ClassPathCalls/Client.enk kuba@kuba-laptop:~/repos/Enkel-JVM-language$ java Client Client: Calling my own'Library' class:
Library: add() method called
Client: Result returned from 'Library.add' = 7
Copy the code

Call the Java API


    start {
        var someString = "someString"
        print someString + " to upper case : " +  someString.toUpperCase()
    }

}
Copy the code
$ java Client 
cos to upper case = COS
Copy the code