*/
package au.notzed.scripta;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Executable;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import static org.objectweb.asm.Opcodes.*;
import org.objectweb.asm.Type;
+/*
+ * - work out resolution
+ var [.fields] .field
+ var [.fields] .method()
+ package.class .method()
+ imported.field
+ imported.method()
+ field (imported)
+ method() (imported)
+
+ - syntax
+ can it be simpler?
+ arrays at least would be nice
+ *
+ */
public class Generator implements ASTVisitor {
ClassWriter cw;
MethodVisitor mw;
LinkedList<Value> stack = new LinkedList<>();
ClassLoader classLoader = new ClassLoader() {
};
- Map<String, String> imports = new HashMap<>();
+ Imports imports = new Imports();
+
+ static final Type STRING_TYPE = Type.getType(String.class);
+ static final Type BOOLEAN_OBJECT_TYPE = Type.getType(Boolean.class);
+ static final Type INTEGER_OBJECT_TYPE = Type.getType(Integer.class);
+ static final Type FLOAT_OBJECT_TYPE = Type.getType(Float.class);
- record Variable(String name, int id, Class type) {
+ record Variable(String name, int id, Type type) {
}
- record Value(Class type, Consumer<MethodVisitor> insert) {
+ record Value(Type type, Consumer<MethodVisitor> insert) {
public Value then(Consumer<MethodVisitor> then) {
return new Value(type, insert.andThen(then));
}
+
+ static public Value constant(float v) {
+ return new Value(Type.FLOAT_TYPE, mv -> mv.visitLdcInsn(v));
+ }
+
+ static public Value constant(int v) {
+ return new Value(Type.INT_TYPE, mv -> mv.visitLdcInsn(v));
+ }
+
+ static public Value constant(boolean v) {
+ return new Value(Type.BOOLEAN_TYPE, mv -> mv.visitLdcInsn(v));
+ }
+
+ static public Value constant(String v) {
+ return new Value(STRING_TYPE, mv -> mv.visitLdcInsn(v));
+ }
+
+ static public Value constant(Object v) {
+ return new Value(Type.getType(v.getClass()), mv -> mv.visitLdcInsn(v));
+ }
}
- Variable addVariable(String name, Class<?> type) {
+ // maybe other shit like locals here too
+ record Block(Optional<String> name, Label cont, Label exit) {
+ }
+
+ LinkedList<Block> blocks = new LinkedList<>();
+
+ Variable addVariable(String name, Type type) {
Variable var;
- if (type == Double.class || type == Long.class) {
+ if (type == Type.DOUBLE_TYPE || type == Type.LONG_TYPE) {
variableID = (variableID + 1) & ~1;
variables.put(name, var = new Variable(name, variableID, type));
variableID += 2;
public Generator(String file) {
this.file = file;
- imports.put("System.out", "java.lang.System.out");
- imports.put("System.err", "java.lang.System.err");
- imports.put("print", "java.lang.System.out.print");
- imports.put("println", "java.lang.System.out.println");
- imports.put("printf", "java.lang.System.out.printf");
- if (false) {
- for (Method m: Math.class.getMethods()) {
- if (Modifier.isStatic(m.getModifiers())) {
- imports.put(m.getName(), "java.lang.Math." + m.getName());
- }
- }
+ imports.importClass(System.class, f -> f.getName().matches("out|error"), m -> false);
+ imports.importClass(PrintStream.class, f -> false, m -> m.getName().matches("^print.*"));
+ imports.importClass(Math.class, f -> true, m -> true);
+ List<Imports.Import> list = imports.importInstance[Imports.IMPORT_METHODS].getOrDefault(Type.getType(PrintStream.class), Map.of()).entrySet().stream()
+ .filter(e -> e.getKey().matches("^print.*"))
+ .flatMap(e -> e.getValue().stream())
+ .toList();
+ for (Imports.Import im: list) {
+ //System.out.println(im);
+ imports.importStatic(new Imports.Import(im.recv(), im.name(), im.type(), (m, mv, params) -> {
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", m.recv().getDescriptor());
+ // TODO: promote/cast arguments
+ //System.out.println(m.name() + " INSERT params " + params);
+ params.forEach(a -> a.insert().accept(mv));
+ mv.visitMethodInsn(INVOKEVIRTUAL, m.recv().getInternalName(), m.name(), m.type().getDescriptor(), false);
+ }));
}
}
@Override
public void visitScript(AST.ScriptA script) {
- cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
- //cw = new ClassWriter(0);
+ boolean valid = true;
+
+ if (valid)
+ cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ else
+ cw = new ClassWriter(0);
cw.visit(V1_8, ACC_PUBLIC,
"au/notzed/scripta/script",
}
@Override
- public void visitIf(AST.SIf s
- ) {
- System.out.printf("%sif (", d);
- s.test.accept(this);
- System.out.println(")");
- s.then.ifPresent(r -> {
- down();
- r.accept(this);
- up();
- });
- s.rest.ifPresent(r -> {
- System.out.printf("%selse", d);
- down();
- r.accept(this);
- up();
- });
- System.out.printf("%sfi\n", d);
+ public void visitIf(AST.SIf s) {
+
+ if (s.rest.isPresent() || s.then.isPresent()) {
+ Label loop = new Label();
+ Label then = new Label();
+ Label exit = new Label();
+
+ blocks.push(new Block(s.label, loop, exit));
+ mw.visitLabel(loop);
+ visitExpression(s.test).insert.accept(mw);
+ s.then.ifPresentOrElse(r -> {
+ if (s.rest.isPresent()) {
+ mw.visitJumpInsn(IFEQ, then);
+ } else {
+ mw.visitJumpInsn(IFEQ, exit);
+ }
+ visitStatements(r);
+ if (s.rest.isPresent()) {
+ mw.visitJumpInsn(GOTO, exit);
+ }
+ }, () -> {
+ if (s.rest.isPresent()) {
+ mw.visitJumpInsn(IFNE, exit);
+ }
+ });
+ s.rest.ifPresent(r -> {
+ mw.visitLabel(then);
+ visitStatements(r);
+ });
+ mw.visitLabel(exit);
+
+ blocks.pop();
+ }
}
@Override
- public void visitWhile(AST.SWhile s
- ) {
- System.out.printf("%swhile (", d);
- s.test.accept(this);
- System.out.println(")");
- down();
+ public void visitWhile(AST.SWhile s) {
+ Label loop = new Label();
+ Label test = new Label();
+ Label exit = new Label();
+
+ blocks.push(new Block(s.label, test, exit));
+
+ mw.visitJumpInsn(GOTO, test);
+ mw.visitLabel(loop);
s.when.accept(this);
- up();
- System.out.printf("%swend\n", d);
+ mw.visitLabel(test);
+ Value testX = visitExpression(s.test);
+
+ promoteBoolean(testX).insert.accept(mw);
+ mw.visitJumpInsn(IFNE, loop);
+ mw.visitLabel(exit);
+
+ blocks.pop();
}
- static Class typeMap[] = {boolean.class, long.class, double.class, String.class};
+ @Override
+ public void visitBlock(AST.SBlock s) {
+ Label loop = new Label();
+ Label exit = new Label();
+
+ blocks.push(new Block(s.label, loop, exit));
+
+ mw.visitLabel(loop);
+ s.when.accept(this);
+ mw.visitLabel(exit);
+
+ blocks.pop();
+ }
+
+ static final Type typeMap[] = {Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.FLOAT_TYPE, STRING_TYPE};
@Override
public void visitDecl(AST.SDeclare s) {
- System.out.printf("var %s%s\n", d, s.name);
+ System.out.printf("define var %s%s\n", d, s.name);
Variable var;
Optional<Value> val;
val = s.value.map(x -> visitExpression(x));
- switch (s.type.type) {
- case BOOLEAN:
- var = new Variable(s.name, variableID++, boolean.class);
- val = val.map(this::promoteBoolean);
- val.ifPresent(v -> {
- v.insert.accept(mw);
- mw.visitVarInsn(ISTORE, var.id);
- });
- break;
- case INTEGER:
- var = new Variable(s.name, variableID++, long.class);
- val = val.map(this::promoteInt);
- val.ifPresent(v -> {
- v.insert.accept(mw);
- mw.visitVarInsn(LSTORE, var.id);
- });
- break;
- case FLOAT:
- var = new Variable(s.name, variableID++, double.class);
- val = val.map(this::promoteFloat);
- val.ifPresent(v -> {
- v.insert.accept(mw);
- mw.visitVarInsn(DSTORE, var.id);
- });
- break;
- case STRING:
- var = new Variable(s.name, variableID++, String.class);
- val = val.map(this::promoteString);
+ if (s.type.type == AST.XType.OBJECT) {
+ var = new Variable(s.name, variableID++, Type.getObjectType(s.type.typeName.get().name()));
val.ifPresent(v -> {
v.insert.accept(mw);
mw.visitVarInsn(ASTORE, var.id);
});
- break;
- case OBJECT:
- try {
- var = new Variable(s.name, variableID++, Class.forName(s.type.typeName.get().name(), false, classLoader));
+ } else {
+ var = new Variable(s.name, variableID++, typeMap[s.type.type.ordinal()]);
+ val = val.map(v -> promote(v, s.type.type));
val.ifPresent(v -> {
v.insert.accept(mw);
- mw.visitVarInsn(ASTORE, var.id);
+ mw.visitVarInsn(v.type().getOpcode(ISTORE), var.id);
});
- } catch (ClassNotFoundException ex) {
- throw new IllegalArgumentException(ex);
- }
- break;
- default:
- throw new IllegalArgumentException();
}
variables.put(var.name, var);
}
Variable var = variables.get(s.ref.name());
Value val = visitExpression(s.value);
- // FIXME: can this go on var?
- if (var.type == boolean.class) {
- val = promoteFloat(val);
- val.insert.accept(mw);
- mw.visitVarInsn(ISTORE, var.id);
- } else if (var.type == long.class) {
- val = promoteInt(val);
- val.insert.accept(mw);
- mw.visitVarInsn(LSTORE, var.id);
- } else if (var.type == double.class) {
- val = promoteFloat(val);
- val.insert.accept(mw);
- mw.visitVarInsn(DSTORE, var.id);
- } else if (var.type == String.class) {
- val = promoteString(val);
- val.insert.accept(mw);
- mw.visitVarInsn(ASTORE, var.id);
- } else {
- val.insert.accept(mw);
- mw.visitVarInsn(ASTORE, var.id);
- }
+ val = promote(val, var.type);
+ val.insert.accept(mw);
+ mw.visitVarInsn(var.type.getOpcode(ISTORE), var.id);
}
@Override
val.insert.accept(mw);
System.out.printf("call result %s\n", val.type);
- if (val.type != void.class) {
- System.out.printf(" drop result\n");
- if (val.type == double.class || val.type == long.class)
- mw.visitInsn(POP2);
- else
- mw.visitInsn(POP);
+ switch (val.type.getSort()) {
+ case Type.VOID:
+ break;
+ case Type.DOUBLE:
+ case Type.LONG:
+ mw.visitInsn(POP2);
+ break;
+ default:
+ mw.visitInsn(POP);
+ break;
}
}
+ Block findBlock(String name) {
+ System.out.println("find block " + name);
+ for (Block b: blocks) {
+ System.out.println(" " + b);
+ }
+
+ for (Block b: blocks) {
+ if (name == null || b.name.isPresent() && b.name.get().equals(name))
+ return b;
+ }
+ return null;
+ }
+
@Override
public void visitBreak(AST.SBreak s) {
- System.out.print(d);
- System.out.print(s.op);
- s.label.ifPresent(l -> System.out.print(" " + l));
- System.out.println();
+ Block b = findBlock(s.label.get());
+
+ if (b == null) {
+ throw new IllegalArgumentException("Unable to find enclosing label: " + s.label);
+ }
+
+ switch (s.op) {
+ case BREAK:
+ mw.visitJumpInsn(GOTO, b.exit);
+ break;
+ case CONTINUE:
+ mw.visitJumpInsn(GOTO, b.cont);
+ break;
+ }
}
@Override
public void visitReturn(AST.SReturn s) {
- System.out.print(d);
- System.out.print("return");
- s.res.ifPresent(l -> {
- System.out.print(" ");
- l.accept(this);
+ Optional<Value> res = s.res.map(r -> visitExpression(r));
+
+ res.ifPresentOrElse(r -> {
+ r.insert.accept(mw);
+ mw.visitInsn(r.type.getOpcode(IRETURN));
+ }, () -> {
+ mw.visitInsn(RETURN);
});
- System.out.println();
}
/* *********************** */
@Override
public void visit(AST.XUnary e) {
- Value a = stack.removeFirst();
- System.out.print(e.op);
- System.out.print("( ");
e.right.accept(this);
- System.out.print(" )");
+
+ Value a = stack.removeFirst();
switch (e.op) {
case NEG:
- if (a.type == double.class) {
- mw.visitInsn(DNEG);
- } else if (a.type == long.class) {
- mw.visitInsn(LNEG);
- } else {
- throw new IllegalArgumentException(e.lineNo + ": expecting number");
- }
+ stack.push(a.then(mv -> mv.visitInsn(a.type.getOpcode(INEG))));
break;
case NOT:
- if (a.type == boolean.class) {
+ Value b = promoteBoolean(a);
+ stack.push(new Value(Type.BOOLEAN_TYPE, mv -> {
+ b.insert.accept(mv);
mw.visitInsn(ICONST_1);
mw.visitInsn(IXOR);
- }
+ }));
+ break;
+ case INV:
+ Value i = promoteInt(a);
+ stack.push(new Value(Type.INT_TYPE, mv -> {
+ i.insert.accept(mv);
+ mw.visitInsn(ICONST_M1);
+ mw.visitInsn(IXOR);
+ }));
+ break;
+ case POS:
break;
- case NOP:
}
}
static final int intOp[] = {
- LADD, LSUB, LMUL, LDIV, LUSHR, LSHL, LSHR, LAND, LOR, LXOR, LCMP, LCMP, LCMP, LCMP, LCMP, LCMP
+ IADD, ISUB, IMUL, IDIV, IUSHR, ISHL, ISHR, IAND, IOR, IXOR
};
- static final int floatOp[] = {
- DADD, DSUB, DMUL, DDIV, NOP, NOP, NOP, NOP, NOP, NOP, NOP, DCMPL, DCMPG, DCMPL, DCMPG, DCMPG, DCMPL
+ // CLT, CLE, CGT, CGE, CEQ, CNE,
+ static final int cmpFloat[] = {
+ FCMPL, FCMPL, FCMPG, FCMPG, FCMPL, FCMPG
};
static final int cmpOp[] = {
- IFNE, IFEQ, IFLE, IFGT, IFGE, IFLT
+ IFGE, IFGT, IFLE, IFLT, IFNE, IFEQ
+ };
+ static final int cmpJump[] = {
+ IF_ICMPLT, IF_ICMPLE, IF_ICMPGT, IF_ICMPGE, IF_ICMPEQ, IF_ICMPEQ
+ };
+
+ static final int boolOp[] = {
+ IAND, IOR, IXOR
};
+ boolean isAssignable(Class<?> target, Type type) {
+ try {
+ Class<?> klass = Class.forName(type.getClassName(), false, classLoader);
+ return target.isAssignableFrom(klass);
+ } catch (ClassNotFoundException ex) {
+ return false;
+ }
+ }
+
+ Value promote(Value val, AST.XType to) {
+ switch (to) {
+ case BOOLEAN:
+ return promoteBoolean(val);
+ case INTEGER:
+ return promoteInt(val);
+ case FLOAT:
+ return promoteFloat(val);
+ case STRING:
+ return promoteString(val);
+ default:
+ return val;
+ }
+ }
+
+ Value promote(Value val, Type to) {
+ switch (to.getSort()) {
+ case Type.BOOLEAN:
+ return promoteBoolean(val);
+ case Type.INT:
+ return promoteInt(val);
+ case Type.FLOAT:
+ return promoteFloat(val);
+ default:
+ if (to.equals(STRING_TYPE))
+ return promoteString(val);
+ }
+ throw new IllegalArgumentException("expecting " + to + ": " + val);
+ }
+
Value promoteBoolean(Value a) {
- if (a.type == boolean.class) {
+ if (a.type == Type.BOOLEAN_TYPE) {
return a;
- } else if (Boolean.class.isAssignableFrom(a.type)) {
- return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Boolean", "booleanValue", "()Z", true));
- } else if (a.type == String.class) {
- return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Z", false));
+ } else if (isAssignable(Boolean.class, a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Boolean", "booleanValue", "()Z", false));
+ } else if (a.type.equals(STRING_TYPE)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "parseBoolean", "(Ljava/lang/String;)Z", false));
} else {
- throw new IllegalArgumentException("expecting boolean or boolean string");
+ throw new IllegalArgumentException("expecting boolean or boolean string: " + a);
}
}
- Value promoteFloat(Value a) {
- if (a.type == double.class) {
+ Value promoteDouble(Value a) {
+ if (a.type == Type.DOUBLE_TYPE) {
return a;
- } else if (a.type == long.class) {
+ } else if (a.type == Type.INT_TYPE) {
+ return a.then(mv -> mv.visitInsn(I2D));
+ } else if (a.type == Type.FLOAT_TYPE) {
+ return a.then(mv -> mv.visitInsn(F2D));
+ } else if (a.type == Type.LONG_TYPE) {
return a.then(mv -> mv.visitInsn(L2D));
- } else if (Number.class.isAssignableFrom(a.type)) {
+ } else if (isAssignable(Number.class, a.type)) {
return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "doubleValue", "()D", true));
- } else if (a.type == String.class) {
- return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)D", false));
+ } else if (a.type.equals(STRING_TYPE)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", false));
} else {
throw new IllegalArgumentException("expecting number or numerical string");
}
}
- Value promoteInt(Value a) {
- if (a.type == long.class) {
+ Value promoteFloat(Value a) {
+ if (a.type == Type.FLOAT_TYPE) {
return a;
- } else if (a.type == double.class) {
+ } else if (a.type == Type.INT_TYPE) {
+ return a.then(mv -> mv.visitInsn(I2F));
+ } else if (a.type == Type.DOUBLE_TYPE) {
+ return a.then(mv -> mv.visitInsn(D2F));
+ } else if (a.type == Type.LONG_TYPE) {
+ return a.then(mv -> mv.visitInsn(L2F));
+ } else if (isAssignable(Number.class, a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "floatValue", "()F", true));
+ } else if (a.type.equals(STRING_TYPE)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", false));
+ } else {
+ throw new IllegalArgumentException("expecting number or numerical string");
+ }
+ }
+
+ Value promoteLong(Value a) {
+ if (a.type == Type.LONG_TYPE) {
+ return a;
+ } else if (a.type == Type.FLOAT_TYPE) {
+ return a.then(mv -> mv.visitInsn(F2L));
+ } else if (a.type == Type.INT_TYPE) {
+ return a.then(mv -> mv.visitInsn(I2L));
+ } else if (a.type == Type.DOUBLE_TYPE) {
return a.then(mv -> mv.visitInsn(D2L));
- } else if (Number.class.isAssignableFrom(a.type)) {
- return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "longValue", "()D", true));
- } else if (a.type == String.class) {
- return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)D", false));
+ } else if (isAssignable(Number.class, a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "longValue", "()J", true));
+ } else if (a.type.equals(STRING_TYPE)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "parseLong", "(Ljava/lang/String;)J", false));
+ } else {
+ throw new IllegalArgumentException("expecting number or numerical string");
+ }
+ }
+
+ Value promoteInt(Value a) {
+ if (a.type == Type.INT_TYPE) {
+ return a;
+ } else if (a.type == Type.FLOAT_TYPE) {
+ return a.then(mv -> mv.visitInsn(F2I));
+ } else if (a.type == Type.LONG_TYPE) {
+ return a.then(mv -> mv.visitInsn(L2I));
+ } else if (a.type == Type.DOUBLE_TYPE) {
+ return a.then(mv -> mv.visitInsn(D2I));
+ } else if (isAssignable(Number.class, a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "intValue", "()I", true));
+ } else if (a.type.equals(STRING_TYPE)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", false));
} else {
throw new IllegalArgumentException("expecting number or numerical string");
}
}
Value promoteString(Value a) {
- if (a.type == String.class) {
+ if (a.type.equals(STRING_TYPE)) {
return a;
- } else if (a.type == double.class) {
+ } else if (a.type == Type.INT_TYPE) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "toString", "(I)Ljava/lang/String;", false));
+ } else if (a.type == Type.FLOAT_TYPE) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "toString", "(F)Ljava/lang/String;", false));
+ } else if (a.type == Type.DOUBLE_TYPE) {
return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "toString", "(D)Ljava/lang/String;", false));
- } else if (a.type == long.class) {
+ } else if (a.type == Type.LONG_TYPE) {
return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "toString", "(J)Ljava/lang/String;", false));
- } else if (a.type == boolean.class) {
+ } else if (a.type == Type.BOOLEAN_TYPE) {
return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "toString", "(Z)Ljava/lang/String;", false));
} else {
return a.then(mv -> mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "(Ljava/lang/Object;)Ljava/lang/String;", false));
}
}
+ static Value compareFloat(Value c, Value d, int fcmp, int cmp) {
+ return new Value(Type.BOOLEAN_TYPE, mv -> {
+ Label t = new Label();
+ Label f = new Label();
+
+ c.insert.accept(mv);
+ d.insert.accept(mv);
+ mv.visitInsn(fcmp);
+ mv.visitJumpInsn(cmp, t);
+
+ mv.visitInsn(ICONST_1);
+ mv.visitJumpInsn(GOTO, f);
+ mv.visitLabel(t);
+ mv.visitInsn(ICONST_0);
+ mv.visitLabel(f);
+ });
+ }
+
+ static Value compareInt(Value c, Value d, int lcmp, int cmp) {
+ return new Value(Type.BOOLEAN_TYPE, mv -> {
+ Label t = new Label();
+ Label f = new Label();
+
+ c.insert.accept(mv);
+ d.insert.accept(mv);
+ if (lcmp != 0)
+ mv.visitInsn(lcmp);
+ mv.visitJumpInsn(cmp, t);
+
+ mv.visitInsn(ICONST_0);
+ mv.visitJumpInsn(GOTO, f);
+ mv.visitLabel(t);
+ mv.visitInsn(ICONST_1);
+ mv.visitLabel(f);
+ });
+ }
+
@Override
public void visit(AST.XBinary e) {
e.right.accept(this);
Value a = stack.removeFirst();
Value b = stack.removeFirst();
- Value v;
+ Value c, d;
+ Type type;
+ int opcode;
switch (e.op) {
case ADD:
- if (a.type == String.class || b.type == String.class) {
- stack.addFirst(new Value(double.class, mv -> {
+ if (a.type == STRING_TYPE || b.type == STRING_TYPE) {
+ stack.addFirst(new Value(STRING_TYPE, mv -> {
promoteString(a).insert.accept(mv);
promoteString(b).insert.accept(mv);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false);
return;
}
case SUB, MUL, DIV:
- if (a.type == double.class || b.type == double.class) {
- stack.addFirst(new Value(double.class, mv -> {
- promoteFloat(a).insert.accept(mv);
- promoteFloat(b).insert.accept(mv);
- mv.visitInsn(floatOp[e.op.ordinal()]);
- }));
- } else if (a.type == long.class || b.type == long.class) {
- stack.addFirst(new Value(long.class, mv -> {
- promoteInt(a).insert.accept(mv);
- promoteInt(b).insert.accept(mv);
- mv.visitInsn(intOp[e.op.ordinal()]);
- }));
+ if (a.type == Type.DOUBLE_TYPE || b.type == Type.DOUBLE_TYPE) {
+ type = Type.DOUBLE_TYPE;
+ c = promoteDouble(a);
+ d = promoteDouble(b);
+ } else if (a.type == Type.FLOAT_TYPE || b.type == Type.FLOAT_TYPE) {
+ type = Type.FLOAT_TYPE;
+ c = promoteFloat(a);
+ d = promoteFloat(b);
+ } else if (a.type == Type.LONG_TYPE || b.type == Type.LONG_TYPE) {
+ type = Type.LONG_TYPE;
+ c = promoteLong(a);
+ d = promoteLong(b);
+ } else if (a.type == Type.INT_TYPE || b.type == Type.INT_TYPE) {
+ type = Type.INT_TYPE;
+ c = promoteInt(a);
+ d = promoteInt(b);
} else {
- throw new IllegalArgumentException("expecting numbers");
+ type = Type.DOUBLE_TYPE;
+ c = promoteDouble(a);
+ d = promoteDouble(b);
}
- break;
- case LSR, LSL, ASR, AND, ORR, XOR:
- stack.addFirst(new Value(long.class, mv -> {
- promoteInt(a).insert.accept(mv);
- promoteInt(b).insert.accept(mv);
- mv.visitInsn(intOp[e.op.ordinal()]);
+ opcode = type.getOpcode(intOp[e.op.ordinal()]);
+ System.out.printf("%s: %d -> %d\n", e.op, intOp[e.op.ordinal()], opcode);
+ stack.addFirst(new Value(type, mv -> {
+ c.insert.accept(mv);
+ d.insert.accept(mv);
+ mv.visitInsn(opcode);
}));
break;
- case CLT, CLE, CGT, CGE, CEQ, CNE:
- Label t = new Label();
- Label f = new Label();
- Value c,
- d;
- int cmp;
-
- if (a.type == double.class || b.type == double.class) {
- c = promoteFloat(a);
- d = promoteFloat(b);
- cmp = floatOp[e.op.ordinal()];
- } else if (a.type == long.class || b.type == long.class) {
+ case LSR, LSL, ASR, AND, ORR, XOR:
+ if (a.type == Type.LONG_TYPE) {
+ type = a.type;
+ c = promoteLong(a);
+ d = promoteInt(b);
+ } else if (a.type == Type.INT_TYPE) {
+ type = a.type;
c = promoteInt(a);
d = promoteInt(b);
- cmp = intOp[e.op.ordinal()];
} else {
- throw new IllegalArgumentException("expecting numbers");
+ type = Type.LONG_TYPE;
+ c = promoteLong(a);
+ d = promoteInt(b);
}
-
- stack.addFirst(new Value(boolean.class, mv -> {
+ opcode = type.getOpcode(intOp[e.op.ordinal()]);
+ stack.addFirst(new Value(type, mv -> {
c.insert.accept(mv);
d.insert.accept(mv);
- mw.visitInsn(cmp);
- mw.visitJumpInsn(cmpOp[e.op.ordinal() - AST.XBinaryOp.CLT.ordinal()], t);
-
- mw.visitInsn(ICONST_1);
- mw.visitJumpInsn(GOTO, f);
- mw.visitLabel(t);
- mw.visitInsn(ICONST_0);
- mw.visitLabel(f);
+ mv.visitInsn(opcode);
}));
break;
- case AAND, OOR, XXOR:
- if (a.type == boolean.class && b.type == boolean.class) {
- stack.addFirst(new Value(boolean.class, mv -> {
- }));
+ case CLT, CLE, CGT, CGE, CEQ, CNE:
+ int index = e.op.ordinal() - AST.XBinaryOp.CLT.ordinal();
+
+ if (a.type == Type.DOUBLE_TYPE || b.type == Type.DOUBLE_TYPE) {
+ stack.addFirst(compareFloat(promoteDouble(a), promoteDouble(b), cmpFloat[index] + 2, cmpOp[index]));
+ } else if (a.type == Type.FLOAT_TYPE || b.type == Type.FLOAT_TYPE) {
+ stack.addFirst(compareFloat(promoteFloat(a), promoteFloat(b), cmpFloat[index], cmpOp[index]));
+ } else if (a.type == Type.LONG_TYPE || b.type == Type.LONG_TYPE) {
+ stack.addFirst(compareInt(promoteLong(a), promoteLong(b), LCMP, cmpJump[index]));
+ } else if (a.type == Type.INT_TYPE || b.type == Type.INT_TYPE) {
+ stack.addFirst(compareInt(promoteInt(a), promoteInt(b), 0, cmpJump[index]));
} else {
- throw new IllegalArgumentException("expecting booleans");
+ stack.addFirst(compareInt(promoteLong(a), promoteLong(b), LCMP, cmpJump[index]));
}
break;
- }
- }
-
- Predicate<Executable> matchParameters(List<Value> args) {
- return (m) -> {
- Class<?>[] params = m.getParameterTypes();
- boolean ok = params.length == args.size();
- for (int i = 0; ok && i < params.length; i++) {
- Class<?> p = params[i];
- Class<?> a = args.get(i).type;
- ok = p.isAssignableFrom(a)
- || p.isPrimitive() && Number.class.isAssignableFrom(a)
- || a.isPrimitive() && Number.class.isAssignableFrom(p);
- }
- return ok;
- };
- }
-
- // TODO: needs more lookup bits for subclasses etc
- Value resolveConstructor(AST.XReference ref, List<Value> args) throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
- if (imports.containsKey(ref.name())) {
- ref = new AST.XReference(ref.lineNo, imports.get(ref.name()).split("\\."));
- }
-
- Class<?> type = Class.forName(ref.name(), false, classLoader);
- Optional<Constructor<?>> match = Stream.of(type.getConstructors())
- .filter(matchParameters(args))
- .findFirst();
-
- if (match.isEmpty())
- throw new NoSuchMethodException(ref.lineNo + ": " + ref.name());
-
- MethodHandle mh = MethodHandles.lookup().unreflectConstructor(match.get());
-
- String tname = ref.name().replace('.', '/');
- return new Value(type, mv -> {
- mw.visitTypeInsn(NEW, tname);
- mw.visitInsn(DUP);
- // TODO: coerce parameters
- args.forEach(a -> a.insert.accept(mv));
- mv.visitMethodInsn(INVOKESPECIAL, "", "<init>", mh.type().descriptorString(), false);
- });
- }
-
- static boolean matchParameters(Executable m, List<Value> args) {
- Class<?>[] params = m.getParameterTypes();
- boolean ok = params.length == args.size();
- for (int i = 0; ok && i < params.length; i++) {
- Class<?> p = params[i];
- Class<?> a = args.get(i).type();
- ok = p.isAssignableFrom(a)
- || p.isPrimitive() && Number.class.isAssignableFrom(a)
- || a.isPrimitive() && Number.class.isAssignableFrom(p);
- }
- return ok;
- }
-
- Value resolveMethod(AST.XReference ref, List<Value> args) throws NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
- Class<?> type;
+ case AAND, OOR, XXOR:
+ c = promoteBoolean(a);
+ d = promoteBoolean(b);
+ opcode = boolOp[e.op.ordinal() - AST.XBinaryOp.AAND.ordinal()];
+ stack.addFirst(new Value(Type.BOOLEAN_TYPE, mv -> {
+ c.insert.accept(mv);
+ d.insert.accept(mv);
- // FIXME: better import mechanism
- if (imports.containsKey(ref.name())) {
- ref = new AST.XReference(ref.lineNo, imports.get(ref.name()).split("\\."));
- }
- int cname = ref.part.length - 1;
- Field pfield = null;
- Class<?> pclass = null;
- System.out.printf("resolve: '%s'\n", ref.name());
- for (; cname > 0; cname--) {
- try {
- System.out.printf(" ? %s\n", ref.part(0, cname));
- type = Class.forName(ref.part(0, cname), false, classLoader);
- System.out.printf("found class: %s for %s\n", type.getName(), ref.name());
-
- int fname = ref.part.length - cname;
- Class<?> ftype = type;
-field: for (; fname < ref.part.length; fname++) {
- System.out.printf(" . %-20s on %s\n", ref.part[fname], ftype);
- if (ref.part[fname].equals("class")) {
- pclass = ftype;
- ftype = ftype.getClass();
- continue;
- }
- if (fname == ref.part.length - 1) {
- for (var m: ftype.getMethods()) {
- System.out.printf(" ! %s name=%s params=%s\n", m.getName(), m.getName().equals(ref.part[fname]), matchParameters(m, args));
- if (m.getName().equals(ref.part[fname]) && matchParameters(m, args)) {
- Class<?> vtype = ftype;
- if (Modifier.isStatic(m.getModifiers())) {
- System.out.printf(" m %s %s:%s\n", m,
- Type.getInternalName(ftype),
- Type.getMethodDescriptor(m));
- return new Value(m.getReturnType(), mv -> {
- // TODO: promote/cast arguments
- args.forEach(a -> a.insert.accept(mv));
- mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
- });
- } else if (pfield != null) {
- Field vfield = pfield;
- System.out.printf(" m %s %s:%s on %s\n", m,
- Type.getInternalName(ftype),
- Type.getMethodDescriptor(m),
- Type.getInternalName(pfield.getDeclaringClass()));
- return new Value(m.getReturnType(), mv -> {
- mv.visitFieldInsn(GETSTATIC, Type.getInternalName(vfield.getDeclaringClass()), vfield.getName(), Type.getDescriptor(vfield.getType()));
- // TODO: promote/cast arguments
- args.forEach(a -> a.insert.accept(mv));
- mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
- });
- } else if (pclass != null) {
- Class<?> ptype = pclass;
- System.out.printf(" m %s %s:%s\n", m,
- Type.getInternalName(ftype),
- Type.getMethodDescriptor(m));
- return new Value(m.getReturnType(), mv -> {
- mv.visitLdcInsn(Type.getType(ptype));
- mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
- // TODO: promote/cast arguments
- args.forEach(a -> a.insert.accept(mv));
- });
- }
- }
- }
- throw new NoSuchMethodException(ref.part[fname]);
- }
- for (var f: ftype.getFields()) {
- if (Modifier.isStatic(f.getModifiers()) && f.getName().equals(ref.part[fname])) {
- System.out.printf(" f %s\n", f);
- pfield = f;
- ftype = f.getType();
- continue field;
- }
- }
- for (var c: ftype.getClasses()) {
- if (Modifier.isStatic(c.getModifiers()) && c.getSimpleName().equals(ref.part[fname])) {
- System.out.printf(" c %s %s\n", c, c.descriptorString());
- ftype = c;
- continue field;
- }
- }
- break;
- }
- throw new NoSuchFieldException(ref.part[fname]);
- } catch (ClassNotFoundException x) {
- }
- }
+ mv.visitInsn(opcode);
- // check aliases I suppose
- throw new ClassNotFoundException(ref.name());
- }
-
- Value resolveField(AST.XReference e) throws ClassNotFoundException, NoSuchFieldException {
- int cname = e.part.length - 1;
- Class<?> type;
- System.out.printf("resolve: '%s'\n", e.name());
- for (; cname > 0; cname--) {
- try {
- System.out.printf(" ? %s\n", e.part(0, cname));
- type = Class.forName(e.part(0, cname), false, classLoader);
- System.out.printf("found class: %s for %s\n", type.getName(), e.name());
-
- int fname = e.part.length - cname;
- Class<?> ftype = type;
- Field pfield = null;
-field: for (; fname < e.part.length; fname++) {
- System.out.printf(" . %s\n", e.part[fname]);
- if (e.part[fname].equals("class")) {
- if (fname == e.part.length - 1) {
- Class<?> vtype = ftype;
- return new Value(vtype, mv -> {
- mv.visitLdcInsn(Type.getType(vtype));
- });
- }
- ftype = ftype.getClass();
- continue;
- }
- for (var f: ftype.getFields()) {
- if (f.getName().equals(e.part[fname])) {
- System.out.printf(" f %s\n", f);
- if (fname == e.part.length - 1) {
- if (Modifier.isStatic(f.getModifiers())) {
- if (f.getType().isPrimitive()) {
- return new Value(f.getType(), mv -> {
- Class<?> vtype = f.getType();
- try {
- if (vtype == double.class) {
- mv.visitLdcInsn(f.getDouble(f.getType()));
- } else if (vtype == long.class) {
- mv.visitLdcInsn(f.getLong(f.getType()));
- } else if (vtype == int.class) {
- mv.visitLdcInsn(f.getInt(f.getType()));
- } else if (vtype == boolean.class) {
- mv.visitLdcInsn(f.getBoolean(f.getType()));
- }
- } catch (IllegalArgumentException ex) {
- ex.printStackTrace();
- } catch (IllegalAccessException ex) {
- ex.printStackTrace();
- }
- });
- } else {
- return new Value(f.getType(), mv -> {
- mv.visitFieldInsn(GETFIELD, Type.getInternalName(f.getType()), f.getName(), Type.getDescriptor(f.getType()));
- });
- }
- } else if (pfield != null) {
- Field vfield = pfield;
- System.out.printf(" m %s %s:%s on %s\n", f,
- Type.getInternalName(ftype),
- Type.getDescriptor(f.getType()),
- Type.getInternalName(pfield.getDeclaringClass()));
- return new Value(f.getType(), mv -> {
- mv.visitFieldInsn(GETSTATIC, Type.getInternalName(vfield.getDeclaringClass()), vfield.getName(), Type.getDescriptor(vfield.getType()));
- mv.visitFieldInsn(GETFIELD, Type.getInternalName(f.getDeclaringClass()), f.getName(), Type.getDescriptor(f.getType()));
- });
- }
- }
- pfield = f;
- ftype = f.getType();
- continue field;
- }
- }
- for (var c: ftype.getClasses()) {
- if (Modifier.isStatic(c.getModifiers()) && c.getSimpleName().equals(e.part[fname])) {
- System.out.printf(" c %s %s\n", c, c.descriptorString());
- ftype = c;
- continue field;
- }
- }
- break;
- }
- throw new NoSuchFieldException(e.part[fname]);
- } catch (ClassNotFoundException x) {
- }
+ }));
+ break;
}
-
- // check aliases I suppose
- throw new ClassNotFoundException(e.name());
}
@Override
public void visit(AST.XCall e) {
System.out.println(e);
- Variable var = variables.get(e.ref.base());
-
- try {
- List<Value> args = e.params.stream().map(this::visitExpression).toList();
- Value m = resolveMethod(e.ref, args);
-
- stack.push(m);
- } catch (ClassNotFoundException ex) {
- ex.printStackTrace();
- } catch (NoSuchFieldException ex) {
- ex.printStackTrace();
- } catch (NoSuchMethodException ex) {
- ex.printStackTrace();
- }
-
- if (false)
- if (var != null && e.ref.part.length == 2) {
- String name = e.ref.part[1];
- try {
- Class<?> type;
- // virtual call
- if (var.type == double.class) {
- mw.visitVarInsn(DLOAD, var.id);
- mw.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
- type = Double.class;
- } else if (var.type == long.class) {
- mw.visitVarInsn(LLOAD, var.id);
- mw.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(D)Ljava/lang/Long;", false);
- type = Long.class;
- } else if (var.type == boolean.class) {
- mw.visitVarInsn(ILOAD, var.id);
- mw.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(D)Ljava/lang/Boolean;", false);
- type = Boolean.class;
- } else {
- mw.visitVarInsn(ALOAD, var.id);
- type = var.type;
- }
- List<Value> params = e.params.stream()
- .map(this::visitExpression)
- .toList();
- Class[] args = params.stream().map(p -> p.type).toArray(Class[]::new);
- Method method = type.getMethod(name, args);
- MethodHandles.Lookup lookup = MethodHandles.lookup();
- MethodHandle mh = lookup.unreflect(method);
- mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", name, mh.type().descriptorString(), false);
- } catch (NoSuchMethodException | SecurityException ex) {
- throw new RuntimeException(ex);
- } catch (IllegalAccessException ex) {
- ex.printStackTrace();
- }
- } else {
- // static call
- // package.class.field.func()
- // package.class.func()
- }
-
- if (false) {
- e.ref.accept(this);
- System.out.print("(");
- Iterator<AST.Expression> it = e.params.iterator();
- if (it.hasNext())
- it.next().accept(this);
- while (it.hasNext()) {
- System.out.print(", ");
- it.next().accept(this);
- }
- System.out.print(")");
- }
+ // TODO: how to resolve parts of expression
+ // TODO: how to promote?
+ List<Value> args = e.params.stream().map(this::visitExpression).toList();
+ stack.push(imports.findImport(e.ref.name(), args).orElseThrow());
}
@Override
@Override
public void visit(AST.XReference e) {
- try {
- stack.push(resolveField(e));
- } catch (ClassNotFoundException ex) {
- ex.printStackTrace();
- } catch (NoSuchFieldException ex) {
- ex.printStackTrace();
+ Variable var = variables.get(e.name());
+
+ if (var != null) {
+ stack.push(new Value(var.type, mv -> {
+ mv.visitVarInsn(var.type.getOpcode(ILOAD), var.id);
+ }));
+ return;
}
- /*
- System.out.println("ref: " + e.name());
- resolve(e);
- stack.addFirst(new Value(Object.class, mw -> {
- }));
- */
- // these needs to lookup what the reference is, and ...
+
+ stack.push(imports.findStaticField(e.name()).orElseThrow());
}
@Override
public void visit(AST.XBool e) {
System.out.printf("%s", e.value);
- stack.addFirst(new Value(boolean.class, mw
- -> mw.visitLdcInsn(e.value ? 1 : 0)));
+ stack.push(Value.constant(e.value));
}
@Override
public void visit(AST.XInteger e) {
System.out.printf("%dL\n", e.value);
- stack.addFirst(new Value(long.class, mw
- -> mw.visitLdcInsn(e.value)));
+ stack.push(Value.constant(e.value));
}
@Override
public void visit(AST.XReal e) {
System.out.printf("%fD", e.value);
- stack.addFirst(new Value(double.class, mw
- -> mw.visitLdcInsn(e.value)));
+ stack.push(Value.constant(e.value));
}
@Override
public void visit(AST.XString e) {
System.out.printf("`%s`", e.value);
- stack.addFirst(new Value(String.class, mw
- -> mw.visitLdcInsn(e.value)));
+ stack.push(Value.constant(e.value));
}
}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.scripta;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.objectweb.asm.MethodVisitor;
+import static org.objectweb.asm.Opcodes.*;
+import org.objectweb.asm.Type;
+
+public class Imports {
+ /*
+ Need to be able to find fields and methods based on:
+ - source variable [or static]
+ - name
+ - rval [or type for field]
+ - arguments [for methods]
+
+ Some order of precedence needs to be enforced. e.g. specificity rules.
+ */
+ record Import(Type recv, String name, Type type, InsertFunction insert) {
+ // insert routine also needs arguments
+
+ public Generator.Value value() {
+ return new Generator.Value(type.getSort() == Type.METHOD ? type.getReturnType() : type, mv -> {
+ insert(mv, List.of());
+ });
+ }
+
+ public Generator.Value value(List<Generator.Value> args) {
+ return new Generator.Value(type.getSort() == Type.METHOD ? type.getReturnType() : type, mv -> {
+ insert(mv, args);
+ });
+ }
+
+ public void insert(MethodVisitor mw, List<Generator.Value> args) {
+ insert.insert(this, mw, args);
+ }
+ }
+
+ public interface InsertFunction {
+ void insert(Import im, MethodVisitor mv, List<Generator.Value> args);
+ }
+ // static?
+ // fields? methods? constructors?
+ static final int IMPORT_FIELDS = 0;
+ static final int IMPORT_METHODS = 1;
+ static final int IMPORT_CONSTRUCTORS = 2;
+ Map<Type, Map<String, List<Import>>> importInstance[] = IntStream.range(0, 3).mapToObj(i -> new HashMap<>()).toArray(Map[]::new);
+ Map<Type, Map<String, List<Import>>> importStatic[] = IntStream.range(0, 3).mapToObj(i -> new LinkedHashMap<>()).toArray(Map[]::new);
+
+ public void importInstance(Import im) {
+ //System.out.println("add new instance " + im);
+ var map = im.type.getSort() == Type.METHOD ? importInstance[IMPORT_METHODS] : importInstance[IMPORT_FIELDS];
+ map.computeIfAbsent(im.recv, k -> new HashMap<>())
+ .computeIfAbsent(im.name, k -> new ArrayList<>(1))
+ .add(im);
+ }
+
+ public void importStatic(Import im) {
+ //System.out.println("add new static " + im);
+ var map = im.type.getSort() == Type.METHOD ? importStatic[IMPORT_METHODS] : importStatic[IMPORT_FIELDS];
+ map.computeIfAbsent(im.recv, k -> new HashMap<>())
+ .computeIfAbsent(im.name, k -> new ArrayList<>(1))
+ .add(im);
+ }
+
+ /**
+ * Import all fields and methods of a class.
+ * <p>
+ * All types are expanded and indexed for this class but the recv is set
+ * to the declaring class.
+ *
+ * @param itype
+ */
+ public void importClass(Class<?> itype) {
+ importClass(itype, a -> true, b -> true);
+ }
+
+ // TODO: primitive constants are fully resolved at compile time in java, without the GETSTATIC
+ public void importClass(Class<?> itype, Predicate<Field> fields, Predicate<Method> methods) {
+ Type irecv = Type.getType(itype);
+ for (Field f: itype.getFields()) {
+ if (fields.test(f)) {
+ Type recv = Type.getType(f.getDeclaringClass());
+ Type type = Type.getType(f.getType());
+ String name = f.getName();
+
+ if (Modifier.isStatic(f.getModifiers())) {
+ if (type.getSort() == Type.OBJECT) {
+ importStatic(new Import(recv, name, type, (im, mv, args) -> {
+ mv.visitFieldInsn(GETSTATIC, irecv.getInternalName(), im.name(), im.type().getDescriptor());
+ }));
+ } else {
+ try {
+ Object value = f.get(null);
+ importStatic(new Import(recv, name, type, (im, mv, args) -> {
+ mv.visitLdcInsn(value);
+ }));
+ } catch (IllegalArgumentException | IllegalAccessException ex) {
+ System.out.println("field: " + f + ": " + ex.getMessage());
+ }
+ }
+ } else {
+ importInstance(new Import(recv, name, type, (im, mv, args) -> {
+ mv.visitFieldInsn(GETFIELD, irecv.getInternalName(), im.name(), im.type().getDescriptor());
+ }));
+ }
+ }
+ }
+ for (Method m: itype.getMethods()) {
+ if (methods.test(m)) {
+ Type recv = Type.getType(m.getDeclaringClass());
+ Type type = Type.getType(m);
+ String name = m.getName();
+
+ if (Modifier.isStatic(m.getModifiers())) {
+ importStatic(new Import(recv, name, type, (im, mv, args) -> {
+ // receive must already be present
+ // TODO: interface
+ // TODO: promote args
+ for (var a: args) {
+ a.insert().accept(mv);
+ }
+ mv.visitMethodInsn(INVOKESTATIC, im.recv.getInternalName(), im.name, im.type.getDescriptor(), false);
+ }));
+ } else {
+ importInstance(new Import(recv, name, type, (im, mv, args) -> {
+ for (var a: args) {
+ a.insert().accept(mv);
+ }
+ mv.visitMethodInsn(INVOKEVIRTUAL, im.recv.getInternalName(), im.name, im.type.getDescriptor(), false);
+ }));
+ }
+ }
+ }
+ }
+
+ // TODO: match promotable types as well
+ // MethodType has some stuff but it's private
+ static Predicate<Import> matchArguments(Type type) {
+ Type[] arga = type.getArgumentTypes();
+ return im -> {
+ Type[] argb = im.type.getArgumentTypes();
+
+ System.out.printf("match %s %s\n", type, im.type);
+ if (arga.length == argb.length) {
+ for (int i = 0; i < arga.length; i++) {
+ if (!im.type().equals(type))
+ return false;
+ }
+ return true;
+ }
+ return false;
+ };
+ }
+
+ // void boolean char byte short int float long double
+ static boolean matchArgument(Type a, Type b) {
+ return a.equals(b);
+ // || (a.getSort() >= Type.CHAR && a.getSort() <= Type.DOUBLE
+ // && b.getSort() >= Type.CHAR && b.getSort() <= Type.DOUBLE);
+
+ }
+
+ static Predicate<Import> matchArguments(Type arga[]) {
+ return im -> {
+ Type[] argb = im.type.getArgumentTypes();
+
+ System.out.printf("match arguments:\n %s\n %s\n",
+ String.join(", ", Stream.of(arga).map(Object::toString).toArray(String[]::new)),
+ String.join(", ", Stream.of(argb).map(Object::toString).toArray(String[]::new)));
+
+ if (arga.length == argb.length) {
+ for (int i = 0; i < arga.length; i++) {
+ if (!matchArgument(arga[i], argb[i])) {
+ System.out.println(" -> false");
+ return false;
+ }
+ }
+ System.out.println(" -> true");
+ return true;
+ }
+ System.out.println(" -> false");
+ return false;
+ };
+ }
+
+ static Type[] toArgumentTypes(List<Generator.Value> arga) {
+ return arga.stream().map(Generator.Value::type).toArray(Type[]::new);
+ }
+
+ static Predicate<Import> matchType(Type type) {
+ return im -> {
+ return im.type().equals(type);
+ };
+ }
+
+ public Optional<Generator.Value> findStaticField(String name) {
+ return importStatic[IMPORT_FIELDS].values().stream()
+ .flatMap(m -> m.getOrDefault(name, List.of()).stream())
+ .findFirst()
+ .map(m -> m.value());
+ }
+
+ // Find a method.
+ public Optional<Generator.Value> findImport(String name, List<Generator.Value> args) {
+ Type[] arga = toArgumentTypes(args);
+ System.out.printf("find method: %s", name);
+ args.stream().forEach(a -> System.out.printf(" %s", a.type()));
+ System.out.println();
+ for (var map: importStatic[IMPORT_METHODS].entrySet()) {
+ System.out.println(map.getKey());
+ for (var im: map.getValue().getOrDefault(name, List.of())) {
+ System.out.printf(" ? %s\n", im);
+ }
+ }
+
+ return importStatic[IMPORT_METHODS].values().stream()
+ .flatMap(m -> m.getOrDefault(name, List.of()).stream())
+ .peek(m -> System.out.println("? " + m))
+ .filter(matchArguments(arga))
+ .findFirst()
+ .map(m -> m.value(args));
+ }
+
+ /**
+ *
+ * @param name
+ * @param type
+ * @return
+ * @deprecated not finished
+ */
+ @Deprecated
+ public Optional<Import> findImport(String name, Type type) {
+ if (type.getSort() == Type.METHOD) {
+ System.out.printf("find method: %s\n", name);
+ System.out.println(importStatic[IMPORT_METHODS].toString());
+ for (var map: importStatic[IMPORT_METHODS].values()) {
+ System.out.println(map.getClass());
+ System.out.println(map);
+ for (var im: map.get(name)) {
+ System.out.printf(" ? %s\n", im);
+ }
+ }
+ System.out.println("----");
+ return importStatic[IMPORT_METHODS].values().stream()
+ .peek(x -> System.out.print(" > "))
+ .peek(System.out::println)
+ .flatMap(m -> m.get(name).stream())
+ .peek(System.out::println)
+ .filter(matchArguments(type))
+ .findFirst();
+ } else {
+ System.out.printf("find field: %s\n", name);
+ importStatic[IMPORT_FIELDS].values().stream()
+ .flatMap(m -> m.get(name).stream())
+ .filter(matchType(type));
+ }
+
+ return null;
+ }
+
+ @Deprecated
+ public Optional<Import> findImport(Type recv, String name, Type type) {
+ if (type.getSort() == Type.METHOD) {
+ return importInstance[IMPORT_METHODS].values().stream()
+ .flatMap(m -> m.get(name).stream())
+ .filter(matchArguments(type))
+ .findFirst();
+ } else {
+ }
+
+ return null;
+ }
+
+ void dump() {
+ for (var rmap: importInstance) {
+ for (var rme: rmap.entrySet()) {
+ for (var vme: rme.getValue().entrySet()) {
+ for (var im: vme.getValue()) {
+ System.out.printf(" %s\n", im);
+ }
+ }
+ }
+ }
+ for (var rmap: importStatic) {
+ for (var rme: rmap.entrySet()) {
+ for (var vme: rme.getValue().entrySet()) {
+ for (var im: vme.getValue()) {
+ System.out.printf(" static %s\n", im);
+ }
+ }
+ }
+ }
+ }
+
+}