Partial .class compiler implementation.
authorNot Zed <notzed@gmail.com>
Mon, 27 Mar 2023 02:15:12 +0000 (12:45 +1030)
committerNot Zed <notzed@gmail.com>
Mon, 27 Mar 2023 02:15:12 +0000 (12:45 +1030)
.gitignore
nbproject/project.properties
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/Compiler.java
src/notzed.scripta/classes/au/notzed/scripta/Generator.java
src/notzed.scripta/classes/au/notzed/scripta/Script.java [new file with mode: 0644]
src/notzed.scripta/classes/module-info.java
src/notzed.scripta/gen/ScriptA.g4

index 2025f8b..eae6f0d 100644 (file)
@@ -4,3 +4,5 @@
 /.lib-sources/
 bin
 config.make
+/build/
+/dist/
index 31e3525..4cd700c 100644 (file)
@@ -51,8 +51,8 @@ javac.modulepath=\
 javac.processormodulepath=
 javac.processorpath=\
     ${javac.classpath}
-javac.source=19
-javac.target=19
+javac.source=20
+javac.target=20
 javac.test.classpath=\
     ${javac.classpath}
 javac.test.modulepath=\
@@ -69,6 +69,7 @@ javadoc.nonavbar=false
 javadoc.notree=false
 javadoc.private=false
 javadoc.reference.antlr4-runtime-4.12.0.jar=.lib-javadoc/antlr4-runtime-4.12.0-javadoc.jar
+javadoc.reference.asm-9.4.jar=.lib-javadoc/asm-9.4-javadoc.jar
 javadoc.splitindex=true
 javadoc.use=true
 javadoc.version=false
@@ -83,10 +84,6 @@ main.class=au.notzed.scripta.Compiler
 platform.active=default_platform
 project.license=gpl3-notzed
 run.classpath=
-# Space-separated list of JVM arguments used when running the project.
-# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
-# To set system properties for unit tests define test-sys-prop.name=value:
-run.jvmargs=
 run.modulepath=\
     ${javac.modulepath}:\
     ${build.modules.dir}
@@ -97,6 +94,7 @@ run.test.modulepath=\
     ${build.test.modules.dir}
 source.encoding=UTF-8
 source.reference.antlr4-runtime-4.12.0.jar=.lib-sources/antlr4-runtime-4.12.0-sources.jar
+source.reference.asm-9.4.jar=.lib-sources/asm-9.4-sources.jar
 src.dir=src
 src.dir.path=classes
 src.gen.dir=bin/gen
index 7756507..ff91f9c 100644 (file)
  */
 package au.notzed.scripta;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 
