mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Добавлен модуль java
This commit is contained in:
parent
a6efd831e0
commit
3b90e544a7
@ -7,7 +7,7 @@ import java.util.Objects;
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public final class FunctionValue implements Value {
|
||||
public class FunctionValue implements Value {
|
||||
|
||||
public static final FunctionValue EMPTY = new FunctionValue(args -> NumberValue.ZERO);
|
||||
|
||||
|
459
src/com/annimon/ownlang/lib/modules/java.java
Normal file
459
src/com/annimon/ownlang/lib/modules/java.java
Normal file
@ -0,0 +1,459 @@
|
||||
package com.annimon.ownlang.lib.modules;
|
||||
|
||||
import com.annimon.ownlang.lib.*;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Java interoperability module.
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public final class java implements Module {
|
||||
|
||||
private static final Value NULL = new NullValue();
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
Variables.define("null", NULL);
|
||||
Variables.define("boolean.class", new ClassValue(boolean.class));
|
||||
Variables.define("boolean[].class", new ClassValue(boolean[].class));
|
||||
Variables.define("boolean[][].class", new ClassValue(boolean[][].class));
|
||||
Variables.define("byte.class", new ClassValue(byte.class));
|
||||
Variables.define("byte[].class", new ClassValue(byte[].class));
|
||||
Variables.define("byte[][].class", new ClassValue(byte[][].class));
|
||||
Variables.define("short.class", new ClassValue(short.class));
|
||||
Variables.define("short[].class", new ClassValue(short[].class));
|
||||
Variables.define("short[][].class", new ClassValue(short[][].class));
|
||||
Variables.define("char.class", new ClassValue(char.class));
|
||||
Variables.define("char[].class", new ClassValue(char[].class));
|
||||
Variables.define("char[][].class", new ClassValue(char[][].class));
|
||||
Variables.define("int.class", new ClassValue(int.class));
|
||||
Variables.define("int[].class", new ClassValue(int[].class));
|
||||
Variables.define("int[][].class", new ClassValue(int[][].class));
|
||||
Variables.define("long.class", new ClassValue(long.class));
|
||||
Variables.define("long[].class", new ClassValue(long[].class));
|
||||
Variables.define("long[][].class", new ClassValue(long[][].class));
|
||||
Variables.define("float.class", new ClassValue(float.class));
|
||||
Variables.define("float[].class", new ClassValue(float[].class));
|
||||
Variables.define("float[][].class", new ClassValue(float[][].class));
|
||||
Variables.define("double.class", new ClassValue(double.class));
|
||||
Variables.define("double[].class", new ClassValue(double[].class));
|
||||
Variables.define("double[][].class", new ClassValue(double[][].class));
|
||||
Variables.define("String.class", new ClassValue(String.class));
|
||||
Variables.define("String[].class", new ClassValue(String[].class));
|
||||
Variables.define("String[][].class", new ClassValue(String[][].class));
|
||||
Variables.define("Object.class", new ClassValue(Object.class));
|
||||
Variables.define("Object[].class", new ClassValue(Object[].class));
|
||||
Variables.define("Object[][].class", new ClassValue(Object[][].class));
|
||||
|
||||
Functions.set("isNull", this::isNull);
|
||||
Functions.set("newClass", this::newClass);
|
||||
Functions.set("toObject", this::toObject);
|
||||
Functions.set("toValue", this::toValue);
|
||||
}
|
||||
|
||||
//<editor-fold defaultstate="collapsed" desc="Values">
|
||||
private static class NullValue implements Value {
|
||||
|
||||
@Override
|
||||
public Object raw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int asInt() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double asNumber() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int type() {
|
||||
return 482862660;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Value o) {
|
||||
if (o.raw() == null) return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassValue extends MapValue {
|
||||
|
||||
public static Value classOrNull(Class<?> clazz) {
|
||||
if (clazz == null) return NULL;
|
||||
return new ClassValue(clazz);
|
||||
}
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
public ClassValue(Class<?> clazz) {
|
||||
super(25);
|
||||
this.clazz = clazz;
|
||||
init(clazz);
|
||||
}
|
||||
|
||||
private void init(Class<?> clazz) {
|
||||
set(new StringValue("isAnnotation"), NumberValue.fromBoolean(clazz.isAnnotation()));
|
||||
set(new StringValue("isAnonymousClass"), NumberValue.fromBoolean(clazz.isAnonymousClass()));
|
||||
set(new StringValue("isArray"), NumberValue.fromBoolean(clazz.isArray()));
|
||||
set(new StringValue("isEnum"), NumberValue.fromBoolean(clazz.isEnum()));
|
||||
set(new StringValue("isInterface"), NumberValue.fromBoolean(clazz.isInterface()));
|
||||
set(new StringValue("isLocalClass"), NumberValue.fromBoolean(clazz.isLocalClass()));
|
||||
set(new StringValue("isMemberClass"), NumberValue.fromBoolean(clazz.isMemberClass()));
|
||||
set(new StringValue("isPrimitive"), NumberValue.fromBoolean(clazz.isPrimitive()));
|
||||
set(new StringValue("isSynthetic"), NumberValue.fromBoolean(clazz.isSynthetic()));
|
||||
|
||||
set(new StringValue("modifiers"), NumberValue.of(clazz.getModifiers()));
|
||||
|
||||
set(new StringValue("canonicalName"), new StringValue(clazz.getCanonicalName()));
|
||||
set(new StringValue("name"), new StringValue(clazz.getName()));
|
||||
set(new StringValue("simpleName"), new StringValue(clazz.getSimpleName()));
|
||||
set(new StringValue("typeName"), new StringValue(clazz.getTypeName()));
|
||||
set(new StringValue("genericString"), new StringValue(clazz.toGenericString()));
|
||||
|
||||
set(new StringValue("getComponentType"), new FunctionValue(v -> classOrNull(clazz.getComponentType()) ));
|
||||
set(new StringValue("getDeclaringClass"), new FunctionValue(v -> classOrNull(clazz.getDeclaringClass()) ));
|
||||
set(new StringValue("getEnclosingClass"), new FunctionValue(v -> classOrNull(clazz.getEnclosingClass()) ));
|
||||
set(new StringValue("getSuperclass"), new FunctionValue(v -> new ClassValue(clazz.getSuperclass()) ));
|
||||
|
||||
set(new StringValue("getClasses"), new FunctionValue(v -> array(clazz.getClasses()) ));
|
||||
set(new StringValue("getDeclaredClasses"), new FunctionValue(v -> array(clazz.getDeclaredClasses()) ));
|
||||
set(new StringValue("getInterfaces"), new FunctionValue(v -> array(clazz.getInterfaces()) ));
|
||||
|
||||
set(new StringValue("asSubclass"), new FunctionValue(this::asSubclass));
|
||||
set(new StringValue("isAssignableFrom"), new FunctionValue(this::isAssignableFrom));
|
||||
set(new StringValue("new"), new FunctionValue(this::newInstance));
|
||||
set(new StringValue("cast"), new FunctionValue(this::cast));
|
||||
}
|
||||
|
||||
private Value asSubclass(Value... args) {
|
||||
Arguments.check(1, args.length);
|
||||
return new ClassValue(clazz.asSubclass( ((ClassValue)args[0]).clazz ));
|
||||
}
|
||||
|
||||
private Value isAssignableFrom(Value... args) {
|
||||
Arguments.check(1, args.length);
|
||||
return NumberValue.fromBoolean(clazz.isAssignableFrom( ((ClassValue)args[0]).clazz ));
|
||||
}
|
||||
|
||||
private Value newInstance(Value... args) {
|
||||
try {
|
||||
return new ObjectValue(clazz.newInstance());
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private Value cast(Value... args) {
|
||||
Arguments.check(1, args.length);
|
||||
return objectToValue(clazz, clazz.cast(((ObjectValue)args[0]).object));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassValue " + clazz.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ObjectValue extends MapValue {
|
||||
|
||||
public static Value objectOrNull(Object object) {
|
||||
if (object == null) return NULL;
|
||||
return new ObjectValue(object);
|
||||
}
|
||||
|
||||
private final Object object;
|
||||
|
||||
public ObjectValue(Object object) {
|
||||
super(2);
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Value key) {
|
||||
return getValue(key.asString()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(Value key) {
|
||||
return getValue(key.asString());
|
||||
}
|
||||
|
||||
private Value getValue(String key) {
|
||||
// Trying to get field
|
||||
try {
|
||||
final Field field = object.getClass().getField(key);
|
||||
return objectToValue(field.getType(), field.get(object));
|
||||
} catch (NoSuchFieldException | SecurityException |
|
||||
IllegalArgumentException | IllegalAccessException ex) {
|
||||
// ignore and go to the next step
|
||||
}
|
||||
|
||||
// Trying to invoke method
|
||||
try {
|
||||
final Method[] allMethods = object.getClass().getMethods();
|
||||
final List<Method> methods = new ArrayList<>();
|
||||
for (Method method : allMethods) {
|
||||
if (method.getName().equals(key)) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
if (methods.size() == 0) {
|
||||
return FunctionValue.EMPTY;
|
||||
}
|
||||
return new FunctionValue(methodsToFunction(methods));
|
||||
} catch (SecurityException ex) {
|
||||
// ignore and go to the next step
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
private Function methodsToFunction(List<Method> methods) {
|
||||
return (args) -> {
|
||||
for (Method method : methods) {
|
||||
if (method.getParameterCount() != args.length) continue;
|
||||
if (!isMatch(args, method.getParameterTypes())) continue;
|
||||
try {
|
||||
final Object result = method.invoke(object, valuesToObjects(args));
|
||||
if (method.getReturnType() != void.class) {
|
||||
return objectToValue(result);
|
||||
}
|
||||
return NumberValue.ONE;
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
// skip
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isMatch(Value[] args, Class<?>[] types) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
final Value arg = args[i];
|
||||
final Class<?> clazz = types[i];
|
||||
|
||||
if (arg == NULL) continue;
|
||||
if (unboxed(clazz).isAssignableFrom(unboxed(valueToObject(arg).getClass()))) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ObjectValue " + asString();
|
||||
}
|
||||
|
||||
private Class<?> unboxed(Class<?> clazz) {
|
||||
if (clazz == null) return null;
|
||||
if (clazz.isPrimitive()) {
|
||||
if (int.class == clazz) return Integer.class;
|
||||
if (boolean.class == clazz) return Boolean.class;
|
||||
if (double.class == clazz) return Double.class;
|
||||
if (float.class == clazz) return Float.class;
|
||||
if (long.class == clazz) return Long.class;
|
||||
if (byte.class == clazz) return Byte.class;
|
||||
if (char.class == clazz) return Character.class;
|
||||
if (short.class == clazz) return Short.class;
|
||||
if (void.class == clazz) return Void.class;
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
private Value isNull(Value... args) {
|
||||
Arguments.checkAtLeast(1, args.length);
|
||||
for (Value arg : args) {
|
||||
if (arg.raw() == null) return NumberValue.ONE;
|
||||
}
|
||||
return NumberValue.ZERO;
|
||||
}
|
||||
|
||||
private Value newClass(Value... args) {
|
||||
Arguments.check(1, args.length);
|
||||
|
||||
final String className = args[0].asString();
|
||||
try {
|
||||
return new ClassValue(Class.forName(className));
|
||||
} catch (ClassNotFoundException ce) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private Value toObject(Value... args) {
|
||||
Arguments.check(1, args.length);
|
||||
if (args[0] == NULL) return NULL;
|
||||
return new ObjectValue(valueToObject(args[0]));
|
||||
}
|
||||
|
||||
private Value toValue(Value... args) {
|
||||
Arguments.check(1, args.length);
|
||||
if (args[0] instanceof ObjectValue) {
|
||||
return objectToValue( ((ObjectValue) args[0]).object );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//<editor-fold defaultstate="collapsed" desc="Helpers">
|
||||
private static ArrayValue array(Class<?>[] classes) {
|
||||
final ArrayValue result = new ArrayValue(classes.length);
|
||||
for (int i = 0; i < classes.length; i++) {
|
||||
result.set(i, ClassValue.classOrNull(classes[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Value objectToValue(Object o) {
|
||||
if (o == null) return NULL;
|
||||
return objectToValue(o.getClass(), o);
|
||||
}
|
||||
|
||||
private static Value objectToValue(Class<?> clazz, Object o) {
|
||||
if (o == null | o == NULL) return NULL;
|
||||
if (clazz.isPrimitive()) {
|
||||
if (int.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((int) o);
|
||||
if (boolean.class.isAssignableFrom(clazz))
|
||||
return NumberValue.fromBoolean((boolean) o);
|
||||
if (double.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((double) o);
|
||||
if (float.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((float) o);
|
||||
if (long.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((long) o);
|
||||
if (byte.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((byte) o);
|
||||
if (char.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((char) o);
|
||||
if (short.class.isAssignableFrom(clazz))
|
||||
return NumberValue.of((short) o);
|
||||
}
|
||||
if (Number.class.isAssignableFrom(clazz)) {
|
||||
return NumberValue.of((Number) o);
|
||||
}
|
||||
if (String.class.isAssignableFrom(clazz)) {
|
||||
return new StringValue((String) o);
|
||||
}
|
||||
if (CharSequence.class.isAssignableFrom(clazz)) {
|
||||
return new StringValue( ((CharSequence) o).toString() );
|
||||
}
|
||||
if (o instanceof Value) {
|
||||
return (Value) o;
|
||||
}
|
||||
if (clazz.isArray()) {
|
||||
final int length = Array.getLength(o);
|
||||
final ArrayValue result = new ArrayValue(length);
|
||||
final Class<?> componentType = clazz.getComponentType();
|
||||
int i = 0;
|
||||
if (boolean.class.isAssignableFrom(componentType)) {
|
||||
for (boolean element : (boolean[]) o) {
|
||||
result.set(i++, NumberValue.fromBoolean(element));
|
||||
}
|
||||
} else if (byte.class.isAssignableFrom(componentType)) {
|
||||
for (byte element : (byte[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else if (char.class.isAssignableFrom(componentType)) {
|
||||
for (char element : (char[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else if (double.class.isAssignableFrom(componentType)) {
|
||||
for (double element : (double[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else if (float.class.isAssignableFrom(componentType)) {
|
||||
for (float element : (float[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else if (int.class.isAssignableFrom(componentType)) {
|
||||
for (int element : (int[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else if (long.class.isAssignableFrom(componentType)) {
|
||||
for (long element : (long[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else if (short.class.isAssignableFrom(componentType)) {
|
||||
for (short element : (short[]) o) {
|
||||
result.set(i++, NumberValue.of(element));
|
||||
}
|
||||
} else {
|
||||
for (Object element : (Object[]) o) {
|
||||
result.set(i++, objectToValue(element));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
final Class<?> componentType = clazz.getComponentType();
|
||||
if (componentType != null) {
|
||||
return objectToValue(componentType, o);
|
||||
}
|
||||
return new ObjectValue(o);
|
||||
}
|
||||
|
||||
private static Object[] valuesToObjects(Value[] args) {
|
||||
Object[] result = new Object[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
result[i] = valueToObject(args[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object valueToObject(Value value) {
|
||||
if (value == NULL) return null;
|
||||
switch (value.type()) {
|
||||
case Types.NUMBER:
|
||||
return value.raw();
|
||||
case Types.STRING:
|
||||
return value.asString();
|
||||
case Types.ARRAY: {
|
||||
final ArrayValue array = (ArrayValue) value;
|
||||
final int size = array.size();
|
||||
final Object[] result = new Object[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
result[i] = valueToObject(array.get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (value instanceof ObjectValue) {
|
||||
return ((ObjectValue) value).object;
|
||||
}
|
||||
if (value instanceof ClassValue) {
|
||||
return ((ClassValue) value).clazz;
|
||||
}
|
||||
return value.raw();
|
||||
}
|
||||
//</editor-fold>
|
||||
}
|
39
test/interop/Data.java
Normal file
39
test/interop/Data.java
Normal file
@ -0,0 +1,39 @@
|
||||
package interop;
|
||||
|
||||
public final class Data {
|
||||
public final int intValue = 3228;
|
||||
public final int[] intArrayValue = {1, 2, 3};
|
||||
public final Object nullObject = null;
|
||||
public final String stringValue = "str";
|
||||
public final String[] stringArrayValue = {"abc", "test"};
|
||||
public final Object[] objectArray = new Object[] {intValue, intArrayValue, nullObject, stringValue, stringArrayValue};
|
||||
public final Object compoundObject = objectArray;
|
||||
|
||||
public void method() {
|
||||
System.out.println("method");
|
||||
}
|
||||
|
||||
public String methodWithResult() {
|
||||
return "result";
|
||||
}
|
||||
|
||||
|
||||
private int value;
|
||||
private String text;
|
||||
|
||||
public void set(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void set(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
49
test/resources/modules/java/classes.own
Normal file
49
test/resources/modules/java/classes.own
Normal file
@ -0,0 +1,49 @@
|
||||
use "std"
|
||||
use "java"
|
||||
|
||||
def testCheckNull() {
|
||||
assertTrue(isNull(null))
|
||||
assertFalse(isNull(`byte[].class`))
|
||||
assertTrue(null == null)
|
||||
}
|
||||
|
||||
def testFieldAccess() {
|
||||
data = createObject()
|
||||
assertEquals(3228, data.intValue)
|
||||
assertEquals([1, 2, 3], data.intArrayValue)
|
||||
assertTrue(isNull(data.nullObject))
|
||||
assertEquals("str", data.stringValue)
|
||||
assertEquals(["abc", "test"], data.stringArrayValue)
|
||||
assertObjectArray(data.objectArray)
|
||||
}
|
||||
|
||||
def testCast() {
|
||||
data = createObject()
|
||||
assertObjectArray( `Object[].class`.cast(data.compoundObject) )
|
||||
}
|
||||
|
||||
def testInvokeMethod() {
|
||||
data = createObject()
|
||||
data.method()
|
||||
assertEquals("result", data.methodWithResult())
|
||||
}
|
||||
|
||||
def testInvokeMethodSameName() {
|
||||
data = createObject()
|
||||
data.set(1)
|
||||
data.set("text")
|
||||
|
||||
assertEquals(1, data.getValue())
|
||||
assertEquals("text", data.getText())
|
||||
}
|
||||
|
||||
|
||||
def createObject() {
|
||||
dataClass = newClass("interop.Data")
|
||||
return dataClass.new()
|
||||
}
|
||||
|
||||
def assertObjectArray(obj) {
|
||||
assertEquals(5, length(obj))
|
||||
assertEquals([3228, [1, 2, 3], null, "str", ["abc", "test"]], obj)
|
||||
}
|
Loading…
Reference in New Issue
Block a user