* 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;
-public class Generator {
+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.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+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;
+
+public class Generator implements ASTVisitor {
+ ClassWriter cw;
+ MethodVisitor mw;
+ int variableID = 1;
+ Map<String, Variable> variables = new HashMap<>();
+ LinkedList<Value> stack = new LinkedList<>();
+ ClassLoader classLoader = new ClassLoader() {
+ };
+ Map<String, String> imports = new HashMap<>();
+
+ record Variable(String name, int id, Class type) {
+ }
+
+ record Value(Class type, Consumer<MethodVisitor> insert) {
+ public Value then(Consumer<MethodVisitor> then) {
+ return new Value(type, insert.andThen(then));
+ }
+ }
+
+ Variable addVariable(String name, Class<?> type) {
+ Variable var;
+ if (type == Double.class || type == Long.class) {
+ variableID = (variableID + 1) & ~1;
+ variables.put(name, var = new Variable(name, variableID, type));
+ variableID += 2;
+ } else {
+ variables.put(name, var = new Variable(name, variableID++, type));
+ }
+ return var;
+ }
+
+ private final String file;
+
+ // TBDH, for printing
+ String d = "";
+
+ void up() {
+ }
+
+ void down() {
+ }
+
+ 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());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void visitScript(AST.ScriptA script) {
+ cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ //cw = new ClassWriter(0);
+
+ cw.visit(V1_8, ACC_PUBLIC,
+ "au/notzed/scripta/script",
+ null,
+ "java/lang/Object",
+ new String[]{"au/notzed/scripta/Script"});
+
+ // TODO: work out debuginfo (jsr45)
+ cw.visitSource(file, null);
+
+ // Standard constructor (is it needed?)
+ if (true) {
+ mw = cw.visitMethod(ACC_PUBLIC,
+ "<init>", "()V",
+ null, null);
+
+ mw.visitCode();
+ mw.visitVarInsn(ALOAD, 0);
+ mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mw.visitInsn(RETURN);
+ mw.visitMaxs(1, 1);
+ mw.visitEnd();
+ }
+
+ mw = cw.visitMethod(ACC_PUBLIC,
+ "eval", "()V",
+ null, null);
+
+ Label start = new Label();
+ Label end = new Label();
+
+ mw.visitLabel(start);
+
+ script.statements.accept(this);
+
+ mw.visitLabel(end);
+ mw.visitInsn(RETURN);
+ mw.visitMaxs(20, variableID);
+ mw.visitEnd();
+ cw.visitEnd();
+ }
+
+ @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);
+ }
+
+ @Override
+ public void visitWhile(AST.SWhile s
+ ) {
+ System.out.printf("%swhile (", d);
+ s.test.accept(this);
+ System.out.println(")");
+ down();
+ s.when.accept(this);
+ up();
+ System.out.printf("%swend\n", d);
+ }
+
+ static Class typeMap[] = {boolean.class, long.class, double.class, String.class};
+
+ @Override
+ public void visitDecl(AST.SDeclare s) {
+ System.out.printf("var %s%s\n", d, s.name);
+ Variable var;
+ Optional<Value> val;
+
+ if (variables.containsKey(s.name)) {
+ throw new java.lang.IllegalArgumentException(s.lineNo + ": Variable redefined: " + s.name);
+ }
+
+ 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);
+ 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));
+ val.ifPresent(v -> {
+ v.insert.accept(mw);
+ mw.visitVarInsn(ASTORE, var.id);
+ });
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ variables.put(var.name, var);
+ }
+
+ @Override
+ public void visitAssign(AST.SAssign s) {
+ System.out.printf("%s%s = ", d, s.ref.name());
+
+ 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);
+ }
+ }
+
+ @Override
+ public void visitCall(AST.SCall s) {
+ Value val = visitExpression(s.call);
+
+ 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);
+ }
+ }
+
+ @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();
+ }
+
+ @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);
+ });
+ System.out.println();
+ }
+
+ /* *********************** */
+ Value visitExpression(AST e) {
+ e.accept(this);
+ return stack.removeFirst();
+ }
+
+ @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(" )");
+
+ 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");
+ }
+ break;
+ case NOT:
+ if (a.type == boolean.class) {
+ mw.visitInsn(ICONST_1);
+ mw.visitInsn(IXOR);
+ }
+ break;
+ case NOP:
+ }
+ }
+
+ static final int intOp[] = {
+ LADD, LSUB, LMUL, LDIV, LUSHR, LSHL, LSHR, LAND, LOR, LXOR, LCMP, LCMP, LCMP, LCMP, LCMP, LCMP
+ };
+ static final int floatOp[] = {
+ DADD, DSUB, DMUL, DDIV, NOP, NOP, NOP, NOP, NOP, NOP, NOP, DCMPL, DCMPG, DCMPL, DCMPG, DCMPG, DCMPL
+ };
+ static final int cmpOp[] = {
+ IFNE, IFEQ, IFLE, IFGT, IFGE, IFLT
+ };
+
+ Value promoteBoolean(Value a) {
+ if (a.type == boolean.class) {
+ 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 {
+ throw new IllegalArgumentException("expecting boolean or boolean string");
+ }
+ }
+
+ Value promoteFloat(Value a) {
+ if (a.type == double.class) {
+ return a;
+ } else if (a.type == long.class) {
+ return a.then(mv -> mv.visitInsn(L2D));
+ } else if (Number.class.isAssignableFrom(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 {
+ throw new IllegalArgumentException("expecting number or numerical string");
+ }
+ }
+
+ Value promoteInt(Value a) {
+ if (a.type == long.class) {
+ return a;
+ } else if (a.type == double.class) {
+ 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 {
+ throw new IllegalArgumentException("expecting number or numerical string");
+ }
+ }
+
+ Value promoteString(Value a) {
+ if (a.type == String.class) {
+ return a;
+ } else if (a.type == double.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "toString", "(D)Ljava/lang/String;", false));
+ } else if (a.type == long.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "toString", "(J)Ljava/lang/String;", false));
+ } else if (a.type == boolean.class) {
+ 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));
+ }
+ }
+
+ @Override
+ public void visit(AST.XBinary e) {
+ e.right.accept(this);
+ e.left.accept(this);
+
+ stack.forEach(System.out::println);
+
+ Value a = stack.removeFirst();
+ Value b = stack.removeFirst();
+ Value v;
+
+ switch (e.op) {
+ case ADD:
+ if (a.type == String.class || b.type == String.class) {
+ stack.addFirst(new Value(double.class, 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()]);
+ }));
+ } else {
+ throw new IllegalArgumentException("expecting numbers");
+ }
+ 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()]);
+ }));
+ 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) {
+ c = promoteInt(a);
+ d = promoteInt(b);
+ cmp = intOp[e.op.ordinal()];
+ } else {
+ throw new IllegalArgumentException("expecting numbers");
+ }
+
+ stack.addFirst(new Value(boolean.class, 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);
+ }));
+ break;
+ case AAND, OOR, XXOR:
+ if (a.type == boolean.class && b.type == boolean.class) {
+ stack.addFirst(new Value(boolean.class, mv -> {
+ }));
+ } else {
+ throw new IllegalArgumentException("expecting booleans");
+ }
+ 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;
+
+ // 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) {
+ }
+ }
+
+ // 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) {
+ }
+ }
+
+ // 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(")");
+ }
+ }
+
+ @Override
+ public void visit(AST.XFunction e) {
+ System.out.print("function (");
+ Iterator<String> it = e.params.iterator();
+ if (it.hasNext())
+ System.out.print(it.next());
+ while (it.hasNext()) {
+ System.out.print(", ");
+ System.out.print(it.next());
+ }
+ System.out.print(") = {\n");
+ down();
+ visitStatements(e.statements);
+ up();
+ System.out.printf("%s}\n", d);
+ }
+
+ @Override
+ public void visit(AST.XReference e) {
+ try {
+ stack.push(resolveField(e));
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ } catch (NoSuchFieldException ex) {
+ ex.printStackTrace();
+ }
+ /*
+ System.out.println("ref: " + e.name());
+ resolve(e);
+ stack.addFirst(new Value(Object.class, mw -> {
+ }));
+ */
+ // these needs to lookup what the reference is, and ...
+ }
+
+ @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)));
+ }
+
+ @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)));
+ }
+
+ @Override
+ public void visit(AST.XReal e) {
+ System.out.printf("%fD", e.value);
+ stack.addFirst(new Value(double.class, mw
+ -> mw.visitLdcInsn(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)));
+ }
}