From c32bc170e7f00429e5a4df62467e12cc59afd08e Mon Sep 17 00:00:00 2001 From: Not Zed Date: Sun, 9 Apr 2023 09:06:46 +0930 Subject: [PATCH] Checkpoint ongoing work. - fixed comparison ops - improved import mechanism - extended primitive type handling - implemented while/break/continue --- nbproject/configs/Test.properties | 0 .../classes/au/notzed/scripta/AST.java | 96 +- .../classes/au/notzed/scripta/ASTBuilder.java | 89 +- .../classes/au/notzed/scripta/ASTPrinter.java | 23 +- .../classes/au/notzed/scripta/ASTVisitor.java | 4 + .../classes/au/notzed/scripta/Generator.java | 998 ++++++++---------- .../classes/au/notzed/scripta/Imports.java | 322 ++++++ src/notzed.scripta/gen/ScriptA.g4 | 8 +- 8 files changed, 908 insertions(+), 632 deletions(-) create mode 100644 nbproject/configs/Test.properties create mode 100644 src/notzed.scripta/classes/au/notzed/scripta/Imports.java diff --git a/nbproject/configs/Test.properties b/nbproject/configs/Test.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/notzed.scripta/classes/au/notzed/scripta/AST.java b/src/notzed.scripta/classes/au/notzed/scripta/AST.java index ff91f9c..2e34f87 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/AST.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/AST.java @@ -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 label; final Expression test; final Optional then; final Optional rest; - public SIf(int lineNo, Expression test, Optional then, Optional rest) { + public SIf(int lineNo, Optional label, Expression test, Optional then, Optional 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 label; final Expression test; final Statements when; - public SWhile(int lineNo, Expression test, Statements when) { + public SWhile(int lineNo, Optional 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 label; + final Statements when; + + public SBlock(int lineNo, Optional 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 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 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 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 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 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 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 types; final List 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) { diff --git a/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java b/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java index 25679fc..2b9641d 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java @@ -42,26 +42,36 @@ public class ASTBuilder extends ScriptABaseVisitor { return (AST.Statement)visit(ctx); } + static Optional 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 then = ctx.then != null ? Optional.of(visitStatements(ctx.then)) : Optional.empty(); + Optional 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 { 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 { 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 { (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)); diff --git a/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java b/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java index d5f4c51..26f6d09 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java @@ -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 diff --git a/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java b/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java index 980514a..03394cb 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java @@ -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); } diff --git a/src/notzed.scripta/classes/au/notzed/scripta/Generator.java b/src/notzed.scripta/classes/au/notzed/scripta/Generator.java index 0807e8b..1ccab0e 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/Generator.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/Generator.java @@ -16,13 +16,7 @@ */ 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 stack = new LinkedList<>(); ClassLoader classLoader = new ClassLoader() { }; - Map 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 insert) { + record Value(Type type, Consumer insert) { public Value then(Consumer 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 name, Label cont, Label exit) { + } + + LinkedList 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 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 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 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 matchParameters(List 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 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> 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, "", "", mh.type().descriptorString(), false); - }); - } - - static boolean matchParameters(Executable m, List 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 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 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 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 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 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 index 0000000..bcd1b10 --- /dev/null +++ b/src/notzed.scripta/classes/au/notzed/scripta/Imports.java @@ -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 . + */ +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 args) { + return new Generator.Value(type.getSort() == Type.METHOD ? type.getReturnType() : type, mv -> { + insert(mv, args); + }); + } + + public void insert(MethodVisitor mw, List args) { + insert.insert(this, mw, args); + } + } + + public interface InsertFunction { + void insert(Import im, MethodVisitor mv, List args); + } + // static? + // fields? methods? constructors? + static final int IMPORT_FIELDS = 0; + static final int IMPORT_METHODS = 1; + static final int IMPORT_CONSTRUCTORS = 2; + Map>> importInstance[] = IntStream.range(0, 3).mapToObj(i -> new HashMap<>()).toArray(Map[]::new); + Map>> 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. + *

+ * 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 fields, Predicate 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 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 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 arga) { + return arga.stream().map(Generator.Value::type).toArray(Type[]::new); + } + + static Predicate matchType(Type type) { + return im -> { + return im.type().equals(type); + }; + } + + public Optional 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 findImport(String name, List 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 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 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); + } + } + } + } + } + +} diff --git a/src/notzed.scripta/gen/ScriptA.g4 b/src/notzed.scripta/gen/ScriptA.g4 index 3a89f56..aaaeb72 100644 --- a/src/notzed.scripta/gen/ScriptA.g4 +++ b/src/notzed.scripta/gen/ScriptA.g4 @@ -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 : '||'; -- 2.39.2