From c57c3a9b89ed350a8fd4ecc39974abb960d6c1c1 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Mon, 27 Mar 2023 12:45:12 +1030 Subject: [PATCH] Partial .class compiler implementation. --- .gitignore | 2 + nbproject/project.properties | 10 +- .../classes/au/notzed/scripta/AST.java | 138 ++- .../classes/au/notzed/scripta/ASTBuilder.java | 69 +- .../classes/au/notzed/scripta/ASTPrinter.java | 66 +- .../classes/au/notzed/scripta/ASTVisitor.java | 28 +- .../classes/au/notzed/scripta/Compiler.java | 75 +- .../classes/au/notzed/scripta/Generator.java | 849 +++++++++++++++++- .../classes/au/notzed/scripta/Script.java | 22 + src/notzed.scripta/classes/module-info.java | 2 + src/notzed.scripta/gen/ScriptA.g4 | 93 +- 11 files changed, 1192 insertions(+), 162 deletions(-) create mode 100644 src/notzed.scripta/classes/au/notzed/scripta/Script.java diff --git a/.gitignore b/.gitignore index 2025f8b..eae6f0d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /.lib-sources/ bin config.make +/build/ +/dist/ diff --git a/nbproject/project.properties b/nbproject/project.properties index 31e3525..4cd700c 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -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 diff --git a/src/notzed.scripta/classes/au/notzed/scripta/AST.java b/src/notzed.scripta/classes/au/notzed/scripta/AST.java index 7756507..ff91f9c 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/AST.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/AST.java @@ -16,9 +16,11 @@ */ 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 statements; + Statements statements; - public ScriptA(List 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 then; final Optional 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 then, Optional 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 value; - public SDeclare(int lineNo, AST.XReference ref) { + public SDeclare(int lineNo, DType type, String id, Optional 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 typeName; + + public DType(int lineNo, XType type, Optional 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 params; - public XCall(int lineNo, XReference ref, List params) { + public XCall(int lineNo, boolean constructor, XReference ref, List 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 types; final List params; final Statements statements; - public XFunction(int lineNo, List params, Statements statements) { + public XFunction(int lineNo, DType rval, List types, List params, Statements statements) { super(lineNo); this.params = params; + this.rval = rval; + this.types = types; this.statements = statements; } diff --git a/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java b/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java index 742f119..25679fc 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/ASTBuilder.java @@ -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 { @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 { @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 { 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 { /* ***************************************************************** */ @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 { 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 { @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)); } } diff --git a/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java b/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java index e15e3a2..d5f4c51 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/ASTPrinter.java @@ -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 it = e.params.iterator(); - if (it.hasNext()) - System.out.print(it.next()); - while (it.hasNext()) { + Iterator ip = e.params.iterator(); + Iterator 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 diff --git a/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java b/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java index cd1548e..980514a 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/ASTVisitor.java @@ -17,53 +17,53 @@ 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); diff --git a/src/notzed.scripta/classes/au/notzed/scripta/Compiler.java b/src/notzed.scripta/classes/au/notzed/scripta/Compiler.java index 539a0c3..77a8bfe 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/Compiler.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/Compiler.java @@ -17,6 +17,12 @@ 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); } } diff --git a/src/notzed.scripta/classes/au/notzed/scripta/Generator.java b/src/notzed.scripta/classes/au/notzed/scripta/Generator.java index f3e1832..0807e8b 100644 --- a/src/notzed.scripta/classes/au/notzed/scripta/Generator.java +++ b/src/notzed.scripta/classes/au/notzed/scripta/Generator.java @@ -14,9 +14,854 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - 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 variables = new HashMap<>(); + LinkedList stack = new LinkedList<>(); + ClassLoader classLoader = new ClassLoader() { + }; + Map imports = new HashMap<>(); + + record Variable(String name, int id, Class type) { + } + + record Value(Class type, Consumer insert) { + public Value then(Consumer 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, + "", "()V", + null, null); + + mw.visitCode(); + mw.visitVarInsn(ALOAD, 0); + mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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 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 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; + + // 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 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(")"); + } + } + + @Override + public void visit(AST.XFunction e) { + System.out.print("function ("); + Iterator 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 index 0000000..56258a4 --- /dev/null +++ b/src/notzed.scripta/classes/au/notzed/scripta/Script.java @@ -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 . + */ +package au.notzed.scripta; + +public interface Script { + + public void eval(); +} diff --git a/src/notzed.scripta/classes/module-info.java b/src/notzed.scripta/classes/module-info.java index ee3fd02..4fc4eda 100644 --- a/src/notzed.scripta/classes/module-info.java +++ b/src/notzed.scripta/classes/module-info.java @@ -18,4 +18,6 @@ module notzed.scripta { requires org.antlr.antlr4.runtime; requires org.objectweb.asm; + + exports au.notzed.scripta; } diff --git a/src/notzed.scripta/gen/ScriptA.g4 b/src/notzed.scripta/gen/ScriptA.g4 index d991268..3a89f56 100644 --- a/src/notzed.scripta/gen/ScriptA.g4 +++ b/src/notzed.scripta/gen/ScriptA.g4 @@ -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); -- 2.39.2