Initialise ScriptB implementation from ScriptA.
authorNot Zed <notzed@gmail.com>
Mon, 27 Mar 2023 02:21:54 +0000 (12:51 +1030)
committerNot Zed <notzed@gmail.com>
Mon, 27 Mar 2023 02:21:54 +0000 (12:51 +1030)
Makefile
src/notzed.scriptb/classes/au/notzed/scriptb/AST.java [new file with mode: 0644]
src/notzed.scriptb/classes/au/notzed/scriptb/ASTBuilder.java [new file with mode: 0644]
src/notzed.scriptb/classes/au/notzed/scriptb/ASTPrinter.java [new file with mode: 0644]
src/notzed.scriptb/classes/au/notzed/scriptb/ASTVisitor.java [new file with mode: 0644]
src/notzed.scriptb/classes/au/notzed/scriptb/Compiler.java [new file with mode: 0644]
src/notzed.scriptb/classes/au/notzed/scriptb/Generator.java [new file with mode: 0644]
src/notzed.scriptb/classes/au/notzed/scriptb/Script.java [new file with mode: 0644]
src/notzed.scriptb/classes/module-info.java [new file with mode: 0644]
src/notzed.scriptb/gen/ScriptB.g4 [new file with mode: 0644]
src/notzed.scriptb/gen/gen.make [new file with mode: 0644]

index e2dc638..354d41e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,8 @@ dist_EXTRA=                                   \
 include config.make
 
 java_MODULES =                                 \
-       notzed.scripta
+       notzed.scripta                          \
+       notzed.scriptb
 
 # auto-generate the <module>_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 (file)
index 0000000..4102454
--- /dev/null
@@ -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 <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);
+               }
+       }
+}
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 (file)
index 0000000..900809e
--- /dev/null
@@ -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 <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));
+       }
+}
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 (file)
index 0000000..c663c0e
--- /dev/null
@@ -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 <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);
+       }
+}
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 (file)
index 0000000..94142c3
--- /dev/null
@@ -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 <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);
+       }
+
+}
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 (file)
index 0000000..6f0d521
--- /dev/null
@@ -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 <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);
+               }
+       }
+
+}
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 (file)
index 0000000..ecbcb9e
--- /dev/null
@@ -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 <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)));
+       }
+
+}
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 (file)
index 0000000..6ef9094
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package au.notzed.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 (file)
index 0000000..98f9619
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+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 (file)
index 0000000..5c0fad3
--- /dev/null
@@ -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 (file)
index 0000000..e47090a
--- /dev/null
@@ -0,0 +1,5 @@
+
+notzed.scriptb_ANTLR4 = ScriptB
+notzed.scriptb-ScriptB_ANTLR4PACKAGE = au.notzed.scriptb
+notzed.scriptb-ScriptB_ANTLR4FLAGS = -visitor -no-listener
+