+// TODO: interface + records instead of this
 public abstract class AST {
 
        public final int lineNo;
@@ -39,10 +41,12 @@ public abstract class AST {
                this.lineNo = lineNo;
        }
 
-       enum ReferenceScope {
-               LOCAL,
-               GLOBAL,
-               PLAYER
+       enum XType {
+               BOOLEAN,
+               INTEGER,
+               FLOAT,
+               STRING,
+               OBJECT
        }
 
        enum XUnaryOp {
@@ -79,16 +83,21 @@ public abstract class AST {
        }
 
        public static class ScriptA extends AST {
-               List<Statement> statements;
+               Statements statements;
 
-               public ScriptA(List<Statement> statements) {
+               public ScriptA(Statements statements) {
                        super(0);
                        this.statements = statements;
                }
 
                @Override
                public void accept(ASTVisitor av) {
-                       accept(statements, av);
+                       av.visitScript(this);
+               }
+
+               @Override
+               public void visitChildren(ASTVisitor av) {
+                       statements.accept(av);
                }
        }
 
@@ -102,7 +111,7 @@ public abstract class AST {
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitStatements(this);
                }
 
                @Override
@@ -122,32 +131,25 @@ public abstract class AST {
 
        static class SIf extends Statement {
                final Expression test;
-               final Statements then;
+               final Optional<Statements> then;
                final Optional<Statements> rest;
 
-               public SIf(int lineNo, Expression test, Statements then) {
-                       super(lineNo);
-                       this.test = test;
-                       this.then = then;
-                       this.rest = Optional.empty();
-               }
-
-               public SIf(int lineNo, Expression test, Statements then, Statements rest) {
+               public SIf(int lineNo, Expression test, Optional<Statements> then, Optional<Statements> rest) {
                        super(lineNo);
                        this.test = test;
                        this.then = then;
-                       this.rest = Optional.of(rest);
+                       this.rest = rest;
                }
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitIf(this);
                }
 
                @Override
                public void visitChildren(ASTVisitor av) {
                        test.accept(av);
-                       then.accept(av);
+                       then.ifPresent(r -> r.accept(av));
                        rest.ifPresent(r -> r.accept(av));
                }
        }
@@ -164,7 +166,7 @@ public abstract class AST {
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitWhile(this);
                }
 
                @Override
@@ -175,21 +177,25 @@ public abstract class AST {
        }
 
        static class SDeclare extends Statement {
-               final XReference ref;
+               final DType type;
+               final String name;
+               final Optional<Expression> value;
 
-               public SDeclare(int lineNo, AST.XReference ref) {
+               public SDeclare(int lineNo, DType type, String id, Optional<Expression> value) {
                        super(lineNo);
-                       this.ref = ref;
+                       this.type = type;
+                       this.name = id;
+                       this.value = value;
                }
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitDecl(this);
                }
 
                @Override
                public void visitChildren(ASTVisitor av) {
-                       ref.accept(av);
+                       value.ifPresent(x -> x.accept(av));
                }
        }
 
@@ -207,7 +213,7 @@ public abstract class AST {
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitAssign(this);
                }
 
                @Override
@@ -240,7 +246,7 @@ public abstract class AST {
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitBreak(this);
                }
        }
 
@@ -259,7 +265,7 @@ public abstract class AST {
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitReturn(this);
                }
 
                @Override
@@ -278,7 +284,7 @@ public abstract class AST {
 
                @Override
                public void accept(ASTVisitor av) {
-                       av.visit(this);
+                       av.visitCall(this);
                }
 
                @Override
@@ -287,6 +293,28 @@ public abstract class AST {
                }
        }
 
+       /* **** metadata */
+       static class DType extends AST {
+               final XType type;
+               final Optional<XReference> typeName;
+
+               public DType(int lineNo, XType type, Optional<XReference> typeName) {
+                       super(lineNo);
+                       this.type = type;
+                       this.typeName = typeName;
+               }
+
+               @Override
+               public void accept(ASTVisitor av) {
+                       av.visit(this);
+               }
+
+               @Override
+               public String toString() {
+                       return typeName.orElse(new XReference(lineNo, type.name())).name();
+               }
+       }
+
        /* **** expressions */
        abstract static class Expression extends AST {
                public Expression(int lineNo) {
@@ -338,12 +366,41 @@ public abstract class AST {
                }
        }
 
+       /**
+        * Represents a generic multi-'.' reference name.
+        * var
+        * var.field
+        * package
+        * package.class
+        * package.class.field
+        * package.class.field.method
+        */
        static class XReference extends Expression {
-               String name;
+               String part[];
 
-               public XReference(int lineNo, String name) {
+               public XReference(int lineNo, String... part) {
                        super(lineNo);
-                       this.name = name;
+                       this.part = part;
+               }
+
+               public String base() {
+                       return part[0];
+               }
+
+               public String name() {
+                       return String.join(".", part);
+               }
+
+               public String prefix(int stripPrefix) {
+                       return String.join(".", Arrays.copyOfRange(part, 0, part.length - stripPrefix));
+               }
+
+               public String suffix(int stripSuffix) {
+                       return String.join(".", Arrays.copyOf(part, stripSuffix));
+               }
+
+               public String part(int stripPrefix, int stripSuffix) {
+                       return String.join(".", Arrays.copyOfRange(part, stripPrefix, part.length - stripSuffix));
                }
 
                @Override
@@ -353,15 +410,17 @@ public abstract class AST {
        }
 
        static class XCall extends Expression {
+               boolean constructor;
                XReference ref;
                List<Expression> params;
 
-               public XCall(int lineNo, XReference ref, List<Expression> params) {
+               public XCall(int lineNo, boolean constructor, XReference ref, List<Expression> params) {
                        super(lineNo);
+                       this.constructor = constructor;
                        this.ref = ref;
                        this.params = params;
 
-                       System.out.printf("new call: %s ", ref.name);
+                       System.out.printf("call: %s%s ", constructor ? "new " : "", ref.name());
                        params.forEach(p -> System.out.printf(" %s", p));
                        System.out.println();
                }
@@ -381,7 +440,8 @@ public abstract class AST {
                public String toString() {
                        StringBuilder sb = new StringBuilder("[call ");
                        sb.append(ref);
-                       params.forEach(x -> sb.append(x.toString()).append(", "));
+                       sb.append(" ");
+                       sb.append(String.join(", ", params.stream().map(x->x.toString()).toArray(String[]::new)));
                        sb.append("]");
                        return sb.toString();
                }
@@ -389,12 +449,16 @@ public abstract class AST {
 
        // return type?
        static class XFunction extends Expression {
+               final DType rval;
+               final List<DType> types;
                final List<String> params;
                final Statements statements;
 
-               public XFunction(int lineNo, List<String> params, Statements statements) {
+               public XFunction(int lineNo, DType rval, List<DType> types, List<String> params, Statements statements) {
                        super(lineNo);
                        this.params = params;
+                       this.rval = rval;
+                       this.types = types;
                        this.statements = statements;
                }
 
index 742f119..25679fc 100644 (file)
@@ -20,6 +20,7 @@ import au.notzed.scripta.AST.*;
 import static au.notzed.scripta.ScriptAParser.*;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 import org.antlr.v4.runtime.Token;
 
@@ -29,47 +30,85 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
 
        @Override
        public AST.ScriptA visitScript(ScriptContext ctx) {
-               return new ScriptA(visitStatements(ctx.statements()).statements);
+               return new ScriptA(visitStatements(ctx.statements()));
        }
 
        @Override
        public AST.Statements visitStatements(StatementsContext ctx) {
-               System.out.printf("statements %s\n", ctx);
                return new AST.Statements(ctx.start.getLine(), ctx.statement().stream().map(this::visitStatement).toList());
        }
 
        public AST.Statement visitStatement(StatementContext ctx) {
-               System.out.println("visit statement " + ctx.getText());
                return (AST.Statement)visit(ctx);
        }
 
        @Override
        public AST.SIf visitIfStatement(IfStatementContext ctx) {
-               System.out.println("visit if statement " + ctx.getText());
                ValueExprContext valX = ctx.valueExpr();
                AST ast = ctx.valueExpr().accept(this);
                if (ctx.rest == null) {
-                       return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), visitStatements(ctx.then));
+                       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 {
-                       return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), visitStatements(ctx.then), visitStatements(ctx.rest));
+                       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)));
                }
        }
 
        @Override
        public AST.SWhile visitWhileStatement(WhileStatementContext ctx) {
-               System.out.println("visit while statement " + ctx.getText());
                return new SWhile(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), visitStatements(ctx.when));
        }
 
+       private XType type(Token tok) {
+               switch (tok.getType()) {
+               case INT:
+                       return XType.INTEGER;
+               case FLT:
+                       return XType.FLOAT;
+               case STR:
+                       return XType.STRING;
+               case BLN:
+                       return XType.BOOLEAN;
+               case ID:
+                       return XType.OBJECT;
+               default:
+                       throw new UnsupportedOperationException(tok.getLine() + ": " + VOCABULARY.getDisplayName(tok.getType()));
+               }
+       }
+
+       @Override
+       public AST.DType visitType(TypeContext ctx) {
+               XType type = type(ctx.start);
+               return new DType(ctx.start.getLine(), type,
+                       type == XType.OBJECT ? Optional.of(visitReference(ctx.reference())) : Optional.empty());
+       }
+
        @Override
        public AST visitDeclStatement(DeclStatementContext ctx) {
-               System.out.println("visit decl statement " + ctx.getText());
-               return new SDeclare(ctx.start.getLine(), visitReference(ctx.reference()));
+               System.out.printf("type\n");
+               Compiler.dump("", ctx.type());
+               if (ctx.valueExpr() == null) {
+                       return new SDeclare(
+                               ctx.start.getLine(),
+                               visitType(ctx.type()),
+                               ctx.ID().getText(),
+                               Optional.empty());
+               } else {
+                       return new SDeclare(
+                               ctx.start.getLine(),
+                               visitType(ctx.type()),
+                               ctx.ID().getText(),
+                               Optional.of(visitValueExpr(ctx.valueExpr())));
+               }
        }
 
        @Override
        public AST.SAssign visitAssignStatement(AssignStatementContext ctx) {
-               System.out.println("visit assign statement " + ctx.getText());
                return new AST.SAssign(ctx.start.getLine(), visitReference(ctx.reference()), visitValueExpr(ctx.valueExpr()));
        }
 
@@ -79,15 +118,15 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
 
        @Override
        public AST visitCallStatement(CallStatementContext ctx) {
-               System.out.println("visit call statement " + ctx.getText());
                return new SCall(ctx.start.getLine(), visitCallExpr(ctx.callExpr()));
        }
 
        @Override
        public AST.XFunction visitFuncExpr(FuncExprContext ctx) {
-               System.out.println("visit func expr " + ctx.getText());
                return new XFunction(
                        ctx.start.getLine(),
+                       visitType(ctx.rval),
+                       ctx.type().stream().map(a -> visitType(a)).toList(),
                        ctx.ID().stream().map(a -> a.getText()).toList(),
                        ctx.statements() != null
                        ? visitStatements(ctx.statements())
@@ -99,6 +138,7 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
        public AST.XCall visitCallExpr(CallExprContext ctx) {
                return new XCall(
                        ctx.start.getLine(),
+                       ctx.NEW() != null,
                        visitReference(ctx.reference()),
                        ctx.valueExpr().stream().map(v -> (AST.Expression)v.accept(this)).toList());
        }
@@ -128,7 +168,6 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
        /* ***************************************************************** */
        @Override
        public AST.Expression visitValueGroupExpr(ValueGroupExprContext ctx) {
-               System.out.printf("visit group\n");
                return visitValueExpr(ctx.valueExpr());
        }
 
@@ -228,7 +267,7 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
                case FALSE:
                        return new XBool(lit.getLine(), false);
                default:
-                       throw new UnsupportedOperationException(VOCABULARY.getDisplayName(lit.getType()));
+                       throw new UnsupportedOperationException(lit.getLine() + ": " + VOCABULARY.getDisplayName(lit.getType()));
                }
        }
 
@@ -239,6 +278,6 @@ public class ASTBuilder extends ScriptABaseVisitor<AST> {
 
        @Override
        public AST.XReference visitReference(ReferenceContext ctx) {
-               return new XReference(ctx.start.getLine(), String.join(".", ctx.ID().stream().map(t -> t.getText()).toList()));
+               return new XReference(ctx.start.getLine(), ctx.ID().stream().map(t -> t.getText()).toArray(String[]::new));
        }
 }
index e15e3a2..d5f4c51 100644 (file)
@@ -30,25 +30,17 @@ public class ASTPrinter implements ASTVisitor {
        }
 
        @Override
-       public void visit(AST.ScriptA a) {
-               ASTVisitor.super.visit(a);
-       }
-
-       @Override
-       public void visit(AST.Statements s) {
-               s.statements.forEach(x -> x.accept(this));
-       }
-
-       @Override
-       public void visit(AST.SIf s) {
+       public void visitIf(AST.SIf s) {
                System.out.printf("%sif (", d);
                s.test.accept(this);
                System.out.println(")");
-               down();
-               s.then.accept(this);
-               up();
+               s.then.ifPresent(r -> {
+                       down();
+                       r.accept(this);
+                       up();
+               });
                s.rest.ifPresent(r -> {
-                       System.out.printf("%selse", d);
+                       System.out.printf("%selse\n", d);
                        down();
                        r.accept(this);
                        up();
@@ -57,7 +49,7 @@ public class ASTPrinter implements ASTVisitor {
        }
 
        @Override
-       public void visit(AST.SWhile s) {
+       public void visitWhile(AST.SWhile s) {
                System.out.printf("%swhile (", d);
                s.test.accept(this);
                System.out.println(")");
@@ -68,26 +60,34 @@ public class ASTPrinter implements ASTVisitor {
        }
 
        @Override
-       public void visit(AST.SDeclare s) {
-               System.out.printf("var %s%s\n", d, s.ref.name);
+       public void visitDecl(AST.SDeclare s) {
+               String type = s.type.typeName.orElse(new AST.XReference(s.lineNo, s.type.type.name())).name();
+               s.value.ifPresentOrElse(
+                       v -> {
+                               System.out.printf("%s%s %s = ", d, type, s.name);
+                               v.accept(this);
+                               System.out.println();
+                       },
+                       ()
+                       -> System.out.printf("%s%s %s\n", d, type, s.name));
        }
 
        @Override
-       public void visit(AST.SAssign s) {
-               System.out.printf("%s%s = ", d, s.ref.name);
+       public void visitAssign(AST.SAssign s) {
+               System.out.printf("%s%s = ", d, s.ref.name());
                s.value.accept(this);
                System.out.println();
        }
 
        @Override
-       public void visit(AST.SCall s) {
+       public void visitCall(AST.SCall s) {
                System.out.printf("%s", d);
                s.call.accept(this);
                System.out.println();
        }
 
        @Override
-       public void visit(AST.SBreak s) {
+       public void visitBreak(AST.SBreak s) {
                System.out.print(d);
                System.out.print(s.op);
                s.label.ifPresent(l -> System.out.print(" " + l));
@@ -95,7 +95,7 @@ public class ASTPrinter implements ASTVisitor {
        }
 
        @Override
-       public void visit(AST.SReturn s) {
+       public void visitReturn(AST.SReturn s) {
                System.out.print(d);
                System.out.print("return");
                s.res.ifPresent(l -> {
@@ -142,23 +142,29 @@ public class ASTPrinter implements ASTVisitor {
        @Override
        public void visit(AST.XFunction e) {
                System.out.print("function (");
-               Iterator<String> it = e.params.iterator();
-               if (it.hasNext())
-                       System.out.print(it.next());
-               while (it.hasNext()) {
+               Iterator<String> ip = e.params.iterator();
+               Iterator<AST.DType> it = e.types.iterator();
+               if (ip.hasNext()) {
+                       System.out.print(it.next().toString());
+                       System.out.print(" ");
+                       System.out.print(ip.next());
+               }
+               while (ip.hasNext()) {
                        System.out.print(", ");
-                       System.out.print(it.next());
+                       System.out.print(it.next().toString());
+                       System.out.print(" ");
+                       System.out.print(ip.next());
                }
                System.out.print(") = {\n");
                down();
-               visit(e.statements);
+               visitStatements(e.statements);
                up();
                System.out.printf("%s}\n", d);
        }
 
        @Override
        public void visit(AST.XReference e) {
-               System.out.print(e.name);
+               System.out.print(e.name());
        }
 
        @Override
index cd1548e..980514a 100644 (file)
 package au.notzed.scripta;
 
 import au.notzed.scripta.AST.*;
-import java.util.List;
 
 public interface ASTVisitor {
 
-       public default void visit(ScriptA a) {
-               for (Statement s: a.statements)
-                       s.accept(this);
-               //a.statements.forEach(s -> s.accept(this));
+       public default void visitScript(ScriptA s) {
+               s.visitChildren(this);
        }
 
-       public default void visit(Statements s) {
+       public default void visitStatements(Statements s) {
                s.visitChildren(this);
        }
 
        // statements
-       public default void visit(Statement s) {
+       public default void visitStatement(Statement s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SIf s) {
+       public default void visitIf(SIf s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SWhile s) {
+       public default void visitWhile(SWhile s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SDeclare s) {
+       public default void visitDecl(SDeclare s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SAssign s) {
+       public default void visitAssign(SAssign s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SBreak s) {
+       public default void visitBreak(SBreak s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SCall s) {
+       public default void visitCall(SCall s) {
                s.visitChildren(this);
        }
 
-       public default void visit(SReturn s) {
+       public default void visitReturn(SReturn s) {
                s.visitChildren(this);
        }
 
+       public default void visit(DType d) {
+       }
+
        // expressions
        public default void visit(Expression e) {
                e.visitChildren(this);
index 539a0c3..77a8bfe 100644 (file)
 package au.notzed.scripta;
 
 import static au.notzed.scripta.ScriptAParser.VOCABULARY;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.stream.Stream;
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.CharStreams;
@@ -40,21 +46,32 @@ public class Compiler {
                }
        }
 
-       public static void main(String[] args) {
+       public static void main(String[] args) throws IOException {
                String src = """
-            c = (2 + a)
-                   a = 2
-            b = 3
-            c = (2 + a)
+            int c = (2 + a)
+                   int a = 2
+            float b = 3
+            int c = (2 + a)
             print(a + b, c, "hello", (3 + "foo"))
-            bob =   function (a, b, c) {
+            int bob = function int (int a, int b, int c) {
                return a + 2;
-                       }
+            }
             if (a == 2) {
                return;
-                     }
+            }
+            if (b == 2) {
+            } else {
+               print("only else")
+            }
+            if (b == 2) {
+              print("only then")
+            } else {
+            }
+            java.lang.String x
             while (a < 1) { a = a + 0.1 }
             """;
+
+               src = Files.readString(Path.of("script.sa"));
                CharStream inputStream = CharStreams.fromString(src);
                var lexer = new ScriptALexer(inputStream);
                var tokenStream = new CommonTokenStream(lexer);
@@ -76,18 +93,46 @@ public class Compiler {
 
                AST.ScriptA script = new ASTBuilder().visitScript(cst);
 
-               System.out.printf("script(%d):\n", script.statements.size());
-               script.statements.forEach(s -> System.out.printf(" %s\n", s));
+               new ASTPrinter().visitScript(script);
+               Generator gen = new Generator("script.sa");
+               gen.visitScript(script);
+
+               try {
+                       byte[] bytecode = gen.cw.toByteArray();
+                       Files.write(Path.of("script.class"), bytecode,
+                               StandardOpenOption.WRITE,
+                               StandardOpenOption.CREATE,
+                               StandardOpenOption.TRUNCATE_EXISTING);
 
-               new ASTPrinter().visit(script);
+                       MethodHandles.Lookup klass = MethodHandles.lookup().defineHiddenClass(bytecode, true);
+                       Object x = klass.lookupClass().getDeclaredConstructor().newInstance();
+                       System.out.println(x);
+                       Script s = (Script)x;
+
+                       s.eval();
+               } catch (IOException ex) {
+                       ex.printStackTrace();
+               } catch (IllegalAccessException ex) {
+                       ex.printStackTrace();
+               } catch (NoSuchMethodException ex) {
+                       ex.printStackTrace();
+               } catch (SecurityException ex) {
+                       ex.printStackTrace();
+               } catch (InstantiationException ex) {
+                       ex.printStackTrace();
+               } catch (IllegalArgumentException ex) {
+                       ex.printStackTrace();
+               } catch (InvocationTargetException ex) {
+                       ex.printStackTrace();
+               }
 
                if (false) {
                        System.out.println("visitor");
                        new ASTVisitor() {
                                @Override
-                               public void visit(AST.SCall s) {
+                               public void visitCall(AST.SCall s) {
                                        System.out.printf(" call\n");
-                                       ASTVisitor.super.visit(s);
+                                       ASTVisitor.super.visitCall(s);
                                }
 
                                @Override
@@ -98,10 +143,10 @@ public class Compiler {
 
                                @Override
                                public void visit(AST.XReference e) {
-                                       System.out.printf(" ref %s\n", e.name);
+                                       System.out.printf(" ref %s\n", e.name());
                                        ASTVisitor.super.visit(e);
                                }
-                       }.visit(script);
+                       }.visitScript(script);
                }
        }
 
index f3e1832..0807e8b 100644 (file)
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-
 package au.notzed.scripta;
 
-public class Generator {
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import static org.objectweb.asm.Opcodes.*;
+import org.objectweb.asm.Type;
+
+public class Generator implements ASTVisitor {
+       ClassWriter cw;
+       MethodVisitor mw;
+       int variableID = 1;
+       Map<String, Variable> variables = new HashMap<>();
+       LinkedList<Value> stack = new LinkedList<>();
+       ClassLoader classLoader = new ClassLoader() {
+       };
+       Map<String, String> imports = new HashMap<>();
+
+       record Variable(String name, int id, Class type) {
+       }
+
+       record Value(Class type, Consumer<MethodVisitor> insert) {
+               public Value then(Consumer<MethodVisitor> then) {
+                       return new Value(type, insert.andThen(then));
+               }
+       }
+
+       Variable addVariable(String name, Class<?> type) {
+               Variable var;
+               if (type == Double.class || type == Long.class) {
+                       variableID = (variableID + 1) & ~1;
+                       variables.put(name, var = new Variable(name, variableID, type));
+                       variableID += 2;
+               } else {
+                       variables.put(name, var = new Variable(name, variableID++, type));
+               }
+               return var;
+       }
+
+       private final String file;
+
+       // TBDH, for printing
+       String d = "";
+
+       void up() {
+       }
+
+       void down() {
+       }
+
+       public Generator(String file) {
+               this.file = file;
+
+               imports.put("System.out", "java.lang.System.out");
+               imports.put("System.err", "java.lang.System.err");
+               imports.put("print", "java.lang.System.out.print");
+               imports.put("println", "java.lang.System.out.println");
+               imports.put("printf", "java.lang.System.out.printf");
+               if (false) {
+                       for (Method m: Math.class.getMethods()) {
+                               if (Modifier.isStatic(m.getModifiers())) {
+                                       imports.put(m.getName(), "java.lang.Math." + m.getName());
+                               }
+                       }
+               }
+       }
+
+       @Override
+       public void visitScript(AST.ScriptA script) {
+               cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+               //cw = new ClassWriter(0);
+
+               cw.visit(V1_8, ACC_PUBLIC,
+                       "au/notzed/scripta/script",
+                       null,
+                       "java/lang/Object",
+                       new String[]{"au/notzed/scripta/Script"});
+
+               // TODO: work out debuginfo (jsr45)
+               cw.visitSource(file, null);
+
+               // Standard constructor (is it needed?)
+               if (true) {
+                       mw = cw.visitMethod(ACC_PUBLIC,
+                               "<init>", "()V",
+                               null, null);
+
+                       mw.visitCode();
+                       mw.visitVarInsn(ALOAD, 0);
+                       mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+                       mw.visitInsn(RETURN);
+                       mw.visitMaxs(1, 1);
+                       mw.visitEnd();
+               }
+
+               mw = cw.visitMethod(ACC_PUBLIC,
+                       "eval", "()V",
+                       null, null);
+
+               Label start = new Label();
+               Label end = new Label();
+
+               mw.visitLabel(start);
+
+               script.statements.accept(this);
+
+               mw.visitLabel(end);
+               mw.visitInsn(RETURN);
+               mw.visitMaxs(20, variableID);
+               mw.visitEnd();
+               cw.visitEnd();
+       }
+
+       @Override
+       public void visitIf(AST.SIf s
+       ) {
+               System.out.printf("%sif (", d);
+               s.test.accept(this);
+               System.out.println(")");
+               s.then.ifPresent(r -> {
+                       down();
+                       r.accept(this);
+                       up();
+               });
+               s.rest.ifPresent(r -> {
+                       System.out.printf("%selse", d);
+                       down();
+                       r.accept(this);
+                       up();
+               });
+               System.out.printf("%sfi\n", d);
+       }
+
+       @Override
+       public void visitWhile(AST.SWhile s
+       ) {
+               System.out.printf("%swhile (", d);
+               s.test.accept(this);
+               System.out.println(")");
+               down();
+               s.when.accept(this);
+               up();
+               System.out.printf("%swend\n", d);
+       }
+
+       static Class typeMap[] = {boolean.class, long.class, double.class, String.class};
+
+       @Override
+       public void visitDecl(AST.SDeclare s) {
+               System.out.printf("var %s%s\n", d, s.name);
+               Variable var;
+               Optional<Value> val;
+
+               if (variables.containsKey(s.name)) {
+                       throw new java.lang.IllegalArgumentException(s.lineNo + ": Variable redefined: " + s.name);
+               }
+
+               val = s.value.map(x -> visitExpression(x));
+
+               switch (s.type.type) {
+               case BOOLEAN:
+                       var = new Variable(s.name, variableID++, boolean.class);
+                       val = val.map(this::promoteBoolean);
+                       val.ifPresent(v -> {
+                               v.insert.accept(mw);
+                               mw.visitVarInsn(ISTORE, var.id);
+                       });
+                       break;
+               case INTEGER:
+                       var = new Variable(s.name, variableID++, long.class);
+                       val = val.map(this::promoteInt);
+                       val.ifPresent(v -> {
+                               v.insert.accept(mw);
+                               mw.visitVarInsn(LSTORE, var.id);
+                       });
+                       break;
+               case FLOAT:
+                       var = new Variable(s.name, variableID++, double.class);
+                       val = val.map(this::promoteFloat);
+                       val.ifPresent(v -> {
+                               v.insert.accept(mw);
+                               mw.visitVarInsn(DSTORE, var.id);
+                       });
+                       break;
+               case STRING:
+                       var = new Variable(s.name, variableID++, String.class);
+                       val = val.map(this::promoteString);
+                       val.ifPresent(v -> {
+                               v.insert.accept(mw);
+                               mw.visitVarInsn(ASTORE, var.id);
+                       });
+                       break;
+               case OBJECT:
+                       try {
+                       var = new Variable(s.name, variableID++, Class.forName(s.type.typeName.get().name(), false, classLoader));
+                       val.ifPresent(v -> {
+                               v.insert.accept(mw);
+                               mw.visitVarInsn(ASTORE, var.id);
+                       });
+               } catch (ClassNotFoundException ex) {
+                       throw new IllegalArgumentException(ex);
+               }
+               break;
+               default:
+                       throw new IllegalArgumentException();
+               }
+               variables.put(var.name, var);
+       }
+
+       @Override
+       public void visitAssign(AST.SAssign s) {
+               System.out.printf("%s%s = ", d, s.ref.name());
+
+               Variable var = variables.get(s.ref.name());
+               Value val = visitExpression(s.value);
+
+               // FIXME: can this go on var?
+               if (var.type == boolean.class) {
+                       val = promoteFloat(val);
+                       val.insert.accept(mw);
+                       mw.visitVarInsn(ISTORE, var.id);
+               } else if (var.type == long.class) {
+                       val = promoteInt(val);
+                       val.insert.accept(mw);
+                       mw.visitVarInsn(LSTORE, var.id);
+               } else if (var.type == double.class) {
+                       val = promoteFloat(val);
+                       val.insert.accept(mw);
+                       mw.visitVarInsn(DSTORE, var.id);
+               } else if (var.type == String.class) {
+                       val = promoteString(val);
+                       val.insert.accept(mw);
+                       mw.visitVarInsn(ASTORE, var.id);
+               } else {
+                       val.insert.accept(mw);
+                       mw.visitVarInsn(ASTORE, var.id);
+               }
+       }
+
+       @Override
+       public void visitCall(AST.SCall s) {
+               Value val = visitExpression(s.call);
+
+               val.insert.accept(mw);
+
+               System.out.printf("call result %s\n", val.type);
+               if (val.type != void.class) {
+                       System.out.printf(" drop result\n");
+                       if (val.type == double.class || val.type == long.class)
+                               mw.visitInsn(POP2);
+                       else
+                               mw.visitInsn(POP);
+               }
+       }
+
+       @Override
+       public void visitBreak(AST.SBreak s) {
+               System.out.print(d);
+               System.out.print(s.op);
+               s.label.ifPresent(l -> System.out.print(" " + l));
+               System.out.println();
+       }
+
+       @Override
+       public void visitReturn(AST.SReturn s) {
+               System.out.print(d);
+               System.out.print("return");
+               s.res.ifPresent(l -> {
+                       System.out.print(" ");
+                       l.accept(this);
+               });
+               System.out.println();
+       }
+
+       /* *********************** */
+       Value visitExpression(AST e) {
+               e.accept(this);
+               return stack.removeFirst();
+       }
+
+       @Override
+       public void visit(AST.XUnary e) {
+               Value a = stack.removeFirst();
+               System.out.print(e.op);
+               System.out.print("( ");
+               e.right.accept(this);
+               System.out.print(" )");
+
+               switch (e.op) {
+               case NEG:
+                       if (a.type == double.class) {
+                               mw.visitInsn(DNEG);
+                       } else if (a.type == long.class) {
+                               mw.visitInsn(LNEG);
+                       } else {
+                               throw new IllegalArgumentException(e.lineNo + ": expecting number");
+                       }
+                       break;
+               case NOT:
+                       if (a.type == boolean.class) {
+                               mw.visitInsn(ICONST_1);
+                               mw.visitInsn(IXOR);
+                       }
+                       break;
+               case NOP:
+               }
+       }
+
+       static final int intOp[] = {
+               LADD, LSUB, LMUL, LDIV, LUSHR, LSHL, LSHR, LAND, LOR, LXOR, LCMP, LCMP, LCMP, LCMP, LCMP, LCMP
+       };
+       static final int floatOp[] = {
+               DADD, DSUB, DMUL, DDIV, NOP, NOP, NOP, NOP, NOP, NOP, NOP, DCMPL, DCMPG, DCMPL, DCMPG, DCMPG, DCMPL
+       };
+       static final int cmpOp[] = {
+               IFNE, IFEQ, IFLE, IFGT, IFGE, IFLT
+       };
+
+       Value promoteBoolean(Value a) {
+               if (a.type == boolean.class) {
+                       return a;
+               } else if (Boolean.class.isAssignableFrom(a.type)) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Boolean", "booleanValue", "()Z", true));
+               } else if (a.type == String.class) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Z", false));
+               } else {
+                       throw new IllegalArgumentException("expecting boolean or boolean string");
+               }
+       }
+
+       Value promoteFloat(Value a) {
+               if (a.type == double.class) {
+                       return a;
+               } else if (a.type == long.class) {
+                       return a.then(mv -> mv.visitInsn(L2D));
+               } else if (Number.class.isAssignableFrom(a.type)) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "doubleValue", "()D", true));
+               } else if (a.type == String.class) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)D", false));
+               } else {
+                       throw new IllegalArgumentException("expecting number or numerical string");
+               }
+       }
+
+       Value promoteInt(Value a) {
+               if (a.type == long.class) {
+                       return a;
+               } else if (a.type == double.class) {
+                       return a.then(mv -> mv.visitInsn(D2L));
+               } else if (Number.class.isAssignableFrom(a.type)) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "longValue", "()D", true));
+               } else if (a.type == String.class) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)D", false));
+               } else {
+                       throw new IllegalArgumentException("expecting number or numerical string");
+               }
+       }
+
+       Value promoteString(Value a) {
+               if (a.type == String.class) {
+                       return a;
+               } else if (a.type == double.class) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "toString", "(D)Ljava/lang/String;", false));
+               } else if (a.type == long.class) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "toString", "(J)Ljava/lang/String;", false));
+               } else if (a.type == boolean.class) {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "toString", "(Z)Ljava/lang/String;", false));
+               } else {
+                       return a.then(mv -> mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "(Ljava/lang/Object;)Ljava/lang/String;", false));
+               }
+       }
+
+       @Override
+       public void visit(AST.XBinary e) {
+               e.right.accept(this);
+               e.left.accept(this);
+
+               stack.forEach(System.out::println);
+
+               Value a = stack.removeFirst();
+               Value b = stack.removeFirst();
+               Value v;
+
+               switch (e.op) {
+               case ADD:
+                       if (a.type == String.class || b.type == String.class) {
+                               stack.addFirst(new Value(double.class, mv -> {
+                                       promoteString(a).insert.accept(mv);
+                                       promoteString(b).insert.accept(mv);
+                                       mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false);
+                               }));
+                               return;
+                       }
+               case SUB, MUL, DIV:
+                       if (a.type == double.class || b.type == double.class) {
+                               stack.addFirst(new Value(double.class, mv -> {
+                                       promoteFloat(a).insert.accept(mv);
+                                       promoteFloat(b).insert.accept(mv);
+                                       mv.visitInsn(floatOp[e.op.ordinal()]);
+                               }));
+                       } else if (a.type == long.class || b.type == long.class) {
+                               stack.addFirst(new Value(long.class, mv -> {
+                                       promoteInt(a).insert.accept(mv);
+                                       promoteInt(b).insert.accept(mv);
+                                       mv.visitInsn(intOp[e.op.ordinal()]);
+                               }));
+                       } else {
+                               throw new IllegalArgumentException("expecting numbers");
+                       }
+                       break;
+               case LSR, LSL, ASR, AND, ORR, XOR:
+                       stack.addFirst(new Value(long.class, mv -> {
+                               promoteInt(a).insert.accept(mv);
+                               promoteInt(b).insert.accept(mv);
+                               mv.visitInsn(intOp[e.op.ordinal()]);
+                       }));
+                       break;
+               case CLT, CLE, CGT, CGE, CEQ, CNE:
+                       Label t = new Label();
+                       Label f = new Label();
+                       Value c,
+                        d;
+                       int cmp;
+
+                       if (a.type == double.class || b.type == double.class) {
+                               c = promoteFloat(a);
+                               d = promoteFloat(b);
+                               cmp = floatOp[e.op.ordinal()];
+                       } else if (a.type == long.class || b.type == long.class) {
+                               c = promoteInt(a);
+                               d = promoteInt(b);
+                               cmp = intOp[e.op.ordinal()];
+                       } else {
+                               throw new IllegalArgumentException("expecting numbers");
+                       }
+
+                       stack.addFirst(new Value(boolean.class, mv -> {
+                               c.insert.accept(mv);
+                               d.insert.accept(mv);
+                               mw.visitInsn(cmp);
+                               mw.visitJumpInsn(cmpOp[e.op.ordinal() - AST.XBinaryOp.CLT.ordinal()], t);
+
+                               mw.visitInsn(ICONST_1);
+                               mw.visitJumpInsn(GOTO, f);
+                               mw.visitLabel(t);
+                               mw.visitInsn(ICONST_0);
+                               mw.visitLabel(f);
+                       }));
+                       break;
+               case AAND, OOR, XXOR:
+                       if (a.type == boolean.class && b.type == boolean.class) {
+                               stack.addFirst(new Value(boolean.class, mv -> {
+                               }));
+                       } else {
+                               throw new IllegalArgumentException("expecting booleans");
+                       }
+                       break;
+               }
+       }
+
+       Predicate<Executable> matchParameters(List<Value> args) {
+               return (m) -> {
+                       Class<?>[] params = m.getParameterTypes();
+                       boolean ok = params.length == args.size();
+                       for (int i = 0; ok && i < params.length; i++) {
+                               Class<?> p = params[i];
+                               Class<?> a = args.get(i).type;
+                               ok = p.isAssignableFrom(a)
+                                       || p.isPrimitive() && Number.class.isAssignableFrom(a)
+                                       || a.isPrimitive() && Number.class.isAssignableFrom(p);
+                       }
+                       return ok;
+               };
+       }
+
+       // TODO: needs more lookup bits for subclasses etc
+       Value resolveConstructor(AST.XReference ref, List<Value> args) throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
+               if (imports.containsKey(ref.name())) {
+                       ref = new AST.XReference(ref.lineNo, imports.get(ref.name()).split("\\."));
+               }
+
+               Class<?> type = Class.forName(ref.name(), false, classLoader);
+               Optional<Constructor<?>> match = Stream.of(type.getConstructors())
+                       .filter(matchParameters(args))
+                       .findFirst();
+
+               if (match.isEmpty())
+                       throw new NoSuchMethodException(ref.lineNo + ": " + ref.name());
+
+               MethodHandle mh = MethodHandles.lookup().unreflectConstructor(match.get());
+
+               String tname = ref.name().replace('.', '/');
+               return new Value(type, mv -> {
+                       mw.visitTypeInsn(NEW, tname);
+                       mw.visitInsn(DUP);
+                       // TODO: coerce parameters
+                       args.forEach(a -> a.insert.accept(mv));
+                       mv.visitMethodInsn(INVOKESPECIAL, "", "<init>", mh.type().descriptorString(), false);
+               });
+       }
+
+       static boolean matchParameters(Executable m, List<Value> args) {
+               Class<?>[] params = m.getParameterTypes();
+               boolean ok = params.length == args.size();
+               for (int i = 0; ok && i < params.length; i++) {
+                       Class<?> p = params[i];
+                       Class<?> a = args.get(i).type();
+                       ok = p.isAssignableFrom(a)
+                               || p.isPrimitive() && Number.class.isAssignableFrom(a)
+                               || a.isPrimitive() && Number.class.isAssignableFrom(p);
+               }
+               return ok;
+       }
+
+       Value resolveMethod(AST.XReference ref, List<Value> args) throws NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
+               Class<?> type;
+
+               // FIXME: better import mechanism
+               if (imports.containsKey(ref.name())) {
+                       ref = new AST.XReference(ref.lineNo, imports.get(ref.name()).split("\\."));
+               }
+               int cname = ref.part.length - 1;
+               Field pfield = null;
+               Class<?> pclass = null;
+               System.out.printf("resolve: '%s'\n", ref.name());
+               for (; cname > 0; cname--) {
+                       try {
+                               System.out.printf(" ? %s\n", ref.part(0, cname));
+                               type = Class.forName(ref.part(0, cname), false, classLoader);
+                               System.out.printf("found class: %s for %s\n", type.getName(), ref.name());
+
+                               int fname = ref.part.length - cname;
+                               Class<?> ftype = type;
+field:          for (; fname < ref.part.length; fname++) {
+                                       System.out.printf(" . %-20s on %s\n", ref.part[fname], ftype);
+                                       if (ref.part[fname].equals("class")) {
+                                               pclass = ftype;
+                                               ftype = ftype.getClass();
+                                               continue;
+                                       }
+                                       if (fname == ref.part.length - 1) {
+                                               for (var m: ftype.getMethods()) {
+                                                       System.out.printf(" ! %s name=%s params=%s\n", m.getName(), m.getName().equals(ref.part[fname]), matchParameters(m, args));
+                                                       if (m.getName().equals(ref.part[fname]) && matchParameters(m, args)) {
+                                                               Class<?> vtype = ftype;
+                                                               if (Modifier.isStatic(m.getModifiers())) {
+                                                                       System.out.printf(" m %s %s:%s\n", m,
+                                                                               Type.getInternalName(ftype),
+                                                                               Type.getMethodDescriptor(m));
+                                                                       return new Value(m.getReturnType(), mv -> {
+                                                                               // TODO: promote/cast arguments
+                                                                               args.forEach(a -> a.insert.accept(mv));
+                                                                               mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
+                                                                       });
+                                                               } else if (pfield != null) {
+                                                                       Field vfield = pfield;
+                                                                       System.out.printf(" m %s %s:%s on %s\n", m,
+                                                                               Type.getInternalName(ftype),
+                                                                               Type.getMethodDescriptor(m),
+                                                                               Type.getInternalName(pfield.getDeclaringClass()));
+                                                                       return new Value(m.getReturnType(), mv -> {
+                                                                               mv.visitFieldInsn(GETSTATIC, Type.getInternalName(vfield.getDeclaringClass()), vfield.getName(), Type.getDescriptor(vfield.getType()));
+                                                                               // TODO: promote/cast arguments
+                                                                               args.forEach(a -> a.insert.accept(mv));
+                                                                               mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
+                                                                       });
+                                                               } else if (pclass != null) {
+                                                                       Class<?> ptype = pclass;
+                                                                       System.out.printf(" m %s %s:%s\n", m,
+                                                                               Type.getInternalName(ftype),
+                                                                               Type.getMethodDescriptor(m));
+                                                                       return new Value(m.getReturnType(), mv -> {
+                                                                               mv.visitLdcInsn(Type.getType(ptype));
+                                                                               mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
+                                                                               // TODO: promote/cast arguments
+                                                                               args.forEach(a -> a.insert.accept(mv));
+                                                                       });
+                                                               }
+                                                       }
+                                               }
+                                               throw new NoSuchMethodException(ref.part[fname]);
+                                       }
+                                       for (var f: ftype.getFields()) {
+                                               if (Modifier.isStatic(f.getModifiers()) && f.getName().equals(ref.part[fname])) {
+                                                       System.out.printf(" f %s\n", f);
+                                                       pfield = f;
+                                                       ftype = f.getType();
+                                                       continue field;
+                                               }
+                                       }
+                                       for (var c: ftype.getClasses()) {
+                                               if (Modifier.isStatic(c.getModifiers()) && c.getSimpleName().equals(ref.part[fname])) {
+                                                       System.out.printf(" c %s %s\n", c, c.descriptorString());
+                                                       ftype = c;
+                                                       continue field;
+                                               }
+                                       }
+                                       break;
+                               }
+                               throw new NoSuchFieldException(ref.part[fname]);
+                       } catch (ClassNotFoundException x) {
+                       }
+               }
+
+               // check aliases I suppose
+               throw new ClassNotFoundException(ref.name());
+       }
+
+       Value resolveField(AST.XReference e) throws ClassNotFoundException, NoSuchFieldException {
+               int cname = e.part.length - 1;
+               Class<?> type;
+               System.out.printf("resolve: '%s'\n", e.name());
+               for (; cname > 0; cname--) {
+                       try {
+                               System.out.printf(" ? %s\n", e.part(0, cname));
+                               type = Class.forName(e.part(0, cname), false, classLoader);
+                               System.out.printf("found class: %s for %s\n", type.getName(), e.name());
+
+                               int fname = e.part.length - cname;
+                               Class<?> ftype = type;
+                               Field pfield = null;
+field:          for (; fname < e.part.length; fname++) {
+                                       System.out.printf(" . %s\n", e.part[fname]);
+                                       if (e.part[fname].equals("class")) {
+                                               if (fname == e.part.length - 1) {
+                                                       Class<?> vtype = ftype;
+                                                       return new Value(vtype, mv -> {
+                                                               mv.visitLdcInsn(Type.getType(vtype));
+                                                       });
+                                               }
+                                               ftype = ftype.getClass();
+                                               continue;
+                                       }
+                                       for (var f: ftype.getFields()) {
+                                               if (f.getName().equals(e.part[fname])) {
+                                                       System.out.printf(" f %s\n", f);
+                                                       if (fname == e.part.length - 1) {
+                                                               if (Modifier.isStatic(f.getModifiers())) {
+                                                                       if (f.getType().isPrimitive()) {
+                                                                               return new Value(f.getType(), mv -> {
+                                                                                       Class<?> vtype = f.getType();
+                                                                                       try {
+                                                                                               if (vtype == double.class) {
+                                                                                                       mv.visitLdcInsn(f.getDouble(f.getType()));
+                                                                                               } else if (vtype == long.class) {
+                                                                                                       mv.visitLdcInsn(f.getLong(f.getType()));
+                                                                                               } else if (vtype == int.class) {
+                                                                                                       mv.visitLdcInsn(f.getInt(f.getType()));
+                                                                                               } else if (vtype == boolean.class) {
+                                                                                                       mv.visitLdcInsn(f.getBoolean(f.getType()));
+                                                                                               }
+                                                                                       } catch (IllegalArgumentException ex) {
+                                                                                               ex.printStackTrace();
+                                                                                       } catch (IllegalAccessException ex) {
+                                                                                               ex.printStackTrace();
+                                                                                       }
+                                                                               });
+                                                                       } else {
+                                                                               return new Value(f.getType(), mv -> {
+                                                                                       mv.visitFieldInsn(GETFIELD, Type.getInternalName(f.getType()), f.getName(), Type.getDescriptor(f.getType()));
+                                                                               });
+                                                                       }
+                                                               } else if (pfield != null) {
+                                                                       Field vfield = pfield;
+                                                                       System.out.printf(" m %s %s:%s on %s\n", f,
+                                                                               Type.getInternalName(ftype),
+                                                                               Type.getDescriptor(f.getType()),
+                                                                               Type.getInternalName(pfield.getDeclaringClass()));
+                                                                       return new Value(f.getType(), mv -> {
+                                                                               mv.visitFieldInsn(GETSTATIC, Type.getInternalName(vfield.getDeclaringClass()), vfield.getName(), Type.getDescriptor(vfield.getType()));
+                                                                               mv.visitFieldInsn(GETFIELD, Type.getInternalName(f.getDeclaringClass()), f.getName(), Type.getDescriptor(f.getType()));
+                                                                       });
+                                                               }
+                                                       }
+                                                       pfield = f;
+                                                       ftype = f.getType();
+                                                       continue field;
+                                               }
+                                       }
+                                       for (var c: ftype.getClasses()) {
+                                               if (Modifier.isStatic(c.getModifiers()) && c.getSimpleName().equals(e.part[fname])) {
+                                                       System.out.printf(" c %s %s\n", c, c.descriptorString());
+                                                       ftype = c;
+                                                       continue field;
+                                               }
+                                       }
+                                       break;
+                               }
+                               throw new NoSuchFieldException(e.part[fname]);
+                       } catch (ClassNotFoundException x) {
+                       }
+               }
+
+               // check aliases I suppose
+               throw new ClassNotFoundException(e.name());
+       }
+
+       @Override
+       public void visit(AST.XCall e) {
+               System.out.println(e);
+               Variable var = variables.get(e.ref.base());
+
+               try {
+                       List<Value> args = e.params.stream().map(this::visitExpression).toList();
+                       Value m = resolveMethod(e.ref, args);
+
+                       stack.push(m);
+               } catch (ClassNotFoundException ex) {
+                       ex.printStackTrace();
+               } catch (NoSuchFieldException ex) {
+                       ex.printStackTrace();
+               } catch (NoSuchMethodException ex) {
+                       ex.printStackTrace();
+               }
+
+               if (false)
+                       if (var != null && e.ref.part.length == 2) {
+                               String name = e.ref.part[1];
+                               try {
+                                       Class<?> type;
+                                       // virtual call
+                                       if (var.type == double.class) {
+                                               mw.visitVarInsn(DLOAD, var.id);
+                                               mw.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
+                                               type = Double.class;
+                                       } else if (var.type == long.class) {
+                                               mw.visitVarInsn(LLOAD, var.id);
+                                               mw.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(D)Ljava/lang/Long;", false);
+                                               type = Long.class;
+                                       } else if (var.type == boolean.class) {
+                                               mw.visitVarInsn(ILOAD, var.id);
+                                               mw.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(D)Ljava/lang/Boolean;", false);
+                                               type = Boolean.class;
+                                       } else {
+                                               mw.visitVarInsn(ALOAD, var.id);
+                                               type = var.type;
+                                       }
+                                       List<Value> params = e.params.stream()
+                                               .map(this::visitExpression)
+                                               .toList();
+                                       Class[] args = params.stream().map(p -> p.type).toArray(Class[]::new);
+                                       Method method = type.getMethod(name, args);
+                                       MethodHandles.Lookup lookup = MethodHandles.lookup();
+                                       MethodHandle mh = lookup.unreflect(method);
+                                       mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", name, mh.type().descriptorString(), false);
+                               } catch (NoSuchMethodException | SecurityException ex) {
+                                       throw new RuntimeException(ex);
+                               } catch (IllegalAccessException ex) {
+                                       ex.printStackTrace();
+                               }
+                       } else {
+                               // static call
+                               // package.class.field.func()
+                               // package.class.func()
+                       }
+
+               if (false) {
+                       e.ref.accept(this);
+                       System.out.print("(");
+                       Iterator<AST.Expression> it = e.params.iterator();
+                       if (it.hasNext())
+                               it.next().accept(this);
+                       while (it.hasNext()) {
+                               System.out.print(", ");
+                               it.next().accept(this);
+                       }
+                       System.out.print(")");
+               }
+       }
+
+       @Override
+       public void visit(AST.XFunction e) {
+               System.out.print("function (");
+               Iterator<String> it = e.params.iterator();
+               if (it.hasNext())
+                       System.out.print(it.next());
+               while (it.hasNext()) {
+                       System.out.print(", ");
+                       System.out.print(it.next());
+               }
+               System.out.print(") = {\n");
+               down();
+               visitStatements(e.statements);
+               up();
+               System.out.printf("%s}\n", d);
+       }
+
+       @Override
+       public void visit(AST.XReference e) {
+               try {
+                       stack.push(resolveField(e));
+               } catch (ClassNotFoundException ex) {
+                       ex.printStackTrace();
+               } catch (NoSuchFieldException ex) {
+                       ex.printStackTrace();
+               }
+               /*
+               System.out.println("ref: " + e.name());
+               resolve(e);
+               stack.addFirst(new Value(Object.class, mw -> {
+               }));
+                */
+               // these needs to lookup what the reference is, and ...
+       }
+
+       @Override
+       public void visit(AST.XBool e) {
+               System.out.printf("%s", e.value);
+               stack.addFirst(new Value(boolean.class, mw
+                       -> mw.visitLdcInsn(e.value ? 1 : 0)));
+       }
+
+       @Override
+       public void visit(AST.XInteger e) {
+               System.out.printf("%dL\n", e.value);
+               stack.addFirst(new Value(long.class, mw
+                       -> mw.visitLdcInsn(e.value)));
+       }
+
+       @Override
+       public void visit(AST.XReal e) {
+               System.out.printf("%fD", e.value);
+               stack.addFirst(new Value(double.class, mw
+                       -> mw.visitLdcInsn(e.value)));
+       }
+
+       @Override
+       public void visit(AST.XString e) {
+               System.out.printf("`%s`", e.value);
+               stack.addFirst(new Value(String.class, mw
+                       -> mw.visitLdcInsn(e.value)));
+       }
 
 }
diff --git a/src/notzed.scripta/classes/au/notzed/scripta/Script.java b/src/notzed.scripta/classes/au/notzed/scripta/Script.java
new file mode 100644 (file)
index 0000000..56258a4
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+public interface Script {
+
+       public void eval();
+}
index ee3fd02..4fc4eda 100644 (file)
@@ -18,4 +18,6 @@
 module notzed.scripta {
        requires org.antlr.antlr4.runtime;
        requires org.objectweb.asm;
+
+       exports au.notzed.scripta;
 }
index d991268..3a89f56 100644 (file)
@@ -1,10 +1,12 @@
-// -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+// -*- Mode:text; tab-width:8; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
 
-// A basic c-like language with optional ';'
+// A basic c-ish language with static typing.
 
 // if (test) { } [ else { } ]
 // while (test) { }
 
+// TODO: some sort of data structure, may to java.util.* but with some syntax support
+
 grammar ScriptA;
 
 script
@@ -14,35 +16,39 @@ statements
        : statement (';'? statement)* ';'?;
 
 statement
-       : (label=ID ':')? IF '(' valueExpr ')' '{' then=statements? '}' ( ELSE '{' rest=statements? '}' )?      # ifStatement
-//     | (label=ID ':')? SWITCH '(' valueExpr ')' '{' cases* '}'                                                                                       # switchStatement
-       | (label=ID ':')? WHILE '(' valueExpr ')' '{' when=statements? '}'                                                                      # whileStatement
-       | scope? reference                                                                                                                                                                      # declStatement
-       | scope? reference '=' valueExpr                                                                                                                                        # assignStatement
-       | callExpr                                                                                                                                                                                      # callStatement
-       | CONTINUE (label=ID) ?                                                                                                                                                         # breakStatement
-       | BREAK (label=ID) ?                                                                                                                                                            # breakStatement
-       | RETURN valueExpr ?                                                                                                                                                            # returnStatement
+       : (label=ID ':')? IF '(' valueExpr ')'
+               '{' then=statements? '}'
+               ( ELSE '{' rest=statements? '}' )?                              # ifStatement
+//     | (label=ID ':')? SWITCH '(' valueExpr ')' '{' cases* '}'               # switchStatement
+       | (label=ID ':')? WHILE '(' valueExpr ')' '{' when=statements? '}'      # whileStatement
+       | type ID ( '=' valueExpr ) ?                                           # declStatement
+       | reference '=' valueExpr                                               # assignStatement
+       | callExpr                                                              # callStatement
+       | CONTINUE (label=ID) ?                                                 # breakStatement
+       | BREAK (label=ID) ?                                                    # breakStatement
+       | RETURN valueExpr ?                                                    # returnStatement
        ;
 
 
+// TODO: chained call: valueExpr '.' call
 valueExpr
-       : '(' valueExpr ')'                                                                                                     # valueGroupExpr
-       | op=('!'|'-'|'+') rightValue=valueExpr                                                         # valueUnaryExpr
-       | leftValue=valueExpr op=('*'|'/'|'%') rightValue=valueExpr                     # valueBinaryExpr
+       : '(' valueExpr ')'                                                     # valueGroupExpr
+       | op=('!'|'-'|'+') rightValue=valueExpr                                 # valueUnaryExpr
+       | leftValue=valueExpr op=('*'|'/'|'%') rightValue=valueExpr             # valueBinaryExpr
        | leftValue=valueExpr op=('&'|'|'|'+'|'-') rightValue=valueExpr         # valueBinaryExpr
        | leftValue=valueExpr op=('&&'|'||'|'^^') rightValue=valueExpr          # valueBinaryExpr
        | leftValue=valueExpr op=(LT|LE|GT|GE|EQ|NE) rightValue=valueExpr       # valueBinaryExpr
-       | lit=literal                                                                                                           # valueLiteralExpr
-       | ref=reference                                                                                                         # valueReferenceExpr
-       | call=callExpr                                                                                                         # valueCallExpr
-       | func=funcExpr                                                                                                         # valueFunctionExpr
+       | lit=literal                                                           # valueLiteralExpr
+       | ref=reference                                                         # valueReferenceExpr
+       | call=callExpr                                                         # valueCallExpr
+       | func=funcExpr                                                         # valueFunctionExpr
+       | callExpr ('.' valueExpr )+                                            # valueChainExpr
        ;
 
-callExpr       : reference '(' (valueExpr (',' valueExpr)*)? ')';
-funcExpr       : FUNC '(' (ID (',' ID) *) ? ')' '{' statements? '}';
+callExpr       : NEW ? reference '(' (valueExpr (',' valueExpr)*)? ')';
+funcExpr       : FUNC rval=type '(' (type ID (',' type ID) *) ? ')' '{' statements? '}';
 
-IF                     : 'if';
+IF             : 'if';
 ELSE           : 'else';
 SWITCH         : 'switch';
 CASE           : 'case';
@@ -52,45 +58,46 @@ BREAK               : 'break';
 RETURN         : 'return';
 FUNC           : 'function';
 DEFAULT                : 'default';
+NEW            : 'new';
 
-GLOBAL         : 'global';
-LOCAL          : 'local';
-PLAYER         : 'player';
-CONST          : 'const';
+INT            : 'int';
+FLT            : 'float';
+STR            : 'string';
+BLN            : 'boolean';
 
+type           : INT | FLT | STR | BLN | reference;
 literal                : INTEGER | FLOAT | STRING | TRUE | FALSE;
-scope          : GLOBAL | LOCAL | PLAYER | CONST;
 reference      : ID ('.' ID)*;
 
 TRUE           : 'true';
 FALSE          : 'false';
 
-LT                     : '<';
-LE                     : '<=';
-GT                     : '>';
-GE                     : '>=';
-EQ                     : '==';
-NE                     : '!=';
+LT             : '<';
+LE             : '<=';
+GT             : '>';
+GE             : '>=';
+EQ             : '==';
+NE             : '!=';
 
-CMP                    : ( LT | LE | GT | GE | EQ | NE );
+CMP            : ( LT | LE | GT | GE | EQ | NE );
 
-ADD                    : '+';
-SUB                    : '-';
-MUL                    : '*';
-DIV                    : '/';
-MOD                    : '%';
-NOT                    : '!';
+ADD            : '+';
+SUB            : '-';
+MUL            : '*';
+DIV            : '/';
+MOD            : '%';
+NOT            : '!';
 
 AAND           : '&&';
-OOR                    : '||';
+OOR            : '||';
 XXOR           : '^^';
 
 INTEGER                : [0-9]+;
 FLOAT          : [0-9]+ '.' ([0-9]+)? ([eE] [+-]? [0-9]+)?;
 STRING         : '"' (~[\\"] | '\\' [\\"])* '"'
                        | '\'' (~[\\'] | '\\' [\\'])* '\'';
-ID                     : [a-zA-Z_][0-9a-zA-Z_]*;
+ID             : [a-zA-Z_][0-9a-zA-Z_$]*;
 
-NL                     : ( '\n' | '\r\n' | '\r' ) -> channel(HIDDEN);
+NL             : ( '\n' | '\r\n' | '\r' ) -> channel(HIDDEN);
 COMMENT                : '#' ~[\r\n]* -> channel(HIDDEN);
-WS                     :  [ \t] -> channel(HIDDEN);
+WS             :  [ \t] -> channel(HIDDEN);