// ex: se sts=4 sw=4 expandtab:
/*
* Yeti language compiler java bytecode generator for java foreign interface.
*
* Copyright (c) 2007-2012 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.*;
class JavaExpr extends Code {
Code object;
JavaType.Method method;
Code[] args;
int line;
JavaExpr(Code object, JavaType.Method method, Code[] args, int line) {
this.object = object;
this.method = method;
this.args = args;
this.line = line;
}
// convert to java
private static void convert(Ctx ctx, YType given, YType argType) {
given = given.deref();
argType = argType.deref();
String descr = argType.javaType == null
? "" : argType.javaType.description;
if (argType.type == YetiType.JAVA_ARRAY &&
given.type == YetiType.JAVA_ARRAY) {
ctx.typeInsn(CHECKCAST, JavaType.descriptionOf(argType));
return; // better than thinking, that array was given...
// still FIXME for a case of different arrays
}
if (given.type != YetiType.JAVA &&
(argType.type == YetiType.JAVA_ARRAY ||
argType.type == YetiType.JAVA &&
argType.javaType.isCollection())) {
YType t = argType.param.length != 0
? argType.param[0].deref() : null;
if (argType.type == YetiType.JAVA_ARRAY && t.javaType != null) {
if (t.javaType.description == "B") {
ctx.typeInsn(CHECKCAST, "yeti/lang/AList");
ctx.methodInsn(INVOKESTATIC, "yeti/lang/Core",
"bytes", "(Lyeti/lang/AList;)[B");
return;
}
if (t.javaType.description.charAt(0) == 'L') {
ctx.typeInsn(CHECKCAST, "yeti/lang/AList");
ctx.methodInsn(INVOKESTATIC, "yeti/lang/MList", "ofList",
"(Lyeti/lang/AList;)Lyeti/lang/MList;");
ctx.insn(DUP);
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/MList",
"length", "()J");
ctx.insn(L2I);
new NewArrayExpr(argType, null, 0).gen(ctx);
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/MList", "toArray",
"([Ljava/lang/Object;)[Ljava/lang/Object;");
descr = JavaType.descriptionOf(argType);
ctx.typeInsn(CHECKCAST, descr);
ctx.forceType(descr);
return;
}
}
Label retry = new Label(), end = new Label();
ctx.typeInsn(CHECKCAST, "yeti/lang/AIter"); // i
String tmpClass = descr != "Ljava/lang/Set;"
? "java/util/ArrayList" : "java/util/HashSet";
ctx.typeInsn(NEW, tmpClass); // ia
ctx.insn(DUP); // iaa
ctx.visitInit(tmpClass, "()V"); // ia
ctx.insn(SWAP); // ai
ctx.insn(DUP); // aii
ctx.jumpInsn(IFNULL, end); // ai
ctx.insn(DUP); // aii
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AIter",
"isEmpty", "()Z"); // aiz
ctx.jumpInsn(IFNE, end); // ai
ctx.visitLabel(retry);
ctx.insn(DUP2); // aiai
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AIter",
"first", "()Ljava/lang/Object;");
if (t != null && (t.type != YetiType.JAVA ||
t.javaType.description.length() > 1))
convert(ctx, given.param[0], argType.param[0]);
// aiav
ctx.methodInsn(INVOKEVIRTUAL, tmpClass,
"add", "(Ljava/lang/Object;)Z"); // aiz
ctx.insn(POP); // ai
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/AIter",
"next", "()Lyeti/lang/AIter;"); // ai
ctx.insn(DUP); // aii
ctx.jumpInsn(IFNONNULL, retry); // ai
ctx.visitLabel(end);
ctx.insn(POP); // a
if (argType.type != YetiType.JAVA_ARRAY)
return; // a - List/Set
String s = "";
YType argArrayType = argType;
while ((argType = argType.param[0]).type ==
YetiType.JAVA_ARRAY) {
s += "[";
argArrayType = argType;
}
String arrayPrefix = s;
if (s == "" && argType.javaType.description.length() != 1) {
s = argType.javaType.className();
} else {
s += argType.javaType.description;
}
ctx.insn(DUP); // aa
ctx.methodInsn(INVOKEVIRTUAL, tmpClass,
"size", "()I"); // an
if (t.type != YetiType.JAVA ||
(descr = t.javaType.description).length() != 1) {
ctx.typeInsn(ANEWARRAY, s); // aA
ctx.methodInsn(INVOKEVIRTUAL, tmpClass, "toArray",
"([Ljava/lang/Object;)[Ljava/lang/Object;");
if (!s.equals("java/lang/Object")) {
ctx.typeInsn(CHECKCAST,
arrayPrefix + "[" + argType.javaType.description);
}
return; // A - object array
}
// emulate a fucking for loop to fill primitive array
int index = ctx.localVarCount++;
Label next = new Label(), done = new Label();
ctx.insn(DUP); // ann
ctx.varInsn(ISTORE, index); // an
new NewArrayExpr(argArrayType, null, 0).gen(ctx);
ctx.insn(SWAP); // Aa
ctx.visitLabel(next);
ctx.varInsn(ILOAD, index); // Aan
ctx.jumpInsn(IFEQ, done); // Aa
ctx.visitIntInsn(IINC, index); // Aa --index
ctx.insn(DUP2); // AaAa
ctx.varInsn(ILOAD, index); // AaAan
ctx.methodInsn(INVOKEVIRTUAL, tmpClass,
"get", "(I)Ljava/lang/Object;"); // AaAv
if (descr == "Z") {
ctx.typeInsn(CHECKCAST, "java/lang/Boolean");
ctx.methodInsn(INVOKEVIRTUAL, "java/lang/Boolean",
"booleanValue", "()Z");
} else {
ctx.typeInsn(CHECKCAST, "yeti/lang/Num");
convertNum(ctx, descr);
}
ctx.varInsn(ILOAD, index); // AaAvn
int insn = BASTORE;
switch (argType.javaType.description.charAt(0)) {
case 'D': insn = DASTORE; break;
case 'F': insn = FASTORE; break;
case 'I': insn = IASTORE; break;
case 'J': insn = LASTORE; break;
case 'S': insn = SASTORE;
}
if (insn == DASTORE || insn == LASTORE) {
// AaAvvn actually - long and double is 2 entries
ctx.insn(DUP_X2); // AaAnvvn
ctx.insn(POP); // AaAnvv
} else {
ctx.insn(SWAP); // AaAnv
}
ctx.insn(insn); // Aa
ctx.jumpInsn(GOTO, next); // Aa
ctx.visitLabel(done);
ctx.insn(POP); // A
return; // A - primitive array
}
if (given.type == YetiType.STR) {
ctx.typeInsn(CHECKCAST, "java/lang/String");
ctx.insn(DUP);
ctx.fieldInsn(GETSTATIC, "yeti/lang/Core", "UNDEF_STR",
"Ljava/lang/String;");
Label defined = new Label();
ctx.jumpInsn(IF_ACMPNE, defined);
ctx.insn(POP);
ctx.insn(ACONST_NULL);
ctx.visitLabel(defined);
return;
}
if (given.type != YetiType.NUM ||
descr == "Ljava/lang/Object;" ||
descr == "Ljava/lang/Number;") {
if (descr != "Ljava/lang/Object;") {
ctx.typeInsn(CHECKCAST, argType.javaType.className());
}
return;
}
// Convert numbers...
ctx.typeInsn(CHECKCAST, "yeti/lang/Num");
if (descr == "Ljava/math/BigInteger;") {
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num",
"toBigInteger", "()Ljava/math/BigInteger;");
return;
}
if (descr == "Ljava/math/BigDecimal;") {
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num",
"toBigDecimal", "()Ljava/math/BigDecimal;");
return;
}
String newInstr = null;
if (descr.startsWith("Ljava/lang/")) {
newInstr = argType.javaType.className();
ctx.typeInsn(NEW, newInstr);
ctx.insn(DUP_X1);
ctx.insn(SWAP);
descr = descr == "Ljava/lang/Long;" ? "J" : descr.substring(11, 12);
}
convertNum(ctx, descr);
if (newInstr != null)
ctx.visitInit(newInstr, "(" + descr + ")V");
}
private static void convertNum(Ctx ctx, String descr) {
String method = null;
switch (descr.charAt(0)) {
case 'B': method = "byteValue"; break;
case 'D': method = "doubleValue"; break;
case 'F': method = "floatValue"; break;
case 'I': method = "intValue"; break;
case 'L': return;
case 'J': method = "longValue"; break;
case 'S': method = "shortValue"; break;
}
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num",
method, "()" + descr);
}
// MethodCall overrides it
void visitInvoke(Ctx ctx, int invokeInsn) {
ctx.methodInsn(invokeInsn, method.classType.javaType.className(),
method.name, method.descr(null));
}
void genCall(Ctx ctx, BindRef[] extraArgs, int invokeInsn) {
for (int i = 0; i < args.length; ++i) {
convertedArg(ctx, args[i], method.arguments[i], line);
}
if (extraArgs != null) {
for (int i = 0; i < extraArgs.length; ++i) {
BindRef arg = extraArgs[i];
CaptureWrapper cw = arg.capture();
if (cw == null) {
arg.gen(ctx);
ctx.captureCast(arg.captureType());
} else {
cw.genPreGet(ctx);
}
}
}
ctx.visitLine(line);
visitInvoke(ctx, invokeInsn);
JavaType jt = method.returnType.javaType;
if (jt != null && jt.description.charAt(0) == 'L')
ctx.forceType(jt.className());
}
static void convertedArg(Ctx ctx, Code arg, YType argType, int line) {
argType = argType.deref();
if (argType.type == YetiType.JAVA) {
// integer arguments can be directly generated
String desc = desc = argType.javaType.description;
if (desc == "I" || desc == "J") {
arg.genInt(ctx, line, desc == "J");
return;
}
}
if (genRawArg(ctx, arg, argType, line))
convert(ctx, arg.type, argType);
else if (argType.type == YetiType.STR)
convertValue(ctx, arg.type.deref()); // for as cast
}
private static boolean genRawArg(Ctx ctx, Code arg,
YType argType, int line) {
YType given = arg.type.deref();
String descr =
argType.javaType == null ? null : argType.javaType.description;
if (descr == "Z") {
// boolean
Label end = new Label(), lie = new Label();
arg.genIf(ctx, lie, false);
ctx.intConst(1);
ctx.jumpInsn(GOTO, end);
ctx.visitLabel(lie);
ctx.intConst(0);
ctx.visitLabel(end);
return false;
}
arg.gen(ctx);
if (given.type == YetiType.UNIT) {
if (!(arg instanceof UnitConstant)) {
ctx.insn(POP);
ctx.insn(ACONST_NULL);
}
return false;
}
ctx.visitLine(line);
if (descr == "C") {
ctx.typeInsn(CHECKCAST, "java/lang/String");
ctx.intConst(0);
ctx.methodInsn(INVOKEVIRTUAL,
"java/lang/String", "charAt", "(I)C");
return false;
}
if (argType.type == YetiType.JAVA_ARRAY &&
given.type == YetiType.STR) {
ctx.typeInsn(CHECKCAST, "java/lang/String");
ctx.methodInsn(INVOKEVIRTUAL,
"java/lang/String", "toCharArray", "()[C");
return false;
}
if (arg instanceof StringConstant || arg instanceof ConcatStrings)
return false;
// conversion from array to list
if (argType.type == YetiType.MAP && given.type == YetiType.JAVA_ARRAY) {
JavaType javaItem = given.param[0].javaType;
if (javaItem != null && javaItem.description.length() == 1) {
String arrayType = "[".concat(javaItem.description);
ctx.typeInsn(CHECKCAST, arrayType);
ctx.methodInsn(INVOKESTATIC, "yeti/lang/PArray",
"wrap", "(" + arrayType + ")Lyeti/lang/AList;");
return false;
}
Label isNull = new Label(), end = new Label();
ctx.typeInsn(CHECKCAST, "[Ljava/lang/Object;");
ctx.insn(DUP);
ctx.jumpInsn(IFNULL, isNull);
boolean toList = argType.param[1].deref().type == YetiType.NONE;
if (toList) {
ctx.insn(DUP);
ctx.insn(ARRAYLENGTH);
ctx.jumpInsn(IFEQ, isNull);
}
if (toList && argType.param[0].deref().type == YetiType.STR) {
// convert null's to undef_str's
ctx.methodInsn(INVOKESTATIC, "yeti/lang/MList", "ofStrArray",
"([Ljava/lang/Object;)Lyeti/lang/MList;");
} else {
ctx.typeInsn(NEW, "yeti/lang/MList");
ctx.insn(DUP_X1);
ctx.insn(SWAP);
ctx.visitInit("yeti/lang/MList", "([Ljava/lang/Object;)V");
}
ctx.jumpInsn(GOTO, end);
ctx.visitLabel(isNull);
ctx.insn(POP);
if (toList) {
ctx.insn(ACONST_NULL);
} else {
ctx.typeInsn(NEW, "yeti/lang/MList");
ctx.insn(DUP);
ctx.visitInit("yeti/lang/MList", "()V");
}
ctx.visitLabel(end);
return false;
}
return argType.type == YetiType.JAVA ||
argType.type == YetiType.JAVA_ARRAY;
}
static void genValue(Ctx ctx, Code arg, YType argType, int line) {
genRawArg(ctx, arg, argType, line);
if (arg.type.deref().type == YetiType.NUM &&
argType.javaType.description.length() == 1) {
ctx.typeInsn(CHECKCAST, "yeti/lang/Num");
convertNum(ctx, argType.javaType.description);
}
}
static void convertValue(Ctx ctx, YType t) {
if (t.type != YetiType.JAVA) {
return; // array, no automatic conversions
}
String descr = t.javaType.description;
if (descr == "V") {
ctx.insn(ACONST_NULL);
} else if (descr == "Ljava/lang/String;") {
Label nonnull = new Label();
// checkcast to not lie later the type with ctx.fieldInsn
ctx.typeInsn(CHECKCAST, "java/lang/String");
ctx.insn(DUP);
ctx.jumpInsn(IFNONNULL, nonnull);
ctx.insn(POP);
ctx.fieldInsn(GETSTATIC, "yeti/lang/Core", "UNDEF_STR",
"Ljava/lang/String;");
ctx.visitLabel(nonnull);
} else if (descr == "Z") {
Label skip = new Label(), end = new Label();
ctx.jumpInsn(IFEQ, skip);
ctx.fieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE",
"Ljava/lang/Boolean;");
ctx.jumpInsn(GOTO, end);
ctx.visitLabel(skip);
ctx.fieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE",
"Ljava/lang/Boolean;");
ctx.visitLabel(end);
} else if (descr == "B" || descr == "S" ||
descr == "I" || descr == "J") {
if (descr == "B") {
ctx.intConst(0xff);
ctx.insn(IAND);
}
ctx.typeInsn(NEW, "yeti/lang/IntNum");
if (descr == "J") {
ctx.insn(DUP_X2);
ctx.insn(DUP_X2);
ctx.insn(POP);
} else {
ctx.insn(DUP_X1);
ctx.insn(SWAP);
}
ctx.visitInit("yeti/lang/IntNum",
descr == "J" ? "(J)V" : "(I)V");
ctx.forceType("yeti/lang/Num");
} else if (descr == "D" || descr == "F") {
ctx.typeInsn(NEW, "yeti/lang/FloatNum");
if (descr == "F") {
ctx.insn(DUP_X1);
ctx.insn(SWAP);
ctx.insn(F2D);
} else {
ctx.insn(DUP_X2);
ctx.insn(DUP_X2);
ctx.insn(POP);
}
ctx.visitInit("yeti/lang/FloatNum", "(D)V");
ctx.forceType("yeti/lang/Num");
} else if (descr == "C") {
ctx.methodInsn(INVOKESTATIC, "java/lang/String",
"valueOf", "(C)Ljava/lang/String;");
ctx.forceType("java/lang/String");
}
}
void gen(Ctx ctx) {
throw new UnsupportedOperationException();
}
}