include config.make
java_MODULES = \
- notzed.scripta
+ notzed.scripta \
+ notzed.scriptb
# auto-generate the <module>_COMMANDS variables
define install-bin=
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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<? extends AST> 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<Statement> statements;
+
+ public Statements(int lineNo, List<Statement> 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<Statements> then;
+ final Optional<Statements> rest;
+
+ public SIf(int lineNo, Expression test, Optional<Statements> then, Optional<Statements> 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<Expression> value;
+
+ public SDeclare(int lineNo, DType type, String id, Optional<Expression> 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<String> label;
+
+ public SBreak(int lineNo, SBreakType op) {
+ super(lineNo);
+ this.op = op;
+ this.label = Optional.empty();
+ }
+
+ public SBreak(int lineNo, SBreakType op, String label) {
+ 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<Expression> 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<XReference> typeName;
+
+ public DType(int lineNo, XType type, Optional<XReference> typeName) {
+ super(lineNo);
+ this.type = type;
+ this.typeName = typeName;
+ }
+
+ @Override
+ public void accept(ASTVisitor av) {
+ av.visit(this);
+ }
+
+ @Override
+ public String toString() {
+ return typeName.orElse(new XReference(lineNo, type.name())).name();
+ }
+ }
+
+ /* **** expressions */
+ abstract static class Expression extends AST {
+ public Expression(int lineNo) {
+ 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<Expression> params;
+
+ public XCall(int lineNo, boolean constructor, XReference ref, List<Expression> params) {
+ super(lineNo);
+ this.constructor = constructor;
+ this.ref = ref;
+ this.params = params;
+
+ System.out.printf("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<DType> types;
+ final List<String> params;
+ final Statements statements;
+
+ public XFunction(int lineNo, DType rval, List<DType> types, List<String> params, Statements statements) {
+ super(lineNo);
+ this.params = params;
+ this.rval = rval;
+ this.types = types;
+ this.statements = statements;
+ }
+
+ @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);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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<AST> {
+
+ List<String> 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));
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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<AST.Expression> it = e.params.iterator();
+ if (it.hasNext())
+ it.next().accept(this);
+ while (it.hasNext()) {
+ System.out.print(", ");
+ it.next().accept(this);
+ }
+ System.out.print(")");
+ }
+
+ @Override
+ public void visit(AST.XFunction e) {
+ System.out.print("function (");
+ Iterator<String> ip = e.params.iterator();
+ Iterator<AST.DType> it = e.types.iterator();
+ if (ip.hasNext()) {
+ System.out.print(it.next().toString());
+ System.out.print(" ");
+ System.out.print(ip.next());
+ }
+ while (ip.hasNext()) {
+ System.out.print(", ");
+ System.out.print(it.next().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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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<String, Variable> variables = new HashMap<>();
+ LinkedList<Value> stack = new LinkedList<>();
+ ClassLoader classLoader = new ClassLoader() {
+ };
+ Map<String, String> imports = new HashMap<>();
+
+ record Variable(String name, int id, Class type) {
+ }
+
+ record Value(Class type, Consumer<MethodVisitor> insert) {
+ public Value then(Consumer<MethodVisitor> then) {
+ return new Value(type, insert.andThen(then));
+ }
+ }
+
+ Variable addVariable(String name, Class<?> type) {
+ Variable var;
+ if (type == Double.class || type == Long.class) {
+ variableID = (variableID + 1) & ~1;
+ variables.put(name, var = new Variable(name, variableID, type));
+ variableID += 2;
+ } else {
+ variables.put(name, var = new Variable(name, variableID++, type));
+ }
+ return var;
+ }
+
+ private final String file;
+
+ // TBDH, for printing
+ String d = "";
+
+ void up() {
+ }
+
+ void down() {
+ }
+
+ public Generator(String file) {
+ this.file = file;
+
+ imports.put("System.out", "java.lang.System.out");
+ imports.put("System.err", "java.lang.System.err");
+ imports.put("print", "java.lang.System.out.print");
+ imports.put("println", "java.lang.System.out.println");
+ imports.put("printf", "java.lang.System.out.printf");
+ if (false) {
+ for (Method m: Math.class.getMethods()) {
+ if (Modifier.isStatic(m.getModifiers())) {
+ imports.put(m.getName(), "java.lang.Math." + m.getName());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void visitScript(AST.ScriptA script) {
+ cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ //cw = new ClassWriter(0);
+
+ cw.visit(V1_8, ACC_PUBLIC,
+ "au/notzed/scripta/script",
+ null,
+ "java/lang/Object",
+ new String[]{"au/notzed/scripta/Script"});
+
+ // TODO: work out debuginfo (jsr45)
+ cw.visitSource(file, null);
+
+ // Standard constructor (is it needed?)
+ if (true) {
+ mw = cw.visitMethod(ACC_PUBLIC,
+ "<init>", "()V",
+ null, null);
+
+ mw.visitCode();
+ mw.visitVarInsn(ALOAD, 0);
+ mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mw.visitInsn(RETURN);
+ mw.visitMaxs(1, 1);
+ mw.visitEnd();
+ }
+
+ mw = cw.visitMethod(ACC_PUBLIC,
+ "eval", "()V",
+ null, null);
+
+ Label start = new Label();
+ Label end = new Label();
+
+ mw.visitLabel(start);
+
+ script.statements.accept(this);
+
+ mw.visitLabel(end);
+ mw.visitInsn(RETURN);
+ mw.visitMaxs(20, variableID);
+ mw.visitEnd();
+ cw.visitEnd();
+ }
+
+ @Override
+ public void visitIf(AST.SIf s
+ ) {
+ System.out.printf("%sif (", d);
+ s.test.accept(this);
+ System.out.println(")");
+ s.then.ifPresent(r -> {
+ down();
+ r.accept(this);
+ up();
+ });
+ s.rest.ifPresent(r -> {
+ System.out.printf("%selse", d);
+ down();
+ r.accept(this);
+ up();
+ });
+ System.out.printf("%sfi\n", d);
+ }
+
+ @Override
+ public void visitWhile(AST.SWhile s
+ ) {
+ System.out.printf("%swhile (", d);
+ s.test.accept(this);
+ System.out.println(")");
+ down();
+ s.when.accept(this);
+ up();
+ System.out.printf("%swend\n", d);
+ }
+
+ static Class typeMap[] = {boolean.class, long.class, double.class, String.class};
+
+ @Override
+ public void visitDecl(AST.SDeclare s) {
+ System.out.printf("var %s%s\n", d, s.name);
+ Variable var;
+ Optional<Value> val;
+
+ if (variables.containsKey(s.name)) {
+ throw new java.lang.IllegalArgumentException(s.lineNo + ": Variable redefined: " + s.name);
+ }
+
+ val = s.value.map(x -> visitExpression(x));
+
+ switch (s.type.type) {
+ case BOOLEAN:
+ var = new Variable(s.name, variableID++, boolean.class);
+ val = val.map(this::promoteBoolean);
+ val.ifPresent(v -> {
+ v.insert.accept(mw);
+ mw.visitVarInsn(ISTORE, var.id);
+ });
+ break;
+ case INTEGER:
+ var = new Variable(s.name, variableID++, long.class);
+ val = val.map(this::promoteInt);
+ val.ifPresent(v -> {
+ v.insert.accept(mw);
+ mw.visitVarInsn(LSTORE, var.id);
+ });
+ break;
+ case FLOAT:
+ var = new Variable(s.name, variableID++, double.class);
+ val = val.map(this::promoteFloat);
+ val.ifPresent(v -> {
+ v.insert.accept(mw);
+ mw.visitVarInsn(DSTORE, var.id);
+ });
+ break;
+ case STRING:
+ var = new Variable(s.name, variableID++, String.class);
+ val = val.map(this::promoteString);
+ val.ifPresent(v -> {
+ v.insert.accept(mw);
+ mw.visitVarInsn(ASTORE, var.id);
+ });
+ break;
+ case OBJECT:
+ try {
+ var = new Variable(s.name, variableID++, Class.forName(s.type.typeName.get().name(), false, classLoader));
+ val.ifPresent(v -> {
+ v.insert.accept(mw);
+ mw.visitVarInsn(ASTORE, var.id);
+ });
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ variables.put(var.name, var);
+ }
+
+ @Override
+ public void visitAssign(AST.SAssign s) {
+ System.out.printf("%s%s = ", d, s.ref.name());
+
+ Variable var = variables.get(s.ref.name());
+ Value val = visitExpression(s.value);
+
+ // FIXME: can this go on var?
+ if (var.type == boolean.class) {
+ val = promoteFloat(val);
+ val.insert.accept(mw);
+ mw.visitVarInsn(ISTORE, var.id);
+ } else if (var.type == long.class) {
+ val = promoteInt(val);
+ val.insert.accept(mw);
+ mw.visitVarInsn(LSTORE, var.id);
+ } else if (var.type == double.class) {
+ val = promoteFloat(val);
+ val.insert.accept(mw);
+ mw.visitVarInsn(DSTORE, var.id);
+ } else if (var.type == String.class) {
+ val = promoteString(val);
+ val.insert.accept(mw);
+ mw.visitVarInsn(ASTORE, var.id);
+ } else {
+ val.insert.accept(mw);
+ mw.visitVarInsn(ASTORE, var.id);
+ }
+ }
+
+ @Override
+ public void visitCall(AST.SCall s) {
+ Value val = visitExpression(s.call);
+
+ val.insert.accept(mw);
+
+ System.out.printf("call result %s\n", val.type);
+ if (val.type != void.class) {
+ System.out.printf(" drop result\n");
+ if (val.type == double.class || val.type == long.class)
+ mw.visitInsn(POP2);
+ else
+ mw.visitInsn(POP);
+ }
+ }
+
+ @Override
+ public void visitBreak(AST.SBreak s) {
+ System.out.print(d);
+ System.out.print(s.op);
+ s.label.ifPresent(l -> System.out.print(" " + l));
+ System.out.println();
+ }
+
+ @Override
+ public void visitReturn(AST.SReturn s) {
+ System.out.print(d);
+ System.out.print("return");
+ s.res.ifPresent(l -> {
+ System.out.print(" ");
+ l.accept(this);
+ });
+ System.out.println();
+ }
+
+ /* *********************** */
+ Value visitExpression(AST e) {
+ e.accept(this);
+ return stack.removeFirst();
+ }
+
+ @Override
+ public void visit(AST.XUnary e) {
+ Value a = stack.removeFirst();
+ System.out.print(e.op);
+ System.out.print("( ");
+ e.right.accept(this);
+ System.out.print(" )");
+
+ switch (e.op) {
+ case NEG:
+ if (a.type == double.class) {
+ mw.visitInsn(DNEG);
+ } else if (a.type == long.class) {
+ mw.visitInsn(LNEG);
+ } else {
+ throw new IllegalArgumentException(e.lineNo + ": expecting number");
+ }
+ break;
+ case NOT:
+ if (a.type == boolean.class) {
+ mw.visitInsn(ICONST_1);
+ mw.visitInsn(IXOR);
+ }
+ break;
+ case NOP:
+ }
+ }
+
+ static final int intOp[] = {
+ LADD, LSUB, LMUL, LDIV, LUSHR, LSHL, LSHR, LAND, LOR, LXOR, LCMP, LCMP, LCMP, LCMP, LCMP, LCMP
+ };
+ static final int floatOp[] = {
+ DADD, DSUB, DMUL, DDIV, NOP, NOP, NOP, NOP, NOP, NOP, NOP, DCMPL, DCMPG, DCMPL, DCMPG, DCMPG, DCMPL
+ };
+ static final int cmpOp[] = {
+ IFNE, IFEQ, IFLE, IFGT, IFGE, IFLT
+ };
+
+ Value promoteBoolean(Value a) {
+ if (a.type == boolean.class) {
+ return a;
+ } else if (Boolean.class.isAssignableFrom(a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Boolean", "booleanValue", "()Z", true));
+ } else if (a.type == String.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Z", false));
+ } else {
+ throw new IllegalArgumentException("expecting boolean or boolean string");
+ }
+ }
+
+ Value promoteFloat(Value a) {
+ if (a.type == double.class) {
+ return a;
+ } else if (a.type == long.class) {
+ return a.then(mv -> mv.visitInsn(L2D));
+ } else if (Number.class.isAssignableFrom(a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "doubleValue", "()D", true));
+ } else if (a.type == String.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)D", false));
+ } else {
+ throw new IllegalArgumentException("expecting number or numerical string");
+ }
+ }
+
+ Value promoteInt(Value a) {
+ if (a.type == long.class) {
+ return a;
+ } else if (a.type == double.class) {
+ return a.then(mv -> mv.visitInsn(D2L));
+ } else if (Number.class.isAssignableFrom(a.type)) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Number", "longValue", "()D", true));
+ } else if (a.type == String.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)D", false));
+ } else {
+ throw new IllegalArgumentException("expecting number or numerical string");
+ }
+ }
+
+ Value promoteString(Value a) {
+ if (a.type == String.class) {
+ return a;
+ } else if (a.type == double.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "toString", "(D)Ljava/lang/String;", false));
+ } else if (a.type == long.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "toString", "(J)Ljava/lang/String;", false));
+ } else if (a.type == boolean.class) {
+ return a.then(mv -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "toString", "(Z)Ljava/lang/String;", false));
+ } else {
+ return a.then(mv -> mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "(Ljava/lang/Object;)Ljava/lang/String;", false));
+ }
+ }
+
+ @Override
+ public void visit(AST.XBinary e) {
+ e.right.accept(this);
+ e.left.accept(this);
+
+ stack.forEach(System.out::println);
+
+ Value a = stack.removeFirst();
+ Value b = stack.removeFirst();
+ Value v;
+
+ switch (e.op) {
+ case ADD:
+ if (a.type == String.class || b.type == String.class) {
+ stack.addFirst(new Value(double.class, mv -> {
+ promoteString(a).insert.accept(mv);
+ promoteString(b).insert.accept(mv);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false);
+ }));
+ return;
+ }
+ case SUB, MUL, DIV:
+ if (a.type == double.class || b.type == double.class) {
+ stack.addFirst(new Value(double.class, mv -> {
+ promoteFloat(a).insert.accept(mv);
+ promoteFloat(b).insert.accept(mv);
+ mv.visitInsn(floatOp[e.op.ordinal()]);
+ }));
+ } else if (a.type == long.class || b.type == long.class) {
+ stack.addFirst(new Value(long.class, mv -> {
+ promoteInt(a).insert.accept(mv);
+ promoteInt(b).insert.accept(mv);
+ mv.visitInsn(intOp[e.op.ordinal()]);
+ }));
+ } else {
+ throw new IllegalArgumentException("expecting numbers");
+ }
+ break;
+ case LSR, LSL, ASR, AND, ORR, XOR:
+ stack.addFirst(new Value(long.class, mv -> {
+ promoteInt(a).insert.accept(mv);
+ promoteInt(b).insert.accept(mv);
+ mv.visitInsn(intOp[e.op.ordinal()]);
+ }));
+ break;
+ case CLT, CLE, CGT, CGE, CEQ, CNE:
+ Label t = new Label();
+ Label f = new Label();
+ Value c,
+ d;
+ int cmp;
+
+ if (a.type == double.class || b.type == double.class) {
+ c = promoteFloat(a);
+ d = promoteFloat(b);
+ cmp = floatOp[e.op.ordinal()];
+ } else if (a.type == long.class || b.type == long.class) {
+ c = promoteInt(a);
+ d = promoteInt(b);
+ cmp = intOp[e.op.ordinal()];
+ } else {
+ throw new IllegalArgumentException("expecting numbers");
+ }
+
+ stack.addFirst(new Value(boolean.class, mv -> {
+ c.insert.accept(mv);
+ d.insert.accept(mv);
+ mw.visitInsn(cmp);
+ mw.visitJumpInsn(cmpOp[e.op.ordinal() - AST.XBinaryOp.CLT.ordinal()], t);
+
+ mw.visitInsn(ICONST_1);
+ mw.visitJumpInsn(GOTO, f);
+ mw.visitLabel(t);
+ mw.visitInsn(ICONST_0);
+ mw.visitLabel(f);
+ }));
+ break;
+ case AAND, OOR, XXOR:
+ if (a.type == boolean.class && b.type == boolean.class) {
+ stack.addFirst(new Value(boolean.class, mv -> {
+ }));
+ } else {
+ throw new IllegalArgumentException("expecting booleans");
+ }
+ break;
+ }
+ }
+
+ Predicate<Executable> matchParameters(List<Value> args) {
+ return (m) -> {
+ Class<?>[] params = m.getParameterTypes();
+ boolean ok = params.length == args.size();
+ for (int i = 0; ok && i < params.length; i++) {
+ Class<?> p = params[i];
+ Class<?> a = args.get(i).type;
+ ok = p.isAssignableFrom(a)
+ || p.isPrimitive() && Number.class.isAssignableFrom(a)
+ || a.isPrimitive() && Number.class.isAssignableFrom(p);
+ }
+ return ok;
+ };
+ }
+
+ // TODO: needs more lookup bits for subclasses etc
+ Value resolveConstructor(AST.XReference ref, List<Value> args) throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
+ if (imports.containsKey(ref.name())) {
+ ref = new AST.XReference(ref.lineNo, imports.get(ref.name()).split("\\."));
+ }
+
+ Class<?> type = Class.forName(ref.name(), false, classLoader);
+ Optional<Constructor<?>> match = Stream.of(type.getConstructors())
+ .filter(matchParameters(args))
+ .findFirst();
+
+ if (match.isEmpty())
+ throw new NoSuchMethodException(ref.lineNo + ": " + ref.name());
+
+ MethodHandle mh = MethodHandles.lookup().unreflectConstructor(match.get());
+
+ String tname = ref.name().replace('.', '/');
+ return new Value(type, mv -> {
+ mw.visitTypeInsn(NEW, tname);
+ mw.visitInsn(DUP);
+ // TODO: coerce parameters
+ args.forEach(a -> a.insert.accept(mv));
+ mv.visitMethodInsn(INVOKESPECIAL, "", "<init>", mh.type().descriptorString(), false);
+ });
+ }
+
+ static boolean matchParameters(Executable m, List<Value> args) {
+ Class<?>[] params = m.getParameterTypes();
+ boolean ok = params.length == args.size();
+ for (int i = 0; ok && i < params.length; i++) {
+ Class<?> p = params[i];
+ Class<?> a = args.get(i).type();
+ ok = p.isAssignableFrom(a)
+ || p.isPrimitive() && Number.class.isAssignableFrom(a)
+ || a.isPrimitive() && Number.class.isAssignableFrom(p);
+ }
+ return ok;
+ }
+
+ Value resolveMethod(AST.XReference ref, List<Value> args) throws NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
+ Class<?> type;
+
+ // FIXME: better import mechanism
+ if (imports.containsKey(ref.name())) {
+ ref = new AST.XReference(ref.lineNo, imports.get(ref.name()).split("\\."));
+ }
+ int cname = ref.part.length - 1;
+ Field pfield = null;
+ Class<?> pclass = null;
+ System.out.printf("resolve: '%s'\n", ref.name());
+ for (; cname > 0; cname--) {
+ try {
+ System.out.printf(" ? %s\n", ref.part(0, cname));
+ type = Class.forName(ref.part(0, cname), false, classLoader);
+ System.out.printf("found class: %s for %s\n", type.getName(), ref.name());
+
+ int fname = ref.part.length - cname;
+ Class<?> ftype = type;
+field: for (; fname < ref.part.length; fname++) {
+ System.out.printf(" . %-20s on %s\n", ref.part[fname], ftype);
+ if (ref.part[fname].equals("class")) {
+ pclass = ftype;
+ ftype = ftype.getClass();
+ continue;
+ }
+ if (fname == ref.part.length - 1) {
+ for (var m: ftype.getMethods()) {
+ System.out.printf(" ! %s name=%s params=%s\n", m.getName(), m.getName().equals(ref.part[fname]), matchParameters(m, args));
+ if (m.getName().equals(ref.part[fname]) && matchParameters(m, args)) {
+ Class<?> vtype = ftype;
+ if (Modifier.isStatic(m.getModifiers())) {
+ System.out.printf(" m %s %s:%s\n", m,
+ Type.getInternalName(ftype),
+ Type.getMethodDescriptor(m));
+ return new Value(m.getReturnType(), mv -> {
+ // TODO: promote/cast arguments
+ args.forEach(a -> a.insert.accept(mv));
+ mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
+ });
+ } else if (pfield != null) {
+ Field vfield = pfield;
+ System.out.printf(" m %s %s:%s on %s\n", m,
+ Type.getInternalName(ftype),
+ Type.getMethodDescriptor(m),
+ Type.getInternalName(pfield.getDeclaringClass()));
+ return new Value(m.getReturnType(), mv -> {
+ mv.visitFieldInsn(GETSTATIC, Type.getInternalName(vfield.getDeclaringClass()), vfield.getName(), Type.getDescriptor(vfield.getType()));
+ // TODO: promote/cast arguments
+ args.forEach(a -> a.insert.accept(mv));
+ mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
+ });
+ } else if (pclass != null) {
+ Class<?> ptype = pclass;
+ System.out.printf(" m %s %s:%s\n", m,
+ Type.getInternalName(ftype),
+ Type.getMethodDescriptor(m));
+ return new Value(m.getReturnType(), mv -> {
+ mv.visitLdcInsn(Type.getType(ptype));
+ mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(vtype), m.getName(), Type.getMethodDescriptor(m), false);
+ // TODO: promote/cast arguments
+ args.forEach(a -> a.insert.accept(mv));
+ });
+ }
+ }
+ }
+ throw new NoSuchMethodException(ref.part[fname]);
+ }
+ for (var f: ftype.getFields()) {
+ if (Modifier.isStatic(f.getModifiers()) && f.getName().equals(ref.part[fname])) {
+ System.out.printf(" f %s\n", f);
+ pfield = f;
+ ftype = f.getType();
+ continue field;
+ }
+ }
+ for (var c: ftype.getClasses()) {
+ if (Modifier.isStatic(c.getModifiers()) && c.getSimpleName().equals(ref.part[fname])) {
+ System.out.printf(" c %s %s\n", c, c.descriptorString());
+ ftype = c;
+ continue field;
+ }
+ }
+ break;
+ }
+ throw new NoSuchFieldException(ref.part[fname]);
+ } catch (ClassNotFoundException x) {
+ }
+ }
+
+ // check aliases I suppose
+ throw new ClassNotFoundException(ref.name());
+ }
+
+ Value resolveField(AST.XReference e) throws ClassNotFoundException, NoSuchFieldException {
+ int cname = e.part.length - 1;
+ Class<?> type;
+ System.out.printf("resolve: '%s'\n", e.name());
+ for (; cname > 0; cname--) {
+ try {
+ System.out.printf(" ? %s\n", e.part(0, cname));
+ type = Class.forName(e.part(0, cname), false, classLoader);
+ System.out.printf("found class: %s for %s\n", type.getName(), e.name());
+
+ int fname = e.part.length - cname;
+ Class<?> ftype = type;
+ Field pfield = null;
+field: for (; fname < e.part.length; fname++) {
+ System.out.printf(" . %s\n", e.part[fname]);
+ if (e.part[fname].equals("class")) {
+ if (fname == e.part.length - 1) {
+ Class<?> vtype = ftype;
+ return new Value(vtype, mv -> {
+ mv.visitLdcInsn(Type.getType(vtype));
+ });
+ }
+ ftype = ftype.getClass();
+ continue;
+ }
+ for (var f: ftype.getFields()) {
+ if (f.getName().equals(e.part[fname])) {
+ System.out.printf(" f %s\n", f);
+ if (fname == e.part.length - 1) {
+ if (Modifier.isStatic(f.getModifiers())) {
+ if (f.getType().isPrimitive()) {
+ return new Value(f.getType(), mv -> {
+ Class<?> vtype = f.getType();
+ try {
+ if (vtype == double.class) {
+ mv.visitLdcInsn(f.getDouble(f.getType()));
+ } else if (vtype == long.class) {
+ mv.visitLdcInsn(f.getLong(f.getType()));
+ } else if (vtype == int.class) {
+ mv.visitLdcInsn(f.getInt(f.getType()));
+ } else if (vtype == boolean.class) {
+ mv.visitLdcInsn(f.getBoolean(f.getType()));
+ }
+ } catch (IllegalArgumentException ex) {
+ ex.printStackTrace();
+ } catch (IllegalAccessException ex) {
+ ex.printStackTrace();
+ }
+ });
+ } else {
+ return new Value(f.getType(), mv -> {
+ mv.visitFieldInsn(GETFIELD, Type.getInternalName(f.getType()), f.getName(), Type.getDescriptor(f.getType()));
+ });
+ }
+ } else if (pfield != null) {
+ Field vfield = pfield;
+ System.out.printf(" m %s %s:%s on %s\n", f,
+ Type.getInternalName(ftype),
+ Type.getDescriptor(f.getType()),
+ Type.getInternalName(pfield.getDeclaringClass()));
+ return new Value(f.getType(), mv -> {
+ mv.visitFieldInsn(GETSTATIC, Type.getInternalName(vfield.getDeclaringClass()), vfield.getName(), Type.getDescriptor(vfield.getType()));
+ mv.visitFieldInsn(GETFIELD, Type.getInternalName(f.getDeclaringClass()), f.getName(), Type.getDescriptor(f.getType()));
+ });
+ }
+ }
+ pfield = f;
+ ftype = f.getType();
+ continue field;
+ }
+ }
+ for (var c: ftype.getClasses()) {
+ if (Modifier.isStatic(c.getModifiers()) && c.getSimpleName().equals(e.part[fname])) {
+ System.out.printf(" c %s %s\n", c, c.descriptorString());
+ ftype = c;
+ continue field;
+ }
+ }
+ break;
+ }
+ throw new NoSuchFieldException(e.part[fname]);
+ } catch (ClassNotFoundException x) {
+ }
+ }
+
+ // check aliases I suppose
+ throw new ClassNotFoundException(e.name());
+ }
+
+ @Override
+ public void visit(AST.XCall e) {
+ System.out.println(e);
+ Variable var = variables.get(e.ref.base());
+
+ try {
+ List<Value> args = e.params.stream().map(this::visitExpression).toList();
+ Value m = resolveMethod(e.ref, args);
+
+ stack.push(m);
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ } catch (NoSuchFieldException ex) {
+ ex.printStackTrace();
+ } catch (NoSuchMethodException ex) {
+ ex.printStackTrace();
+ }
+
+ if (false)
+ if (var != null && e.ref.part.length == 2) {
+ String name = e.ref.part[1];
+ try {
+ Class<?> type;
+ // virtual call
+ if (var.type == double.class) {
+ mw.visitVarInsn(DLOAD, var.id);
+ mw.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
+ type = Double.class;
+ } else if (var.type == long.class) {
+ mw.visitVarInsn(LLOAD, var.id);
+ mw.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(D)Ljava/lang/Long;", false);
+ type = Long.class;
+ } else if (var.type == boolean.class) {
+ mw.visitVarInsn(ILOAD, var.id);
+ mw.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(D)Ljava/lang/Boolean;", false);
+ type = Boolean.class;
+ } else {
+ mw.visitVarInsn(ALOAD, var.id);
+ type = var.type;
+ }
+ List<Value> params = e.params.stream()
+ .map(this::visitExpression)
+ .toList();
+ Class[] args = params.stream().map(p -> p.type).toArray(Class[]::new);
+ Method method = type.getMethod(name, args);
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodHandle mh = lookup.unreflect(method);
+ mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", name, mh.type().descriptorString(), false);
+ } catch (NoSuchMethodException | SecurityException ex) {
+ throw new RuntimeException(ex);
+ } catch (IllegalAccessException ex) {
+ ex.printStackTrace();
+ }
+ } else {
+ // static call
+ // package.class.field.func()
+ // package.class.func()
+ }
+
+ if (false) {
+ e.ref.accept(this);
+ System.out.print("(");
+ Iterator<AST.Expression> it = e.params.iterator();
+ if (it.hasNext())
+ it.next().accept(this);
+ while (it.hasNext()) {
+ System.out.print(", ");
+ it.next().accept(this);
+ }
+ System.out.print(")");
+ }
+ }
+
+ @Override
+ public void visit(AST.XFunction e) {
+ System.out.print("function (");
+ Iterator<String> it = e.params.iterator();
+ if (it.hasNext())
+ System.out.print(it.next());
+ while (it.hasNext()) {
+ System.out.print(", ");
+ System.out.print(it.next());
+ }
+ System.out.print(") = {\n");
+ down();
+ visitStatements(e.statements);
+ up();
+ System.out.printf("%s}\n", d);
+ }
+
+ @Override
+ public void visit(AST.XReference e) {
+ try {
+ stack.push(resolveField(e));
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ } catch (NoSuchFieldException ex) {
+ ex.printStackTrace();
+ }
+ /*
+ System.out.println("ref: " + e.name());
+ resolve(e);
+ stack.addFirst(new Value(Object.class, mw -> {
+ }));
+ */
+ // these needs to lookup what the reference is, and ...
+ }
+
+ @Override
+ public void visit(AST.XBool e) {
+ System.out.printf("%s", e.value);
+ stack.addFirst(new Value(boolean.class, mw
+ -> mw.visitLdcInsn(e.value ? 1 : 0)));
+ }
+
+ @Override
+ public void visit(AST.XInteger e) {
+ System.out.printf("%dL\n", e.value);
+ stack.addFirst(new Value(long.class, mw
+ -> mw.visitLdcInsn(e.value)));
+ }
+
+ @Override
+ public void visit(AST.XReal e) {
+ System.out.printf("%fD", e.value);
+ stack.addFirst(new Value(double.class, mw
+ -> mw.visitLdcInsn(e.value)));
+ }
+
+ @Override
+ public void visit(AST.XString e) {
+ System.out.printf("`%s`", e.value);
+ stack.addFirst(new Value(String.class, mw
+ -> mw.visitLdcInsn(e.value)));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.scriptb;
+
+public interface Script {
+
+ public void eval();
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+module notzed.scriptb {
+ requires org.antlr.antlr4.runtime;
+ requires org.objectweb.asm;
+
+ exports au.notzed.scriptb;
+}
--- /dev/null
+// -*- 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);
--- /dev/null
+
+notzed.scriptb_ANTLR4 = ScriptB
+notzed.scriptb-ScriptB_ANTLR4PACKAGE = au.notzed.scriptb
+notzed.scriptb-ScriptB_ANTLR4FLAGS = -visitor -no-listener
+