// ex: se sts=4 sw=4 expandtab: /* * Yeti language compiler java bytecode generator. * * Copyright (c) 2007-2013 Madis Janson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package yeti.lang.compiler; import yeti.renamed.asmx.*; import java.util.*; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; final class BuiltIn implements Binder { int op; public BuiltIn(int op) { this.op = op; } static BindRef undef_str(Binder binder, int line) { return new StaticRef("yeti/lang/Core", "UNDEF_STR", YetiType.STR_TYPE, binder, true, line); } public BindRef getRef(int line) { BindRef r = null; switch (op) { //case 1: WAS ARGV case 2: r = new InOpFun(line); break; case 3: r = new Cons(line); break; case 4: r = new LazyCons(line); break; case 5: r = new For(line); break; case 6: r = new Compose(line); break; case 7: r = new Synchronized(line); break; case 8: r = new IsNullPtr(YetiType.A_TO_BOOL, "nullptr$q", line); break; case 9: r = new IsDefined(line); break; case 10: r = new IsEmpty(line); break; case 11: r = new Head(line); break; case 12: r = new Tail(line); break; case 13: r = new MatchOpFun(line, true); break; case 14: r = new MatchOpFun(line, false); break; case 15: r = new NotOp(line); break; case 16: r = new StrChar(line); break; case 17: r = new UnitConstant(YetiType.BOOL_TYPE); break; case 18: r = new BooleanConstant(false); break; case 19: r = new BooleanConstant(true); break; case 20: r = new Negate(); break; case 21: r = new Same(); break; case 23: r = undef_str(this, line); break; case 24: r = new Escape(line); break; case 25: r = new Length(); break; case 26: r = new Throw(line); break; } r.binder = this; return r; } } class IsNullPtr extends StaticRef { private String libName; boolean normalIf; IsNullPtr(YType type, String fun, int line) { super(fun, type, true, line); } Code apply(final Code arg, final YType res, final int line) { return new Code() { { type = res; } void gen(Ctx ctx) { IsNullPtr.this.gen(ctx, arg, line); } void genIf(Ctx ctx, Label to, boolean ifTrue) { if (normalIf) { super.genIf(ctx, to, ifTrue); } else { IsNullPtr.this.genIf(ctx, arg, to, ifTrue, line); } } }; } void gen(Ctx ctx, Code arg, int line) { Label label = new Label(); genIf(ctx, arg, label, false, line); ctx.genBoolean(label); } void genIf(Ctx ctx, Code arg, Label to, boolean ifTrue, int line) { arg.gen(ctx); ctx.jumpInsn(ifTrue ? IFNULL : IFNONNULL, to); } } final class IsDefined extends IsNullPtr { IsDefined(int line) { super(YetiType.A_TO_BOOL, "defined$q", line); } void genIf(Ctx ctx, Code arg, Label to, boolean ifTrue, int line) { Label isNull = new Label(), end = new Label(); arg.gen(ctx); ctx.insn(DUP); ctx.jumpInsn(IFNULL, isNull); ctx.fieldInsn(GETSTATIC, "yeti/lang/Core", "UNDEF_STR", "Ljava/lang/String;"); ctx.jumpInsn(IF_ACMPEQ, ifTrue ? end : to); ctx.jumpInsn(GOTO, ifTrue ? to : end); ctx.visitLabel(isNull); ctx.insn(POP); if (!ifTrue) { ctx.jumpInsn(GOTO, to); } ctx.visitLabel(end); } } final class IsEmpty extends IsNullPtr { IsEmpty(int line) { super(YetiType.MAP_TO_BOOL, "empty$q", line); } void genIf(Ctx ctx, Code arg, Label to, boolean ifTrue, int line) { Label isNull = new Label(), end = new Label(); arg.gen(ctx); ctx.visitLine(line); ctx.insn(DUP); ctx.jumpInsn(IFNULL, isNull); if (ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "yeti/lang/Coll"); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/Coll", "isEmpty", "()Z"); ctx.jumpInsn(IFNE, ifTrue ? to : end); ctx.jumpInsn(GOTO, ifTrue ? end : to); ctx.visitLabel(isNull); ctx.insn(POP); if (ifTrue) { ctx.jumpInsn(GOTO, to); } ctx.visitLabel(end); } } final class Head extends IsNullPtr { Head(int line) { super(YetiType.LIST_TO_A, "head", line); } void gen(Ctx ctx, Code arg, int line) { arg.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/AList"); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AList", "first", "()Ljava/lang/Object;"); } } final class Tail extends IsNullPtr { Tail(int line) { super(YetiType.LIST_TO_LIST, "tail", line); } void gen(Ctx ctx, Code arg, int line) { arg.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/AList"); ctx.insn(DUP); Label end = new Label(); ctx.jumpInsn(IFNULL, end); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AList", "rest", "()Lyeti/lang/AList;"); ctx.visitLabel(end); ctx.forceType("yeti/lang/AList"); } } final class Throw extends IsNullPtr { Throw(int line) { super(YetiType.THROW_TYPE, "throw", line); } void gen(Ctx ctx, Code arg, int line) { arg.gen(ctx); ctx.visitLine(line); JavaType t = arg.type.deref().javaType; ctx.typeInsn(CHECKCAST, t != null ? t.className() : "java/lang/Throwable"); ctx.insn(ATHROW); } } final class Escape extends IsNullPtr { Escape(int line) { super(YetiType.WITH_EXIT_TYPE, "withExit", line); normalIf = true; } void gen(Ctx ctx, Code block, int line) { block.gen(ctx); ctx.typeInsn(CHECKCAST, "yeti/lang/Fun"); ctx.methodInsn(INVOKESTATIC, "yeti/lang/EscapeFun", "with", "(Lyeti/lang/Fun;)Ljava/lang/Object;"); } } final class Negate extends StaticRef implements CodeGen { Negate() { super("negate", YetiType.NUM_TO_NUM, false, 0); } public void gen2(Ctx ctx, Code arg, int line) { arg.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/Num"); ctx.ldcInsn(new Long(0)); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", "subFrom", "(J)Lyeti/lang/Num;"); ctx.forceType("yeti/lang/Num"); } Code apply(final Code arg1, final YType res1, final int line) { if (arg1 instanceof NumericConstant) { return new NumericConstant(((NumericConstant) arg1) .num.subFrom(0)); } return new SimpleCode(this, arg1, YetiType.NUM_TYPE, line); } } final class Length extends StaticRef { Length() { super("length", YetiType.MAP_TO_NUM, true, 0); } void genLong(Ctx ctx, Code arg, int line, boolean toint) { if (arg instanceof Cast) { Code obj = ((Cast) arg).object; YType t = obj.type.deref(); if (t.type == YetiType.JAVA_ARRAY) { obj.gen(ctx); ctx.typeInsn(CHECKCAST, JavaType.descriptionOf(t)); ctx.visitLine(line); ctx.insn(ARRAYLENGTH); if (!toint) ctx.insn(I2L); return; } } Label nonnull = new Label(), end = new Label(); arg.gen(ctx); ctx.visitLine(line); // arrays can't be null, other can if (arg.type.deref().param[1].deref() != YetiType.NUM_TYPE) { ctx.insn(DUP); ctx.jumpInsn(IFNONNULL, nonnull); ctx.insn(POP); if (toint) ctx.intConst(0); else ctx.ldcInsn(new Long(0)); ctx.jumpInsn(GOTO, end); ctx.visitLabel(nonnull); } if (ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "yeti/lang/Coll"); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/Coll", "length", "()J"); if (toint) ctx.insn(L2I); ctx.visitLabel(end); } Code apply(final Code arg, final YType res, final int line) { return new Code() { { type = res; } void gen(Ctx ctx) { ctx.typeInsn(NEW, "yeti/lang/IntNum"); ctx.insn(DUP); genLong(ctx, arg, line, false); ctx.visitInit("yeti/lang/IntNum", "(J)V"); ctx.forceType("yeti/lang/Num"); } void genInt(Ctx ctx, int line_, boolean longValue) { genLong(ctx, arg, line, !longValue); } boolean flagop(int fl) { return (fl & INT_NUM) != 0; } }; } } abstract class Core2 extends StaticRef { boolean derivePolymorph; Core2(String coreFun, YType type, int line) { super(coreFun, type, true, line); } Code apply(final Code arg1, YType res, int line1) { return new Apply(res, this, arg1, line1) { Code apply(final Code arg2, final YType res, final int line2) { class A extends Code implements CodeGen { public void gen2(Ctx ctx, Code param, int line) { genApply2(ctx, arg1, arg2, line2); } void gen(Ctx ctx) { if (prepareConst(ctx)) { Object[] key = { Core2.this.getClass(), arg1.valueKey(), arg2.valueKey() }; ctx.constant(Arrays.asList(key), new SimpleCode(this, null, type, 0)); } else { genApply2(ctx, arg1, arg2, line2); } } boolean flagop(int fl) { return derivePolymorph && (fl & (CONST | PURE)) != 0 && arg1.flagop(fl) && arg2.flagop(fl); } boolean prepareConst(Ctx ctx) { return derivePolymorph && arg1.prepareConst(ctx) && arg2.prepareConst(ctx); } }; A r = new A(); r.type = res; r.polymorph = derivePolymorph && arg1.polymorph && arg2.polymorph; return r; } }; } abstract void genApply2(Ctx ctx, Code arg1, Code arg2, int line); } final class For extends Core2 { For(int line) { super("for", YetiType.FOR_TYPE, line); } void genApply2(Ctx ctx, Code list, Code fun, int line) { Function f; LoadVar arg = new LoadVar(); YType t; if (!list.flagop(LIST_RANGE) && fun instanceof Function && ((f = (Function) fun).body instanceof CaseExpr || (t = list.type.deref()).type == YetiType.MAP && t.param[1].deref().type == YetiType.NONE) && f.uncapture(arg)) { Label retry = new Label(), end = new Label(); list.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/AList"); ctx.insn(DUP); ctx.jumpInsn(IFNULL, end); ctx.insn(DUP); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AList", "isEmpty", "()Z"); ctx.jumpInsn(IFNE, end); // start of loop ctx.visitLabel(retry); ctx.insn(DUP); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AIter", "first", "()Ljava/lang/Object;"); // invoke body block ctx.varInsn(ASTORE, arg.var = ctx.localVarCount++); ++ctx.tainted; // disable argument-nulling - we're in cycle // new closure has to be created on each cycle // as closure vars could be captured f.genClosureInit(ctx); f.body.gen(ctx); --ctx.tainted; ctx.visitLine(line); ctx.insn(POP); // ignore return value // next ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AIter", "next", "()Lyeti/lang/AIter;"); ctx.insn(DUP); ctx.jumpInsn(IFNONNULL, retry); ctx.visitLabel(end); } else { Label nop = new Label(), end = new Label(); list.gen(ctx); fun.gen(ctx); ctx.visitLine(line); ctx.insn(SWAP); ctx.typeInsn(CHECKCAST, "yeti/lang/AList"); ctx.insn(DUP_X1); ctx.jumpInsn(IFNULL, nop); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AList", "forEach", "(Ljava/lang/Object;)V"); ctx.jumpInsn(GOTO, end); ctx.visitLabel(nop); ctx.insn(POP2); ctx.visitLabel(end); ctx.insn(ACONST_NULL); } } } final class Compose extends Core2 { Compose(int line) { super("$d", YetiType.COMPOSE_TYPE, line); derivePolymorph = true; } void genApply2(Ctx ctx, Code arg1, Code arg2, int line) { ctx.typeInsn(NEW, "yeti/lang/Compose"); ctx.insn(DUP); arg1.gen(ctx); arg2.gen(ctx); ctx.visitLine(line); ctx.visitInit("yeti/lang/Compose", "(Ljava/lang/Object;Ljava/lang/Object;)V"); } } final class Synchronized extends Core2 { Synchronized(int line) { super("synchronized", YetiType.SYNCHRONIZED_TYPE, line); } void genApply2(Ctx ctx, Code monitor, Code block, int line) { monitor.gen(ctx); int monitorVar = ctx.localVarCount++; ctx.visitLine(line); ctx.insn(DUP); ctx.varInsn(ASTORE, monitorVar); ctx.insn(MONITORENTER); Label startBlock = new Label(), endBlock = new Label(); ctx.visitLabel(startBlock); new Apply(type, block, new UnitConstant(null), line).gen(ctx); ctx.visitLine(line); ctx.load(monitorVar).insn(MONITOREXIT); ctx.visitLabel(endBlock); Label end = new Label(); ctx.jumpInsn(GOTO, end); Label startCleanup = new Label(), endCleanup = new Label(); ctx.tryCatchBlock(startBlock, endBlock, startCleanup, null); // I have no fucking idea, what this second catch is supposed // to be doing. javac generates it, so it has to be good. // yeah, sure... ctx.tryCatchBlock(startCleanup, endCleanup, startCleanup, null); int exceptionVar = ctx.localVarCount++; ctx.visitLabel(startCleanup); ctx.varInsn(ASTORE, exceptionVar); ctx.load(monitorVar).insn(MONITOREXIT); ctx.visitLabel(endCleanup); ctx.load(exceptionVar).insn(ATHROW); ctx.visitLabel(end); } } abstract class BinOpRef extends BindRef { boolean markTail2; String coreFun; class Result extends Code { private Code arg1; private Code arg2; Result(Code arg1, Code arg2, YType res) { type = res; this.arg1 = arg1; this.arg2 = arg2; } void gen(Ctx ctx) { binGen(ctx, arg1, arg2); } void genIf(Ctx ctx, Label to, boolean ifTrue) { binGenIf(ctx, arg1, arg2, to, ifTrue); } void markTail() { if (markTail2) { arg2.markTail(); } } } Code apply(final Code arg1, final YType res1, final int line) { return new Code() { { type = res1; } Code apply(Code arg2, YType res, int line) { return new Result(arg1, arg2, res); } void gen(Ctx ctx) { BinOpRef.this.gen(ctx); ctx.visitApply(arg1, line); } }; } void gen(Ctx ctx) { ctx.methodInsn(INVOKESTATIC, "yeti/lang/std", coreFun, "()Lyeti/lang/Fun;"); ctx.forceType("yeti/lang/Fun"); } abstract void binGen(Ctx ctx, Code arg1, Code arg2); void binGenIf(Ctx ctx, Code arg1, Code arg2, Label to, boolean ifTrue) { throw new UnsupportedOperationException("binGenIf"); } boolean flagop(int fl) { return (fl & STD_CONST) != 0; } } final class ArithOpFun extends BinOpRef { private String method; private int line; public ArithOpFun(String fun, String method, YType type, Binder binder, int line) { this.type = type; this.method = method; coreFun = fun; this.binder = binder; this.line = line; } public BindRef getRef(int line) { return this; // XXX should copy for type? } void binGen(Ctx ctx, Code arg1, Code arg2) { boolean arg2IsInt = arg2.flagop(INT_NUM); if (method == "and" && arg2IsInt) { ctx.typeInsn(NEW, "yeti/lang/IntNum"); ctx.insn(DUP); arg1.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/Num"); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", "longValue", "()J"); arg2.genInt(ctx, line, true); ctx.insn(LAND); ctx.visitInit("yeti/lang/IntNum", "(J)V"); ctx.forceType("yeti/lang/Num"); return; } arg1.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/Num"); if (method == "shl" || method == "shr") { arg2.genInt(ctx, line, false); if (method == "shr") ctx.insn(INEG); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", "shl", "(I)Lyeti/lang/Num;"); } else if (arg2IsInt) { boolean ii = method == "intDiv" || method == "rem"; arg2.genInt(ctx, line, !ii); ctx.visitLine(line); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", method, ii ? "(I)Lyeti/lang/Num;" : "(J)Lyeti/lang/Num;"); ctx.forceType("yeti/lang/Num"); } else { arg2.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/Num"); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", method, "(Lyeti/lang/Num;)Lyeti/lang/Num;"); } ctx.forceType("yeti/lang/Num"); } } final class ArithOp implements Binder { private String fun; private String method; private YType type; ArithOp(String op, String method, YType type) { fun = Code.mangle(op); this.method = method; this.type = type; } public BindRef getRef(int line) { return new ArithOpFun(fun, method, type, this, line); } } abstract class BoolBinOp extends BinOpRef { void binGen(Ctx ctx, Code arg1, Code arg2) { Label label = new Label(); binGenIf(ctx, arg1, arg2, label, false); ctx.genBoolean(label); } } final class CompareFun extends BoolBinOp { static final int COND_EQ = 0; static final int COND_NOT = 1; static final int COND_LT = 2; static final int COND_GT = 4; static final int COND_LE = COND_NOT | COND_GT; static final int COND_GE = COND_NOT | COND_LT; static final int[] OPS = { IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE }; static final int[] ROP = { IFEQ, IFNE, IFGT, IFLE, IFLT, IFGE }; int op; int line; void binGenIf(Ctx ctx, Code arg1, Code arg2, Label to, boolean ifTrue) { YType t = arg1.type.deref(); int op = this.op; boolean eq = (op & (COND_LT | COND_GT)) == 0; if (!ifTrue) { op ^= COND_NOT; } Label nojmp = null; if (t.type == YetiType.VAR || t.type == YetiType.MAP && t.param[2] == YetiType.LIST_TYPE && t.param[1].type != YetiType.NUM) { Label nonull = new Label(); nojmp = new Label(); arg2.gen(ctx); arg1.gen(ctx); // 2-1 ctx.visitLine(line); ctx.insn(DUP); // 2-1-1 ctx.jumpInsn(IFNONNULL, nonull); // 2-1 // reach here, when 1 was null if (op == COND_GT || op == COND_LE || arg2.flagop(EMPTY_LIST) && (op == COND_EQ || op == COND_NOT)) { // null is never greater and always less or equal ctx.insn(POP2); ctx.jumpInsn(GOTO, op == COND_LE || op == COND_EQ ? to : nojmp); } else { ctx.insn(POP); // 2 ctx.jumpInsn(op == COND_EQ || op == COND_GE ? IFNULL : IFNONNULL, to); ctx.jumpInsn(GOTO, nojmp); } ctx.visitLabel(nonull); if (!eq && ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "java/lang/Comparable"); ctx.insn(SWAP); // 1-2 } else if (arg2 instanceof StringConstant && ((StringConstant) arg2).str.length() == 0 && (op & COND_LT) == 0) { arg1.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "java/lang/String"); ctx.methodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I"); ctx.jumpInsn((op & COND_NOT) == (op >>> 2) ? IFEQ : IFNE, to); return; } else { arg1.gen(ctx); ctx.visitLine(line); if (arg2.flagop(INT_NUM)) { ctx.typeInsn(CHECKCAST, "yeti/lang/Num"); arg2.genInt(ctx, line, true); ctx.visitLine(line); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", "rCompare", "(J)I"); ctx.jumpInsn(ROP[op], to); return; } if (!eq && ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "java/lang/Comparable"); arg2.gen(ctx); ctx.visitLine(line); } if (eq) { op ^= COND_NOT; ctx.methodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z"); } else { ctx.methodInsn(INVOKEINTERFACE, "java/lang/Comparable", "compareTo", "(Ljava/lang/Object;)I"); } ctx.jumpInsn(OPS[op], to); if (nojmp != null) { ctx.visitLabel(nojmp); } } } final class Compare implements Binder { YType type; int op; String fun; public Compare(YType type, int op, String fun) { this.op = op; this.type = type; this.fun = Code.mangle(fun); } public BindRef getRef(int line) { CompareFun c = new CompareFun(); c.binder = this; c.type = type; c.op = op; c.polymorph = true; c.line = line; c.coreFun = fun; return c; } } final class Same extends BoolBinOp { Same() { type = YetiType.EQ_TYPE; polymorph = true; coreFun = "same$q"; } void binGenIf(Ctx ctx, Code arg1, Code arg2, Label to, boolean ifTrue) { arg1.gen(ctx); arg2.gen(ctx); ctx.jumpInsn(ifTrue ? IF_ACMPEQ : IF_ACMPNE, to); } } final class InOpFun extends BoolBinOp { int line; InOpFun(int line) { type = YetiType.IN_TYPE; this.line = line; polymorph = true; coreFun = "in"; } void binGenIf(Ctx ctx, Code arg1, Code arg2, Label to, boolean ifTrue) { arg2.gen(ctx); ctx.visitLine(line); if (ctx.compilation.isGCJ) { ctx.typeInsn(CHECKCAST, "yeti/lang/ByKey"); } arg1.gen(ctx); ctx.visitLine(line); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/ByKey", "containsKey", "(Ljava/lang/Object;)Z"); ctx.jumpInsn(ifTrue ? IFNE : IFEQ, to); } } final class NotOp extends StaticRef { NotOp(int line) { super("not", YetiType.BOOL_TO_BOOL, false, line); } Code apply(final Code arg, YType res, int line) { return new Code() { { type = YetiType.BOOL_TYPE; } void genIf(Ctx ctx, Label to, boolean ifTrue) { arg.genIf(ctx, to, !ifTrue); } void gen(Ctx ctx) { Label label = new Label(); arg.genIf(ctx, label, true); ctx.genBoolean(label); } }; } } final class BoolOpFun extends BoolBinOp implements Binder { boolean orOp; BoolOpFun(boolean orOp) { this.type = YetiType.BOOLOP_TYPE; this.orOp = orOp; this.binder = this; markTail2 = true; coreFun = orOp ? "or" : "and"; } public BindRef getRef(int line) { return this; } void binGen(Ctx ctx, Code arg1, Code arg2) { if (arg2 instanceof CompareFun) { super.binGen(ctx, arg1, arg2); } else { Label label = new Label(), end = new Label(); arg1.genIf(ctx, label, orOp); arg2.gen(ctx); ctx.jumpInsn(GOTO, end); ctx.visitLabel(label); ctx.fieldInsn(GETSTATIC, "java/lang/Boolean", orOp ? "TRUE" : "FALSE", "Ljava/lang/Boolean;"); ctx.visitLabel(end); } } void binGenIf(Ctx ctx, Code arg1, Code arg2, Label to, boolean ifTrue) { if (orOp == ifTrue) { arg1.genIf(ctx, to, orOp); arg2.genIf(ctx, to, orOp); } else { Label noJmp = new Label(); arg1.genIf(ctx, noJmp, orOp); arg2.genIf(ctx, to, !orOp); ctx.visitLabel(noJmp); } } } final class Cons extends BinOpRef { private int line; Cons(int line) { type = YetiType.CONS_TYPE; coreFun = "$c$c"; polymorph = true; this.line = line; } void binGen(Ctx ctx, Code arg1, Code arg2) { ctx.visitLine(line); ctx.typeInsn(NEW, "yeti/lang/LList"); ctx.insn(DUP); arg1.gen(ctx); arg2.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/AList"); if (arg2.type.deref().param[1].deref() != YetiType.NO_TYPE) { Label cons = new Label(); ctx.insn(DUP); ctx.jumpInsn(IFNULL, cons); // null, ok ctx.insn(DUP); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AList", "isEmpty", "()Z"); ctx.jumpInsn(IFEQ, cons); // not empty, ok ctx.insn(POP); // empty not-null, dump it ctx.insn(ACONST_NULL); // and use null instead ctx.visitLabel(cons); } ctx.visitInit("yeti/lang/LList", "(Ljava/lang/Object;Lyeti/lang/AList;)V"); ctx.forceType("yeti/lang/AList"); } } final class LazyCons extends BinOpRef { private int line; LazyCons(int line) { type = YetiType.LAZYCONS_TYPE; coreFun = "$c$d"; polymorph = true; this.line = line; } void binGen(Ctx ctx, Code arg1, Code arg2) { ctx.visitLine(line); ctx.typeInsn(NEW, "yeti/lang/LazyList"); ctx.insn(DUP); arg1.gen(ctx); arg2.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/Fun"); ctx.visitInit("yeti/lang/LazyList", "(Ljava/lang/Object;Lyeti/lang/Fun;)V"); ctx.forceType("yeti/lang/AList"); } } final class MatchOpFun extends BinOpRef implements CodeGen { private int line; private boolean yes; MatchOpFun(int line, boolean yes) { type = YetiType.STR2_PRED_TYPE; coreFun = mangle(yes ? "=~" : "!~"); this.line = line; this.yes = yes; } void binGen(Ctx ctx, Code arg1, final Code arg2) { apply2nd(arg2, YetiType.STR2_PRED_TYPE, line).gen(ctx); ctx.visitApply(arg1, line); } public void gen2(Ctx ctx, Code arg2, int line) { ctx.typeInsn(NEW, "yeti/lang/Match"); ctx.insn(DUP); arg2.gen(ctx); ctx.intConst(yes ? 1 : 0); ctx.visitLine(line); ctx.visitInit("yeti/lang/Match", "(Ljava/lang/Object;Z)V"); } Code apply2nd(final Code arg2, final YType t, final int line) { if (line == 0) { throw new NullPointerException(); } final Code matcher = new SimpleCode(this, arg2, t, line); if (!(arg2 instanceof StringConstant)) return matcher; try { Pattern.compile(((StringConstant) arg2).str, Pattern.DOTALL); } catch (PatternSyntaxException ex) { throw new CompileException(line, 0, "Bad pattern syntax: " + ex.getMessage()); } return new Code() { { type = t; } void gen(Ctx ctx) { ctx.constant((yes ? "MATCH-FUN:" : "MATCH!FUN:") .concat(((StringConstant) arg2).str), matcher); } }; } void binGenIf(Ctx ctx, Code arg1, Code arg2, Label to, boolean ifTrue) { binGen(ctx, arg1, arg2); ctx.fieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"); ctx.jumpInsn(ifTrue ? IF_ACMPEQ : IF_ACMPNE, to); } } final class RegexFun extends StaticRef implements CodeGen { private String impl; private String funName; RegexFun(String fun, String impl, YType type, Binder binder, int line) { super(fun, type, false, line); this.funName = fun; this.binder = binder; this.impl = impl; } public void gen2(Ctx ctx, Code arg, int line) { ctx.typeInsn(NEW, impl); ctx.insn(DUP); arg.gen(ctx); ctx.visitLine(line); ctx.visitInit(impl, "(Ljava/lang/Object;)V"); } Code apply(final Code arg, final YType t, final int line) { final Code f = new SimpleCode(this, arg, t, line); if (!(arg instanceof StringConstant)) return f; try { Pattern.compile(((StringConstant) arg).str, Pattern.DOTALL); } catch (PatternSyntaxException ex) { throw new CompileException(line, 0, "Bad pattern syntax: " + ex.getMessage()); } return new Code() { { type = t; } void gen(Ctx ctx) { ctx.constant(funName + ":regex:" + ((StringConstant) arg).str, f); } }; } } final class Regex implements Binder { private String fun, impl; private YType type; Regex(String fun, String impl, YType type) { this.fun = fun; this.impl = impl; this.type = type; } public BindRef getRef(int line) { return new RegexFun(fun, impl, type, this, line); } } final class ClassOfExpr extends Code implements CodeGen { String className; ClassOfExpr(JavaType what, int array) { type = YetiType.CLASS_TYPE; String cn = what.dottedName(); if (array != 0) { cn = 'L' + cn + ';'; do { cn = "[".concat(cn); } while (--array > 0); } className = cn; } public void gen2(Ctx ctx, Code param, int line) { ctx.ldcInsn(className); ctx.methodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); ctx.forceType("java/lang/Class"); } void gen(Ctx ctx) { ctx.constant("CLASS-OF:".concat(className), new SimpleCode(this, null, YetiType.CLASS_TYPE, 0)); } boolean flagop(int fl) { return (fl & STD_CONST) != 0; } } final class InstanceOfExpr extends Code { Code expr; String className; InstanceOfExpr(Code expr, JavaType what) { type = YetiType.BOOL_TYPE; this.expr = expr; className = what.className(); } void genIf(Ctx ctx, Label to, boolean ifTrue) { expr.gen(ctx); ctx.typeInsn(INSTANCEOF, className); ctx.jumpInsn(ifTrue ? IFNE : IFEQ, to); } void gen(Ctx ctx) { Label label = new Label(); genIf(ctx, label, false); ctx.genBoolean(label); } } final class JavaArrayRef extends Code implements CodeGen { Code value, index; YType elementType; int line; JavaArrayRef(YType _type, Code _value, Code _index, int _line) { type = JavaType.convertValueType(elementType = _type); value = _value; index = _index; line = _line; } private void _gen(Ctx ctx, Code store) { value.gen(ctx); ctx.typeInsn(CHECKCAST, JavaType.descriptionOf(value.type)); index.genInt(ctx, line, false); String resDescr = elementType.javaType == null ? JavaType.descriptionOf(elementType) : elementType.javaType.description; int insn = BALOAD; switch (resDescr.charAt(0)) { case 'C': insn = CALOAD; break; case 'D': insn = DALOAD; break; case 'F': insn = FALOAD; break; case 'I': insn = IALOAD; break; case 'J': insn = LALOAD; break; case 'S': insn = SALOAD; break; case 'L': resDescr = resDescr.substring(1, resDescr.length() - 1); case '[': insn = AALOAD; break; } if (store != null) { insn += 33; JavaExpr.genValue(ctx, store, elementType, line); if (insn == AASTORE) ctx.typeInsn(CHECKCAST, resDescr); } ctx.insn(insn); if (insn == AALOAD) { ctx.forceType(resDescr); } } void gen(Ctx ctx) { _gen(ctx, null); JavaExpr.convertValue(ctx, elementType); } public void gen2(Ctx ctx, Code setValue, int line) { _gen(ctx, setValue); ctx.insn(ACONST_NULL); } Code assign(final Code setValue) { return new SimpleCode(this, setValue, null, 0); } } final class StrOp extends StaticRef implements Binder { final static Code NOP_CODE = new Code() { void gen(Ctx ctx) { } }; String method; String sig; YType argTypes[]; final class StrApply extends Apply { StrApply prev; StrApply(Code arg, YType type, StrApply prev, int line) { super(type, NOP_CODE, arg, line); this.prev = prev; } Code apply(Code arg, YType res, int line) { return new StrApply(arg, res, this, line); } void genApply(Ctx ctx) { super.gen(ctx); } void gen(Ctx ctx) { genIf(ctx, null, false); } void genIf(Ctx ctx, Label to, boolean ifTrue) { List argv = new ArrayList(); for (StrApply a = this; a != null; a = a.prev) { argv.add(a); } if (argv.size() != argTypes.length) { StrOp.this.gen(ctx); for (int i = argv.size(); --i >= 0;) ((StrApply) argv.get(i)).genApply(ctx); return; } ((StrApply) argv.get(argv.size() - 1)).arg.gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "java/lang/String"); for (int i = 0, last = argv.size() - 2; i <= last; ++i) { StrApply a = (StrApply) argv.get(last - i); if (a.arg.type.deref().type == YetiType.STR) { a.arg.gen(ctx); ctx.typeInsn(CHECKCAST, "java/lang/String"); } else { JavaExpr.convertedArg(ctx, a.arg, argTypes[i], a.line); } } ctx.visitLine(line); ctx.methodInsn(INVOKEVIRTUAL, "java/lang/String", method, sig); if (to != null) { // really genIf ctx.jumpInsn(ifTrue ? IFNE : IFEQ, to); } else if (type.deref().type == YetiType.STR) { ctx.forceType("java/lang/String;"); } else { JavaExpr.convertValue(ctx, argTypes[argTypes.length - 1]); } } } StrOp(String fun, String method, String sig, YType type) { super(mangle(fun), type, false, 0); this.method = method; this.sig = sig; binder = this; argTypes = JavaTypeReader.parseSig1(1, sig); } public BindRef getRef(int line) { return this; } Code apply(final Code arg, final YType res, final int line) { return new StrApply(arg, res, null, line); } boolean flagop(int fl) { return (fl & STD_CONST) != 0; } } final class StrChar extends BinOpRef { private int line; StrChar(int line) { coreFun = "strChar"; type = YetiType.STR_TO_NUM_TO_STR; this.line = line; } void binGen(Ctx ctx, Code arg1, Code arg2) { arg1.gen(ctx); ctx.typeInsn(CHECKCAST, "java/lang/String"); arg2.genInt(ctx, line, false); ctx.insn(DUP); ctx.intConst(1); ctx.insn(IADD); ctx.methodInsn(INVOKEVIRTUAL, "java/lang/String", "substring", "(II)Ljava/lang/String;"); ctx.forceType("java/lang/String"); } }