Checkpoint ongoing work.
authorNot Zed <notzed@gmail.com>
Sat, 8 Apr 2023 23:36:46 +0000 (09:06 +0930)
committerNot Zed <notzed@gmail.com>
Sat, 8 Apr 2023 23:36:46 +0000 (09:06 +0930)
 - fixed comparison ops
 - improved import mechanism
 - extended primitive type handling
 - implemented while/break/continue

nbproject/configs/Test.properties [new file with mode: 0644]
src/notzed.scripta/classes/au/notzed/scripta/AST.java
src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java
src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java
src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java
src/notzed.scripta/classes/au/notzed/scripta/Generator.java
src/notzed.scripta/classes/au/notzed/scripta/Imports.java [new file with mode: 0644]
src/notzed.scripta/gen/ScriptA.g4

diff --git a/nbproject/configs/Test.properties b/nbproject/configs/Test.properties
new file mode 100644 (file)
index 0000000..e69de29
index ff91f9c..2e34f87 100644 (file)
@@ -41,7 +41,7 @@ public abstract class AST {
                this.lineNo = lineNo;
        }
 
-       enum XType {
+       public enum XType {
                BOOLEAN,
                INTEGER,
                FLOAT,
@@ -49,13 +49,14 @@ public abstract class AST {
                OBJECT
        }
 
-       enum XUnaryOp {
-               NOP, // for +
+       public enum XUnaryOp {
+               POS,
                NEG,
                NOT,
+               INV, // bitwise not ~
        }
 
-       enum XBinaryOp {
+       public enum XBinaryOp {
                // arithmetic
                ADD,
                SUB,
@@ -129,14 +130,16 @@ public abstract class AST {
                }
        }
 
-       static class SIf extends Statement {
+       public static class SIf extends Statement {
+               final Optional<String> label;
                final Expression test;
                final Optional<Statements> then;
                final Optional<Statements> rest;
 
-               public SIf(int lineNo, Expression test, Optional<Statements> then, Optional<Statements> rest) {
+               public SIf(int lineNo, Optional<String> label, Expression test, Optional<Statements> then, Optional<Statements> rest) {
                        super(lineNo);
                        this.test = test;
+                       this.label = label;
                        this.then = then;
                        this.rest = rest;
                }
@@ -154,12 +157,14 @@ public abstract class AST {
                }
        }
 
-       static class SWhile extends Statement {
+       public static class SWhile extends Statement {
+               final Optional<String> label;
                final Expression test;
                final Statements when;
 
-               public SWhile(int lineNo, Expression test, Statements when) {
+               public SWhile(int lineNo, Optional<String> label, Expression test, Statements when) {
                        super(lineNo);
+                       this.label = label;
                        this.test = test;
                        this.when = when;
                }
@@ -176,7 +181,28 @@ public abstract class AST {
                }
        }
 
-       static class SDeclare extends Statement {
+       public static class SBlock extends Statement {
+               final Optional<String> label;
+               final Statements when;
+
+               public SBlock(int lineNo, Optional<String> label, Statements when) {
+                       super(lineNo);
+                       this.label = label;
+                       this.when = when;
+               }
+
+               @Override
+               public void accept(ASTVisitor av) {
+                       av.visitBlock(this);
+               }
+
+               @Override
+               public void visitChildren(ASTVisitor av) {
+                       when.accept(av);
+               }
+       }
+
+       public static class SDeclare extends Statement {
                final DType type;
                final String name;
                final Optional<Expression> value;
@@ -199,7 +225,7 @@ public abstract class AST {
                }
        }
 
-       static class SAssign extends Statement {
+       public static class SAssign extends Statement {
                final XReference ref;
                final Expression value;
 
@@ -223,24 +249,18 @@ public abstract class AST {
                }
        }
 
-       enum SBreakType {
+       public enum SBreakType {
                CONTINUE,
                BREAK,
        }
 
-       static class SBreak extends Statement {
+       public static class SBreak extends Statement {
                SBreakType op;
                Optional<String> label;
 
-               public SBreak(int lineNo, SBreakType op) {
-                       super(lineNo);
-                       this.op = op;
-                       this.label = Optional.empty();
-               }
-
-               public SBreak(int lineNo, SBreakType op, String label) {
+               public SBreak(int lineNo, SBreakType op, Optional<String> label) {
                        super(lineNo);
-                       this.label = Optional.of(label);
+                       this.label = label;
                        this.op = op;
                }
 
@@ -250,7 +270,7 @@ public abstract class AST {
                }
        }
 
-       static class SReturn extends Statement {
+       public static class SReturn extends Statement {
                Optional<Expression> res;
 
                public SReturn(int lineNo) {
@@ -274,7 +294,7 @@ public abstract class AST {
                }
        }
 
-       static class SCall extends Statement {
+       public static class SCall extends Statement {
                XCall call;
 
                public SCall(int lineNo, XCall call) {
@@ -294,7 +314,7 @@ public abstract class AST {
        }
 
        /* **** metadata */
-       static class DType extends AST {
+       public static class DType extends AST {
                final XType type;
                final Optional<XReference> typeName;
 
@@ -316,13 +336,13 @@ public abstract class AST {
        }
 
        /* **** expressions */
-       abstract static class Expression extends AST {
+       public abstract static class Expression extends AST {
                public Expression(int lineNo) {
                        super(lineNo);
                }
        }
 
-       static class XUnary extends Expression {
+       public static class XUnary extends Expression {
                XUnaryOp op;
                Expression right;
 
@@ -343,7 +363,7 @@ public abstract class AST {
                }
        }
 
-       static class XBinary extends Expression {
+       public static class XBinary extends Expression {
                XBinaryOp op;
                Expression left, right;
 
@@ -375,7 +395,7 @@ public abstract class AST {
         * package.class.field
         * package.class.field.method
         */
-       static class XReference extends Expression {
+       public static class XReference extends Expression {
                String part[];
 
                public XReference(int lineNo, String... part) {
@@ -409,7 +429,7 @@ public abstract class AST {
                }
        }
 
-       static class XCall extends Expression {
+       public static class XCall extends Expression {
                boolean constructor;
                XReference ref;
                List<Expression> params;
@@ -441,14 +461,14 @@ public abstract class AST {
                        StringBuilder sb = new StringBuilder("[call ");
                        sb.append(ref);
                        sb.append(" ");
-                       sb.append(String.join(", ", params.stream().map(x->x.toString()).toArray(String[]::new)));
+                       sb.append(String.join(", ", params.stream().map(x -> x.toString()).toArray(String[]::new)));
                        sb.append("]");
                        return sb.toString();
                }
        }
 
        // return type?
-       static class XFunction extends Expression {
+       public static class XFunction extends Expression {
                final DType rval;
                final List<DType> types;
                final List<String> params;
@@ -473,10 +493,10 @@ public abstract class AST {
                }
        }
 
-       static class XInteger extends Expression {
-               long value;
+       public static class XInteger extends Expression {
+               int value;
 
-               public XInteger(int lineNo, long value) {
+               public XInteger(int lineNo, int value) {
                        super(lineNo);
                        this.value = value;
                }
@@ -487,10 +507,10 @@ public abstract class AST {
                }
        }
 
-       static class XReal extends Expression {
-               double value;
+       public static class XReal extends Expression {
+               float value;
 
-               public XReal(int lineNo, double value) {
+               public XReal(int lineNo, float value) {
                        super(lineNo);
                        this.value = value;
                }
@@ -501,7 +521,7 @@ public abstract class AST {
                }
        }
 
-       static class XString extends Expression {
+       public static class XString extends Expression {
                String value;
 
                public XString(int lineNo, String value) {
@@ -515,7 +535,7 @@ public abstract class AST {
                }
        }
 
-       static class XBool extends Expression {
+       public static class XBool extends Expression {
                boolean value;
 
                public XBool(int lineNo, boolean value) {
index 25679fc..2b9641d 100644 (file)
@@ -42,26 +42,36 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
                return (AST.Statement)visit(ctx);
        }
 
+       static Optional<String> label(Token label) {
+               return label != null ? Optional.of(label.getText()) : Optional.empty();
+       }
+
        @Override
        public AST.SIf visitIfStatement(IfStatementContext ctx) {
                ValueExprContext valX = ctx.valueExpr();
-               AST ast = ctx.valueExpr().accept(this);
-               if (ctx.rest == null) {
-                       if (ctx.then == null)
-                               return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.empty(), Optional.empty());
-                       else
-                               return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.of(visitStatements(ctx.then)), Optional.empty());
-               } else {
-                       if (ctx.then == null)
-                               return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.empty(), Optional.of(visitStatements(ctx.rest)));
-                       else
-                               return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.of(visitStatements(ctx.then)), Optional.of(visitStatements(ctx.rest)));
-               }
+               AST test = ctx.valueExpr().accept(this);
+
+               Optional<Statements> then = ctx.then != null ? Optional.of(visitStatements(ctx.then)) : Optional.empty();
+               Optional<Statements> rest = ctx.rest != null ? Optional.of(visitStatements(ctx.rest)) : Optional.empty();
+
+               return new AST.SIf(ctx.start.getLine(), label(ctx.label), (Expression)test, then, rest);
        }
 
        @Override
        public AST.SWhile visitWhileStatement(WhileStatementContext ctx) {
-               return new SWhile(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), visitStatements(ctx.when));
+               return new SWhile(
+                       ctx.start.getLine(),
+                       label(ctx.label),
+                       (Expression)ctx.valueExpr().accept(this),
+                       visitStatements(ctx.when));
+       }
+
+       @Override
+       public AST visitBlockStatement(BlockStatementContext ctx) {
+               return new SBlock(
+                       ctx.start.getLine(),
+                       label(ctx.label),
+                       visitStatements(ctx.when));
        }
 
        private XType type(Token tok) {
@@ -143,17 +153,20 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
                        ctx.valueExpr().stream().map(v -> (AST.Expression)v.accept(this)).toList());
        }
 
-       @Override
-       public AST visitBreakStatement(BreakStatementContext ctx) {
-               switch (ctx.start.getType()) {
+       private SBreakType breakType(Token tok) {
+               switch (tok.getType()) {
                case BREAK:
+                       return SBreakType.BREAK;
                case CONTINUE:
-               case RETURN:
-                       break;
+                       return SBreakType.CONTINUE;
                default:
-                       throw new UnsupportedOperationException();
+                       throw new UnsupportedOperationException(tok.getLine() + ": " + VOCABULARY.getDisplayName(tok.getType()));
                }
-               return super.visitBreakStatement(ctx);
+       }
+
+       @Override
+       public AST visitBreakStatement(BreakStatementContext ctx) {
+               return new SBreak(NEW, breakType(ctx.start), label(ctx.label));
        }
 
        @Override
@@ -174,11 +187,13 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
        XUnaryOp valueUnaryOp(Token op) {
                switch (op.getType()) {
                case ADD:
-                       return XUnaryOp.NOP;
+                       return XUnaryOp.POS;
                case SUB:
                        return XUnaryOp.NEG;
                case NOT:
                        return XUnaryOp.NOT;
+               case INV:
+                       return XUnaryOp.INV;
                default:
                        throw new UnsupportedOperationException(op.getLine() + ": " + VOCABULARY.getDisplayName(op.getType()));
                }
@@ -242,40 +257,38 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
                        (Expression)visit(ctx.rightValue));
        }
 
-       Expression visitLiteral(Token lit) {
-               switch (lit.getType()) {
+       @Override
+       public AST visitLiteral(LiteralContext ctx) {
+               switch (ctx.start.getType()) {
                case INTEGER: {
                        try {
-                               return new XInteger(lit.getLine(), Long.parseLong(lit.getText()));
+                               return new XInteger(ctx.start.getLine(), Integer.parseInt(ctx.start.getText()));
                        } catch (NumberFormatException x) {
-                               errors.add("Invalid INTEGER: " + lit.getText());
-                               return new XInteger(lit.getLine(), Long.MAX_VALUE);
+                               errors.add("Invalid INTEGER: " + ctx.start.getText());
+                               return new XInteger(ctx.start.getLine(), Integer.MAX_VALUE);
                        }
                }
                case FLOAT: {
                        try {
-                               return new XReal(lit.getLine(), Double.parseDouble(lit.getText()));
+                               return new XReal(ctx.start.getLine(), Float.parseFloat(ctx.start.getText()));
                        } catch (NumberFormatException x) {
-                               errors.add("Invalid REAL: " + lit.getText());
-                               return new XReal(lit.getLine(), Double.NaN);
+                               errors.add("Invalid REAL: " + ctx.start.getText());
+                               return new XReal(ctx.start.getLine(), Float.NaN);
                        }
                }
                case STRING:
-                       return new XString(lit.getLine(), lit.getText());
+                       String s = ctx.start.getText();
+                       s = s.substring(1, s.length() - 1);
+                       return new XString(ctx.start.getLine(), s.translateEscapes());
                case TRUE:
-                       return new XBool(lit.getLine(), true);
+                       return new XBool(ctx.start.getLine(), true);
                case FALSE:
-                       return new XBool(lit.getLine(), false);
+                       return new XBool(ctx.start.getLine(), false);
                default:
-                       throw new UnsupportedOperationException(lit.getLine() + ": " + VOCABULARY.getDisplayName(lit.getType()));
+                       throw new UnsupportedOperationException(ctx.start.getLine() + ": " + VOCABULARY.getDisplayName(ctx.start.getType()));
                }
        }
 
-       @Override
-       public AST visitLiteral(LiteralContext ctx) {
-               return visitLiteral(ctx.start);
-       }
-
        @Override
        public AST.XReference visitReference(ReferenceContext ctx) {
                return new XReference(ctx.start.getLine(), ctx.ID().stream().map(t -> t.getText()).toArray(String[]::new));
index d5f4c51..26f6d09 100644 (file)
@@ -31,7 +31,9 @@ public class ASTPrinter implements ASTVisitor {
 
        @Override
        public void visitIf(AST.SIf s) {
-               System.out.printf("%sif (", d);
+               System.out.print(d);
+               s.label.ifPresent(l -> System.out.print(l + ": "));
+               System.out.print("if (");
                s.test.accept(this);
                System.out.println(")");
                s.then.ifPresent(r -> {
@@ -50,7 +52,9 @@ public class ASTPrinter implements ASTVisitor {
 
        @Override
        public void visitWhile(AST.SWhile s) {
-               System.out.printf("%swhile (", d);
+               System.out.print(d);
+               s.label.ifPresent(l -> System.out.print(l + ": "));
+               System.out.print("while (");
                s.test.accept(this);
                System.out.println(")");
                down();
@@ -59,6 +63,17 @@ public class ASTPrinter implements ASTVisitor {
                System.out.printf("%swend\n", d);
        }
 
+       @Override
+       public void visitBlock(AST.SBlock s) {
+               System.out.print(d);
+               s.label.ifPresent(l -> System.out.print(l + ": "));
+               System.out.println("{");
+               down();
+               s.when.accept(this);
+               up();
+               System.out.printf("%s}\n", d);
+       }
+
        @Override
        public void visitDecl(AST.SDeclare s) {
                String type = s.type.typeName.orElse(new AST.XReference(s.lineNo, s.type.type.name())).name();
@@ -174,12 +189,12 @@ public class ASTPrinter implements ASTVisitor {
 
        @Override
        public void visit(AST.XInteger e) {
-               System.out.printf("%dL", e.value);
+               System.out.printf("%d", e.value);
        }
 
        @Override
        public void visit(AST.XReal e) {
-               System.out.printf("%fD", e.value);
+               System.out.printf("%ff", e.value);
        }
 
        @Override
index 980514a..03394cb 100644 (file)
@@ -41,6 +41,10 @@ public interface ASTVisitor {
                s.visitChildren(this);
        }
 
+       public default void visitBlock(SBlock s) {
+               s.visitChildren(this);
+       }
+
        public default void visitDecl(SDeclare s) {
                s.visitChildren(this);
        }
index 0807e8b..1ccab0e 100644 (file)
  */
 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;
@@ -30,15 +24,27 @@ 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;
 
+/*
+ * - 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;
@@ -47,20 +53,51 @@ public class Generator implements ASTVisitor {
        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;
@@ -84,24 +121,33 @@ public class Generator implements ASTVisitor {
        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",
@@ -145,42 +191,81 @@ public class Generator implements ASTVisitor {
        }
 
        @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;
 
@@ -190,52 +275,19 @@ public class Generator implements ASTVisitor {
 
                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);
        }
@@ -247,27 +299,9 @@ public class Generator implements ASTVisitor {
                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
@@ -277,32 +311,60 @@ public class Generator implements ASTVisitor {
                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();
        }
 
        /* *********************** */
@@ -313,96 +375,231 @@ public class Generator implements ASTVisitor {
 
        @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);
@@ -412,12 +609,14 @@ public class Generator implements ASTVisitor {
 
                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);
@@ -425,380 +624,93 @@ public class Generator implements ASTVisitor {
                                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
@@ -820,48 +732,40 @@ field:          for (; fname < e.part.length; fname++) {
 
        @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));
        }
 
 }
diff --git a/src/notzed.scripta/classes/au/notzed/scripta/Imports.java b/src/notzed.scripta/classes/au/notzed/scripta/Imports.java
new file mode 100644 (file)
index 0000000..bcd1b10
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * 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);
+                                       }
+                               }
+                       }
+               }
+       }
+
+}
index 3a89f56..aaaeb72 100644 (file)
@@ -19,8 +19,8 @@ statement
        : (label=ID ':')? IF '(' valueExpr ')'
                '{' then=statements? '}'
                ( ELSE '{' rest=statements? '}' )?                              # ifStatement
-//     | (label=ID ':')? SWITCH '(' valueExpr ')' '{' cases* '}'               # switchStatement
        | (label=ID ':')? WHILE '(' valueExpr ')' '{' when=statements? '}'      # whileStatement
+       | (label=ID ':')? '{' when=statements '}'                               # blockStatement
        | type ID ( '=' valueExpr ) ?                                           # declStatement
        | reference '=' valueExpr                                               # assignStatement
        | callExpr                                                              # callStatement
@@ -29,11 +29,9 @@ statement
        | RETURN valueExpr ?                                                    # returnStatement
        ;
 
-
-// TODO: chained call: valueExpr '.' call
 valueExpr
        : '(' valueExpr ')'                                                     # valueGroupExpr
-       | op=('!'|'-'|'+') rightValue=valueExpr                                 # valueUnaryExpr
+       | op=('!'|'-'|'+'|'~') rightValue=valueExpr                             # valueUnaryExpr
        | leftValue=valueExpr op=('*'|'/'|'%') rightValue=valueExpr             # valueBinaryExpr
        | leftValue=valueExpr op=('&'|'|'|'+'|'-') rightValue=valueExpr         # valueBinaryExpr
        | leftValue=valueExpr op=('&&'|'||'|'^^') rightValue=valueExpr          # valueBinaryExpr
@@ -42,7 +40,6 @@ valueExpr
        | ref=reference                                                         # valueReferenceExpr
        | call=callExpr                                                         # valueCallExpr
        | func=funcExpr                                                         # valueFunctionExpr
-       | callExpr ('.' valueExpr )+                                            # valueChainExpr
        ;
 
 callExpr       : NEW ? reference '(' (valueExpr (',' valueExpr)*)? ')';
@@ -87,6 +84,7 @@ MUL           : '*';
 DIV            : '/';
 MOD            : '%';
 NOT            : '!';
+INV            : '~';
 
 AAND           : '&&';
 OOR            : '||';