From 9ade3814c3d1fd5abb1d2805926fe14eba76c0df Mon Sep 17 00:00:00 2001 From: Not Zed Date: Mon, 27 Mar 2023 12:51:54 +1030 Subject: [PATCH] Initialise ScriptB implementation from ScriptA. --- Makefile | 3 +- .../classes/au/notzed/scriptb/AST.java | 531 +++++++++++ .../classes/au/notzed/scriptb/ASTBuilder.java | 283 ++++++ .../classes/au/notzed/scriptb/ASTPrinter.java | 189 ++++ .../classes/au/notzed/scriptb/ASTVisitor.java | 108 +++ .../classes/au/notzed/scriptb/Compiler.java | 153 ++++ .../classes/au/notzed/scriptb/Generator.java | 867 ++++++++++++++++++ .../classes/au/notzed/scriptb/Script.java | 22 + src/notzed.scriptb/classes/module-info.java | 23 + src/notzed.scriptb/gen/ScriptB.g4 | 103 +++ src/notzed.scriptb/gen/gen.make | 5 + 11 files changed, 2286 insertions(+), 1 deletion(-) create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/AST.java create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/ASTBuilder.java create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/ASTPrinter.java create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/ASTVisitor.java create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/Compiler.java create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/Generator.java create mode 100644 src/notzed.scriptb/classes/au/notzed/scriptb/Script.java create mode 100644 src/notzed.scriptb/classes/module-info.java create mode 100644 src/notzed.scriptb/gen/ScriptB.g4 create mode 100644 src/notzed.scriptb/gen/gen.make diff --git a/Makefile b/Makefile index e2dc638..354d41e 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,8 @@ dist_EXTRA= \ include config.make java_MODULES = \ - notzed.scripta + notzed.scripta \ + notzed.scriptb # auto-generate the _COMMANDS variables define install-bin= diff --git a/src/notzed.scriptb/classes/au/notzed/scriptb/AST.java b/src/notzed.scriptb/classes/au/notzed/scriptb/AST.java new file mode 100644 index 0000000..4102454 --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/AST.java @@ -0,0 +1,531 @@ +/* + * 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.scriptb; + +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; + + // visit children how? + public abstract void accept(ASTVisitor av); + + public void visitChildren(ASTVisitor av) { + } + + public void accept(List list, ASTVisitor av) { + for (AST s: list) { + s.accept(av); + } + } + + public AST(int lineNo) { + this.lineNo = lineNo; + } + + enum XType { + BOOLEAN, + INTEGER, + FLOAT, + STRING, + OBJECT + } + + enum XUnaryOp { + NOP, // for + + NEG, + NOT, + } + + enum XBinaryOp { + // arithmetic + ADD, + SUB, + MUL, + DIV, + MOD, + // bitwise + LSR, + LSL, + ASR, + AND, + ORR, + XOR, + // compare + CLT, + CLE, + CGT, + CGE, + CEQ, + CNE, + // boolean + AAND, + OOR, + XXOR, + } + + public static class ScriptA extends AST { + Statements statements; + + public ScriptA(Statements statements) { + super(0); + this.statements = statements; + } + + @Override + public void accept(ASTVisitor av) { + av.visitScript(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + statements.accept(av); + } + } + + public static class Statements extends AST { + List statements; + + public Statements(int lineNo, List statements) { + super(lineNo); + this.statements = statements; + } + + @Override + public void accept(ASTVisitor av) { + av.visitStatements(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + accept(statements, av); + } + + public static final Statements EMPTY = new Statements(0, List.of()); + } + + /* **** statements */ + public abstract static class Statement extends AST { + public Statement(int lineNo) { + super(lineNo); + } + } + + static class SIf extends Statement { + final Expression test; + final Optional then; + final Optional rest; + + public SIf(int lineNo, Expression test, Optional then, Optional rest) { + super(lineNo); + this.test = test; + this.then = then; + this.rest = rest; + } + + @Override + public void accept(ASTVisitor av) { + av.visitIf(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + test.accept(av); + then.ifPresent(r -> r.accept(av)); + rest.ifPresent(r -> r.accept(av)); + } + } + + static class SWhile extends Statement { + final Expression test; + final Statements when; + + public SWhile(int lineNo, Expression test, Statements when) { + super(lineNo); + this.test = test; + this.when = when; + } + + @Override + public void accept(ASTVisitor av) { + av.visitWhile(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + test.accept(av); + when.accept(av); + } + } + + static class SDeclare extends Statement { + final DType type; + final String name; + final Optional value; + + public SDeclare(int lineNo, DType type, String id, Optional value) { + super(lineNo); + this.type = type; + this.name = id; + this.value = value; + } + + @Override + public void accept(ASTVisitor av) { + av.visitDecl(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + value.ifPresent(x -> x.accept(av)); + } + } + + static class SAssign extends Statement { + final XReference ref; + final Expression value; + + public SAssign(int lineNo, AST.XReference ref, AST.Expression value) { + super(lineNo); + this.ref = ref; + this.value = value; + + System.out.printf(" new assign %s = %s\n", ref, value); + } + + @Override + public void accept(ASTVisitor av) { + av.visitAssign(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + ref.accept(av); + value.accept(av); + } + } + + enum SBreakType { + CONTINUE, + BREAK, + } + + static class SBreak extends Statement { + SBreakType op; + Optional label; + + public SBreak(int lineNo, SBreakType op) { + super(lineNo); + this.op = op; + this.label = Optional.empty(); + } + + public SBreak(int lineNo, SBreakType op, String label) { + super(lineNo); + this.label = Optional.of(label); + this.op = op; + } + + @Override + public void accept(ASTVisitor av) { + av.visitBreak(this); + } + } + + static class SReturn extends Statement { + Optional res; + + public SReturn(int lineNo) { + super(lineNo); + res = Optional.empty(); + } + + public SReturn(int lineNo, Expression res) { + super(lineNo); + this.res = Optional.of(res); + } + + @Override + public void accept(ASTVisitor av) { + av.visitReturn(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + res.ifPresent(r -> r.accept(av)); + } + } + + static class SCall extends Statement { + XCall call; + + public SCall(int lineNo, XCall call) { + super(lineNo); + this.call = call; + } + + @Override + public void accept(ASTVisitor av) { + av.visitCall(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + call.accept(av); + } + } + + /* **** 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) { + super(lineNo); + } + } + + static class XUnary extends Expression { + XUnaryOp op; + Expression right; + + public XUnary(int lineNo, XUnaryOp op, Expression right) { + super(lineNo); + this.op = op; + this.right = right; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + right.accept(av); + } + } + + static class XBinary extends Expression { + XBinaryOp op; + Expression left, right; + + public XBinary(int lineNo, XBinaryOp op, Expression left, Expression right) { + super(lineNo); + this.op = op; + this.left = left; + this.right = right; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + left.accept(av); + right.accept(av); + } + } + + /** + * 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 part[]; + + public XReference(int lineNo, String... part) { + super(lineNo); + 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 + public void accept(ASTVisitor av) { + av.visit(this); + } + } + + static class XCall extends Expression { + boolean constructor; + 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("call: %s%s ", constructor ? "new " : "", ref.name()); + params.forEach(p -> System.out.printf(" %s", p)); + System.out.println(); + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + ref.accept(av); + accept(params, av); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("[call "); + sb.append(ref); + sb.append(" "); + sb.append(String.join(", ", params.stream().map(x->x.toString()).toArray(String[]::new))); + sb.append("]"); + return sb.toString(); + } + } + + // return type? + static class XFunction extends Expression { + final DType rval; + final List types; + final List params; + final 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; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + + @Override + public void visitChildren(ASTVisitor av) { + statements.accept(av); + } + } + + static class XInteger extends Expression { + long value; + + public XInteger(int lineNo, long value) { + super(lineNo); + this.value = value; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + } + + static class XReal extends Expression { + double value; + + public XReal(int lineNo, double value) { + super(lineNo); + this.value = value; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + } + + static class XString extends Expression { + String value; + + public XString(int lineNo, String value) { + super(lineNo); + this.value = value; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + } + + static class XBool extends Expression { + boolean value; + + public XBool(int lineNo, boolean value) { + super(lineNo); + this.value = value; + } + + @Override + public void accept(ASTVisitor av) { + av.visit(this); + } + } +} diff --git a/src/notzed.scriptb/classes/au/notzed/scriptb/ASTBuilder.java b/src/notzed.scriptb/classes/au/notzed/scriptb/ASTBuilder.java new file mode 100644 index 0000000..900809e --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/ASTBuilder.java @@ -0,0 +1,283 @@ +/* + * 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.scriptb; + +import au.notzed.scriptb.AST.*; +import static au.notzed.scriptb.ScriptBParser.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.antlr.v4.runtime.Token; + +public class ASTBuilder extends ScriptBBaseVisitor { + + List errors = new ArrayList<>(); + + @Override + public AST.ScriptA visitScript(ScriptContext ctx) { + return new ScriptA(visitStatements(ctx.statements())); + } + + @Override + public AST.Statements visitStatements(StatementsContext ctx) { + return new AST.Statements(ctx.start.getLine(), ctx.statement().stream().map(this::visitStatement).toList()); + } + + public AST.Statement visitStatement(StatementContext ctx) { + return (AST.Statement)visit(ctx); + } + + @Override + public AST.SIf visitIfStatement(IfStatementContext ctx) { + ValueExprContext valX = ctx.valueExpr(); + AST ast = ctx.valueExpr().accept(this); + if (ctx.rest == null) { + if (ctx.then == null) + return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.empty(), Optional.empty()); + else + return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.of(visitStatements(ctx.then)), Optional.empty()); + } else { + if (ctx.then == null) + return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.empty(), Optional.of(visitStatements(ctx.rest))); + else + return new AST.SIf(ctx.start.getLine(), (Expression)ctx.valueExpr().accept(this), Optional.of(visitStatements(ctx.then)), Optional.of(visitStatements(ctx.rest))); + } + } + + @Override + public AST.SWhile visitWhileStatement(WhileStatementContext ctx) { + 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.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) { + return new AST.SAssign(ctx.start.getLine(), visitReference(ctx.reference()), visitValueExpr(ctx.valueExpr())); + } + + public AST.Expression visitValueExpr(ValueExprContext ctx) { + return (AST.Expression)ctx.accept(this); + } + + @Override + public AST visitCallStatement(CallStatementContext ctx) { + return new SCall(ctx.start.getLine(), visitCallExpr(ctx.callExpr())); + } + + @Override + public AST.XFunction visitFuncExpr(FuncExprContext ctx) { + 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()) + : AST.Statements.EMPTY + ); + } + + @Override + 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()); + } + + @Override + public AST visitBreakStatement(BreakStatementContext ctx) { + switch (ctx.start.getType()) { + case BREAK: + case CONTINUE: + case RETURN: + break; + default: + throw new UnsupportedOperationException(); + } + return super.visitBreakStatement(ctx); + } + + @Override + public AST.SReturn visitReturnStatement(ReturnStatementContext ctx) { + if (ctx.valueExpr() != null) { + return new AST.SReturn(ctx.start.getLine(), visitValueExpr(ctx.valueExpr())); + } else { + return new AST.SReturn(ctx.start.getLine()); + } + } + + /* ***************************************************************** */ + @Override + public AST.Expression visitValueGroupExpr(ValueGroupExprContext ctx) { + return visitValueExpr(ctx.valueExpr()); + } + + XUnaryOp valueUnaryOp(Token op) { + switch (op.getType()) { + case ADD: + return XUnaryOp.NOP; + case SUB: + return XUnaryOp.NEG; + case NOT: + return XUnaryOp.NOT; + default: + throw new UnsupportedOperationException(op.getLine() + ": " + VOCABULARY.getDisplayName(op.getType())); + } + } + + @Override + public AST.XUnary visitValueUnaryExpr(ValueUnaryExprContext ctx) { + return new AST.XUnary( + ctx.start.getLine(), + valueUnaryOp(ctx.op), + (Expression)visit(ctx.rightValue)); + } + + XBinaryOp valueBinaryOp(Token op) { + switch (op.getType()) { + case ADD: + return XBinaryOp.ADD; + case SUB: + return XBinaryOp.SUB; + case MUL: + return XBinaryOp.MUL; + case DIV: + return XBinaryOp.DIV; + case MOD: + return XBinaryOp.MOD; + case AAND: + return XBinaryOp.AAND; + case OOR: + return XBinaryOp.OOR; + case XXOR: + return XBinaryOp.XXOR; + case LT: + return XBinaryOp.CLT; + case LE: + return XBinaryOp.CLE; + case GT: + return XBinaryOp.CGT; + case GE: + return XBinaryOp.CGE; + case EQ: + return XBinaryOp.CEQ; + case NE: + return XBinaryOp.CNE; + default: + throw new UnsupportedOperationException(op.getLine() + ": " + VOCABULARY.getDisplayName(op.getType())); + } + } + + @Override + public AST.XBinary visitValueBinaryExpr(ValueBinaryExprContext ctx) { + XBinary xb = new XBinary( + ctx.op.getLine(), + valueBinaryOp(ctx.op), + (Expression)visit(ctx.leftValue), + (Expression)visit(ctx.rightValue)); + System.out.printf("visit binary %s %s %s\n", xb.left, xb.op, xb.right); + return new XBinary( + ctx.op.getLine(), + valueBinaryOp(ctx.op), + (Expression)visit(ctx.leftValue), + (Expression)visit(ctx.rightValue)); + } + + Expression visitLiteral(Token lit) { + switch (lit.getType()) { + case INTEGER: { + try { + return new XInteger(lit.getLine(), Long.parseLong(lit.getText())); + } catch (NumberFormatException x) { + errors.add("Invalid INTEGER: " + lit.getText()); + return new XInteger(lit.getLine(), Long.MAX_VALUE); + } + } + case FLOAT: { + try { + return new XReal(lit.getLine(), Double.parseDouble(lit.getText())); + } catch (NumberFormatException x) { + errors.add("Invalid REAL: " + lit.getText()); + return new XReal(lit.getLine(), Double.NaN); + } + } + case STRING: + return new XString(lit.getLine(), lit.getText()); + case TRUE: + return new XBool(lit.getLine(), true); + case FALSE: + return new XBool(lit.getLine(), false); + default: + throw new UnsupportedOperationException(lit.getLine() + ": " + VOCABULARY.getDisplayName(lit.getType())); + } + } + + @Override + public AST visitLiteral(LiteralContext ctx) { + return visitLiteral(ctx.start); + } + + @Override + public AST.XReference visitReference(ReferenceContext ctx) { + return new XReference(ctx.start.getLine(), ctx.ID().stream().map(t -> t.getText()).toArray(String[]::new)); + } +} diff --git a/src/notzed.scriptb/classes/au/notzed/scriptb/ASTPrinter.java b/src/notzed.scriptb/classes/au/notzed/scriptb/ASTPrinter.java new file mode 100644 index 0000000..c663c0e --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/ASTPrinter.java @@ -0,0 +1,189 @@ +/* + * 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.scriptb; + +import java.util.Iterator; + +public class ASTPrinter implements ASTVisitor { + StringBuilder d = new StringBuilder(); + + void down() { + d.append("\t"); + } + + void up() { + d.setLength(d.length() - 1); + } + + @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\n", 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); + } + + @Override + 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 visitAssign(AST.SAssign s) { + System.out.printf("%s%s = ", d, s.ref.name()); + s.value.accept(this); + System.out.println(); + } + + @Override + public void visitCall(AST.SCall s) { + System.out.printf("%s", d); + s.call.accept(this); + System.out.println(); + } + + @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(); + } + + /* *********************** */ + @Override + public void visit(AST.XUnary e) { + System.out.print(e.op); + System.out.print("( "); + e.right.accept(this); + System.out.print(" )"); + } + + @Override + public void visit(AST.XBinary e) { + System.out.print("( "); + e.left.accept(this); + System.out.print(" "); + System.out.print(e.op); + System.out.print(" "); + e.right.accept(this); + System.out.print(" )"); + } + + @Override + public void visit(AST.XCall e) { + 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 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().toString()); + System.out.print(" "); + System.out.print(ip.next()); + } + System.out.print(") = {\n"); + down(); + visitStatements(e.statements); + up(); + System.out.printf("%s}\n", d); + } + + @Override + public void visit(AST.XReference e) { + System.out.print(e.name()); + } + + @Override + public void visit(AST.XBool e) { + System.out.printf("%s", e.value); + } + + @Override + public void visit(AST.XInteger e) { + System.out.printf("%dL", e.value); + } + + @Override + public void visit(AST.XReal e) { + System.out.printf("%fD", e.value); + } + + @Override + public void visit(AST.XString e) { + System.out.printf("`%s`", e.value); + } +} diff --git a/src/notzed.scriptb/classes/au/notzed/scriptb/ASTVisitor.java b/src/notzed.scriptb/classes/au/notzed/scriptb/ASTVisitor.java new file mode 100644 index 0000000..94142c3 --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/ASTVisitor.java @@ -0,0 +1,108 @@ +/* + * 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.scriptb; + +import au.notzed.scriptb.AST.*; + +public interface ASTVisitor { + + public default void visitScript(ScriptA s) { + s.visitChildren(this); + } + + public default void visitStatements(Statements s) { + s.visitChildren(this); + } + + // statements + public default void visitStatement(Statement s) { + s.visitChildren(this); + } + + public default void visitIf(SIf s) { + s.visitChildren(this); + } + + public default void visitWhile(SWhile s) { + s.visitChildren(this); + } + + public default void visitDecl(SDeclare s) { + s.visitChildren(this); + } + + public default void visitAssign(SAssign s) { + s.visitChildren(this); + } + + public default void visitBreak(SBreak s) { + s.visitChildren(this); + } + + public default void visitCall(SCall s) { + s.visitChildren(this); + } + + 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); + } + + public default void visit(XUnary e) { + e.visitChildren(this); + } + + public default void visit(XBinary e) { + e.visitChildren(this); + } + + public default void visit(XReference e) { + e.visitChildren(this); + } + + public default void visit(XCall e) { + e.visitChildren(this); + } + + public default void visit(XFunction e) { + e.visitChildren(this); + } + + public default void visit(XBool e) { + e.visitChildren(this); + } + + public default void visit(XInteger e) { + e.visitChildren(this); + } + + public default void visit(XReal e) { + e.visitChildren(this); + } + + public default void visit(XString e) { + e.visitChildren(this); + } + +} diff --git a/src/notzed.scriptb/classes/au/notzed/scriptb/Compiler.java b/src/notzed.scriptb/classes/au/notzed/scriptb/Compiler.java new file mode 100644 index 0000000..6f0d521 --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/Compiler.java @@ -0,0 +1,153 @@ +/* + * 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.scriptb; + +import static au.notzed.scriptb.ScriptBParser.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; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; + +public class Compiler { + + static void dump(String s, ParseTree p) { + if (p.getPayload() instanceof ParserRuleContext t) { + System.out.printf("%s%s (%s)\n", s, t.start.getText(), t.getClass()); + } else if (p.getPayload() instanceof Token t) { + System.out.printf("%s%s\n", s, VOCABULARY.getDisplayName(t.getType())); + } else { + System.out.printf("%s%s\n", s, p.getPayload().getClass()); + } + for (int i = 0; i < p.getChildCount(); i++) { + dump(s + " ", p.getChild(i)); + } + } + + public static void main(String[] args) throws IOException { + String src = """ + int c = (2 + a) + int a = 2 + float b = 3 + int c = (2 + a) + print(a + b, c, "hello", (3 + "foo")) + 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 ScriptBLexer(inputStream); + var tokenStream = new CommonTokenStream(lexer); + + int line[] = new int[1]; + Stream.of(src.split("\n")).map(l -> String.format("%5d: %s", ++line[0], l)).forEach(System.out::println); + + if (false) { + Token t; + while ((t = lexer.nextToken()).getType() != Token.EOF) { + System.out.println(t); + } + } + + var parser = new ScriptBParser(tokenStream); + var cst = parser.script(); + + dump(" ", cst); + + AST.ScriptA script = new ASTBuilder().visitScript(cst); + + 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); + + 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 visitCall(AST.SCall s) { + System.out.printf(" call\n"); + ASTVisitor.super.visitCall(s); + } + + @Override + public void visit(AST.XBinary e) { + System.out.printf(" binary %s\n", e.op); + ASTVisitor.super.visit(e); + } + + @Override + public void visit(AST.XReference e) { + System.out.printf(" ref %s\n", e.name()); + ASTVisitor.super.visit(e); + } + }.visitScript(script); + } + } + +} diff --git a/src/notzed.scriptb/classes/au/notzed/scriptb/Generator.java b/src/notzed.scriptb/classes/au/notzed/scriptb/Generator.java new file mode 100644 index 0000000..ecbcb9e --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/Generator.java @@ -0,0 +1,867 @@ +/* + * 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.scriptb; + +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.scriptb/classes/au/notzed/scriptb/Script.java b/src/notzed.scriptb/classes/au/notzed/scriptb/Script.java new file mode 100644 index 0000000..6ef9094 --- /dev/null +++ b/src/notzed.scriptb/classes/au/notzed/scriptb/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.scriptb; + +public interface Script { + + public void eval(); +} diff --git a/src/notzed.scriptb/classes/module-info.java b/src/notzed.scriptb/classes/module-info.java new file mode 100644 index 0000000..98f9619 --- /dev/null +++ b/src/notzed.scriptb/classes/module-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +module notzed.scriptb { + requires org.antlr.antlr4.runtime; + requires org.objectweb.asm; + + exports au.notzed.scriptb; +} diff --git a/src/notzed.scriptb/gen/ScriptB.g4 b/src/notzed.scriptb/gen/ScriptB.g4 new file mode 100644 index 0000000..5c0fad3 --- /dev/null +++ b/src/notzed.scriptb/gen/ScriptB.g4 @@ -0,0 +1,103 @@ +// -*- Mode:text; tab-width:8; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +// 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 ScriptB; + +script + : statements? EOF; + +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 + | 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 + | 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 + | callExpr ('.' valueExpr )+ # valueChainExpr + ; + +callExpr : NEW ? reference '(' (valueExpr (',' valueExpr)*)? ')'; +funcExpr : FUNC rval=type '(' (type ID (',' type ID) *) ? ')' '{' statements? '}'; + +IF : 'if'; +ELSE : 'else'; +SWITCH : 'switch'; +CASE : 'case'; +WHILE : 'while'; +CONTINUE : 'continue'; +BREAK : 'break'; +RETURN : 'return'; +FUNC : 'function'; +DEFAULT : 'default'; +NEW : 'new'; + +INT : 'int'; +FLT : 'float'; +STR : 'string'; +BLN : 'boolean'; + +type : INT | FLT | STR | BLN | reference; +literal : INTEGER | FLOAT | STRING | TRUE | FALSE; +reference : ID ('.' ID)*; + +TRUE : 'true'; +FALSE : 'false'; + +LT : '<'; +LE : '<='; +GT : '>'; +GE : '>='; +EQ : '=='; +NE : '!='; + +CMP : ( LT | LE | GT | GE | EQ | NE ); + +ADD : '+'; +SUB : '-'; +MUL : '*'; +DIV : '/'; +MOD : '%'; +NOT : '!'; + +AAND : '&&'; +OOR : '||'; +XXOR : '^^'; + +INTEGER : [0-9]+; +FLOAT : [0-9]+ '.' ([0-9]+)? ([eE] [+-]? [0-9]+)?; +STRING : '"' (~[\\"] | '\\' [\\"])* '"' + | '\'' (~[\\'] | '\\' [\\'])* '\''; +ID : [a-zA-Z_][0-9a-zA-Z_$]*; + +NL : ( '\n' | '\r\n' | '\r' ) -> channel(HIDDEN); +COMMENT : '#' ~[\r\n]* -> channel(HIDDEN); +WS : [ \t] -> channel(HIDDEN); diff --git a/src/notzed.scriptb/gen/gen.make b/src/notzed.scriptb/gen/gen.make new file mode 100644 index 0000000..e47090a --- /dev/null +++ b/src/notzed.scriptb/gen/gen.make @@ -0,0 +1,5 @@ + +notzed.scriptb_ANTLR4 = ScriptB +notzed.scriptb-ScriptB_ANTLR4PACKAGE = au.notzed.scriptb +notzed.scriptb-ScriptB_ANTLR4FLAGS = -visitor -no-listener + -- 2.39.2