vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 10 Dec 2012 12:03:22 +0100
changeset 297 a20721a10717
parent 272 a6a23aa7a546
child 292 fc3f6ea5e246
child 306 f36b3c273de6
permissions -rw-r--r--
Cache the compiled code per class, not globally
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.vm4brwsr;
    19 
    20 import java.io.IOException;
    21 import java.io.InputStream;
    22 import org.apidesign.javap.AnnotationParser;
    23 import org.apidesign.javap.ClassData;
    24 import org.apidesign.javap.FieldData;
    25 import org.apidesign.javap.MethodData;
    26 import static org.apidesign.javap.RuntimeConstants.*;
    27 
    28 /** Translator of the code inside class files to JavaScript.
    29  *
    30  * @author Jaroslav Tulach <jtulach@netbeans.org>
    31  */
    32 public abstract class ByteCodeToJavaScript {
    33     private ClassData jc;
    34     final Appendable out;
    35 
    36     protected ByteCodeToJavaScript(Appendable out) {
    37         this.out = out;
    38     }
    39     
    40     /* Collects additional required resources.
    41      * 
    42      * @param internalClassName classes that were referenced and should be loaded in order the
    43      *   generated JavaScript code works properly. The names are in internal 
    44      *   JVM form so String is <code>java/lang/String</code>. 
    45      */
    46     protected abstract boolean requireReference(String internalClassName);
    47     
    48     /*
    49      * @param resourcePath name of resources to read
    50      */
    51     protected abstract void requireScript(String resourcePath);
    52     
    53     /** Allows subclasses to redefine what field a function representing a
    54      * class gets assigned. By default it returns the suggested name followed
    55      * by <code>" = "</code>;
    56      * 
    57      * @param className suggested name of the class
    58      */
    59     protected String assignClass(String className) {
    60         return className + " = ";
    61     }
    62 
    63     /**
    64      * Converts a given class file to a JavaScript version.
    65      *
    66      * @param classFile input stream with code of the .class file
    67      * @return the initialization code for this class, if any. Otherwise <code>null</code>
    68      * 
    69      * @throws IOException if something goes wrong during read or write or translating
    70      */
    71     
    72     public String compile(InputStream classFile) throws IOException {
    73         this.jc = new ClassData(classFile);
    74         byte[] arrData = jc.findAnnotationData(true);
    75         String[] arr = findAnnotation(arrData, jc, 
    76             "org.apidesign.bck2brwsr.core.ExtraJavaScript", 
    77             "resource", "processByteCode"
    78         );
    79         if (arr != null) {
    80             requireScript(arr[0]);
    81             if ("0".equals(arr[1])) {
    82                 return null;
    83             }
    84         }
    85         String[] proto = findAnnotation(arrData, jc, 
    86             "org.apidesign.bck2brwsr.core.JavaScriptPrototype", 
    87             "container", "prototype"
    88         );
    89         StringArray toInitilize = new StringArray();
    90         final String className = className(jc);
    91         out.append("\n\n").append(assignClass(className));
    92         out.append("function CLS() {");
    93         out.append("\n  if (!CLS.prototype.$instOf_").append(className).append(") {");
    94         for (FieldData v : jc.getFields()) {
    95             if (v.isStatic()) {
    96                 out.append("\n  CLS.").append(v.getName()).append(initField(v));
    97             }
    98         }
    99         if (proto == null) {
   100             String sc = jc.getSuperClassName(); // with _
   101             out.append("\n    var pp = ").
   102                 append(sc.replace('/', '_')).append("(true);");
   103             out.append("\n    var p = CLS.prototype = pp;");
   104             out.append("\n    var c = p;");
   105             out.append("\n    var sprcls = pp.constructor.$class;");
   106         } else {
   107             out.append("\n    var p = CLS.prototype = ").append(proto[1]).append(";");
   108             out.append("\n    var c = ").append(proto[0]).append(";");
   109             out.append("\n    var sprcls = null;");
   110         }
   111         for (MethodData m : jc.getMethods()) {
   112             byte[] onlyArr = m.findAnnotationData(true);
   113             String[] only = findAnnotation(onlyArr, jc, 
   114                 "org.apidesign.bck2brwsr.core.JavaScriptOnly", 
   115                 "name", "value"
   116             );
   117             if (only != null) {
   118                 if (only[0] != null && only[1] != null) {
   119                     out.append("\n    p.").append(only[0]).append(" = ")
   120                         .append(only[1]).append(";");
   121                 }
   122                 continue;
   123             }
   124             String mn;
   125             if (m.isStatic()) {
   126                 mn = generateStaticMethod("\n    c.", m, toInitilize);
   127             } else {
   128                 mn = generateInstanceMethod("\n    c.", m);
   129             }
   130             byte[] runAnno = m.findAnnotationData(false);
   131             if (runAnno != null) {
   132                 out.append("\n    c.").append(mn).append(".anno = {");
   133                 generateAnno(jc, out, runAnno);
   134                 out.append("\n    };");
   135             }
   136         }
   137         out.append("\n    c.constructor = CLS;");
   138         out.append("\n    c.$instOf_").append(className).append(" = true;");
   139         for (String superInterface : jc.getSuperInterfaces()) {
   140             out.append("\n    c.$instOf_").append(superInterface.replace('/', '_')).append(" = true;");
   141         }
   142         out.append("\n    CLS.$class = java_lang_Class(true);");
   143         out.append("\n    CLS.$class.jvmName = '").append(jc.getClassName()).append("';");
   144         out.append("\n    CLS.$class.superclass = sprcls;");
   145         out.append("\n    CLS.$class.cnstr = CLS;");
   146         byte[] classAnno = jc.findAnnotationData(false);
   147         if (classAnno != null) {
   148             out.append("\n    CLS.$class.anno = {");
   149             generateAnno(jc, out, classAnno);
   150             out.append("\n    };");
   151         }
   152         out.append("\n  }");
   153         out.append("\n  if (arguments.length === 0) {");
   154         out.append("\n    if (!(this instanceof CLS)) {");
   155         out.append("\n      return new CLS();");
   156         out.append("\n    }");
   157         for (FieldData v : jc.getFields()) {
   158             byte[] onlyArr = v.findAnnotationData(true);
   159             String[] only = findAnnotation(onlyArr, jc, 
   160                 "org.apidesign.bck2brwsr.core.JavaScriptOnly", 
   161                 "name", "value"
   162             );
   163             if (only != null) {
   164                 if (only[0] != null && only[1] != null) {
   165                     out.append("\n    p.").append(only[0]).append(" = ")
   166                         .append(only[1]).append(";");
   167                 }
   168                 continue;
   169             }
   170             if (!v.isStatic()) {
   171                 out.append("\n    this.fld_").
   172                     append(v.getName()).append(initField(v));
   173             }
   174         }
   175         out.append("\n    return this;");
   176         out.append("\n  }");
   177         out.append("\n  return arguments[0] ? new CLS() : CLS.prototype;");
   178         out.append("\n}");
   179         StringBuilder sb = new StringBuilder();
   180         for (String init : toInitilize.toArray()) {
   181             sb.append("\n").append(init).append("();");
   182         }
   183         return sb.toString();
   184     }
   185     private String generateStaticMethod(String prefix, MethodData m, StringArray toInitilize) throws IOException {
   186         String jsb = javaScriptBody(prefix, m, true);
   187         if (jsb != null) {
   188             return jsb;
   189         }
   190         StringBuilder argsCnt = new StringBuilder();
   191         final String mn = findMethodName(m, argsCnt);
   192         out.append(prefix).append(mn).append(" = function");
   193         if (mn.equals("class__V")) {
   194             toInitilize.add(className(jc) + "(false)." + mn);
   195         }
   196         out.append('(');
   197         String space = "";
   198         for (int index = 0, i = 0; i < argsCnt.length(); i++) {
   199             out.append(space);
   200             out.append("arg").append(String.valueOf(index));
   201             space = ",";
   202             final String desc = null;// XXX findDescriptor(args.get(i).getDescriptor());
   203             if (argsCnt.charAt(i) == '1') {
   204                 index += 2;
   205             } else {
   206                 index++;
   207             }
   208         }
   209         out.append(") {").append("\n");
   210         final byte[] code = m.getCode();
   211         if (code != null) {
   212             int len = m.getMaxLocals();
   213             for (int index = argsCnt.length(), i = argsCnt.length(); i < len; i++) {
   214                 out.append("  var ");
   215                 out.append("arg").append(String.valueOf(i)).append(";\n");
   216             }
   217             out.append("  var s = new Array();\n");
   218             produceCode(code);
   219         } else {
   220             out.append("  throw 'no code found for ").append(m.getInternalSig()).append("';\n");
   221         }
   222         out.append("};");
   223         return mn;
   224     }
   225     
   226     private String generateInstanceMethod(String prefix, MethodData m) throws IOException {
   227         String jsb = javaScriptBody(prefix, m, false);
   228         if (jsb != null) {
   229             return jsb;
   230         }
   231         StringBuilder argsCnt = new StringBuilder();
   232         final String mn = findMethodName(m, argsCnt);
   233         out.append(prefix).append(mn).append(" = function");
   234         out.append("(arg0");
   235         String space = ",";
   236         for (int index = 1, i = 0; i < argsCnt.length(); i++) {
   237             out.append(space);
   238             out.append("arg").append(String.valueOf(index));
   239             if (argsCnt.charAt(i) == '1') {
   240                 index += 2;
   241             } else {
   242                 index++;
   243             }
   244         }
   245         out.append(") {").append("\n");
   246         final byte[] code = m.getCode();
   247         if (code != null) {
   248             int len = m.getMaxLocals();
   249             for (int index = argsCnt.length(), i = argsCnt.length(); i < len; i++) {
   250                 out.append("  var ");
   251                 out.append("arg").append(String.valueOf(i + 1)).append(";\n");
   252             }
   253             out.append(";\n  var s = new Array();\n");
   254             produceCode(code);
   255         } else {
   256             out.append("  throw 'no code found for ").append(m.getInternalSig()).append("';\n");
   257         }
   258         out.append("};");
   259         return mn;
   260     }
   261 
   262     private void produceCode(byte[] byteCodes) throws IOException {
   263         out.append("\n  var gt = 0;\n  for(;;) switch(gt) {\n");
   264         for (int i = 0; i < byteCodes.length; i++) {
   265             int prev = i;
   266             out.append("    case " + i).append(": ");
   267             final int c = readByte(byteCodes, i);
   268             switch (c) {
   269                 case opc_aload_0:
   270                 case opc_iload_0:
   271                 case opc_lload_0:
   272                 case opc_fload_0:
   273                 case opc_dload_0:
   274                     out.append("s.push(arg0);");
   275                     break;
   276                 case opc_aload_1:
   277                 case opc_iload_1:
   278                 case opc_lload_1:
   279                 case opc_fload_1:
   280                 case opc_dload_1:
   281                     out.append("s.push(arg1);");
   282                     break;
   283                 case opc_aload_2:
   284                 case opc_iload_2:
   285                 case opc_lload_2:
   286                 case opc_fload_2:
   287                 case opc_dload_2:
   288                     out.append("s.push(arg2);");
   289                     break;
   290                 case opc_aload_3:
   291                 case opc_iload_3:
   292                 case opc_lload_3:
   293                 case opc_fload_3:
   294                 case opc_dload_3:
   295                     out.append("s.push(arg3);");
   296                     break;
   297                 case opc_iload:
   298                 case opc_lload:
   299                 case opc_fload:
   300                 case opc_dload:
   301                 case opc_aload: {
   302                     final int indx = readByte(byteCodes, ++i);
   303                     out.append("s.push(arg").append(indx + ");");
   304                     break;
   305                 }
   306                 case opc_istore:
   307                 case opc_lstore:
   308                 case opc_fstore:
   309                 case opc_dstore:
   310                 case opc_astore: {
   311                     final int indx = readByte(byteCodes, ++i);
   312                     out.append("arg" + indx).append(" = s.pop();");
   313                     break;
   314                 }
   315                 case opc_astore_0:
   316                 case opc_istore_0:
   317                 case opc_lstore_0:
   318                 case opc_fstore_0:
   319                 case opc_dstore_0:
   320                     out.append("arg0 = s.pop();");
   321                     break;
   322                 case opc_astore_1:
   323                 case opc_istore_1:
   324                 case opc_lstore_1:
   325                 case opc_fstore_1:
   326                 case opc_dstore_1:
   327                     out.append("arg1 = s.pop();");
   328                     break;
   329                 case opc_astore_2:
   330                 case opc_istore_2:
   331                 case opc_lstore_2:
   332                 case opc_fstore_2:
   333                 case opc_dstore_2:
   334                     out.append("arg2 = s.pop();");
   335                     break;
   336                 case opc_astore_3:
   337                 case opc_istore_3:
   338                 case opc_lstore_3:
   339                 case opc_fstore_3:
   340                 case opc_dstore_3:
   341                     out.append("arg3 = s.pop();");
   342                     break;
   343                 case opc_iadd:
   344                 case opc_ladd:
   345                 case opc_fadd:
   346                 case opc_dadd:
   347                     out.append("s.push(s.pop() + s.pop());");
   348                     break;
   349                 case opc_isub:
   350                 case opc_lsub:
   351                 case opc_fsub:
   352                 case opc_dsub:
   353                     out.append("{ var tmp = s.pop(); s.push(s.pop() - tmp); }");
   354                     break;
   355                 case opc_imul:
   356                 case opc_lmul:
   357                 case opc_fmul:
   358                 case opc_dmul:
   359                     out.append("s.push(s.pop() * s.pop());");
   360                     break;
   361                 case opc_idiv:
   362                 case opc_ldiv:
   363                     out.append("{ var tmp = s.pop(); s.push(Math.floor(s.pop() / tmp)); }");
   364                     break;
   365                 case opc_fdiv:
   366                 case opc_ddiv:
   367                     out.append("{ var tmp = s.pop(); s.push(s.pop() / tmp); }");
   368                     break;
   369                 case opc_irem:
   370                 case opc_lrem:
   371                 case opc_frem:
   372                 case opc_drem:
   373                     out.append("{ var d = s.pop(); s.push(s.pop() % d); }");
   374                     break;
   375                 case opc_iand:
   376                 case opc_land:
   377                     out.append("s.push(s.pop() & s.pop());");
   378                     break;
   379                 case opc_ior:
   380                 case opc_lor:
   381                     out.append("s.push(s.pop() | s.pop());");
   382                     break;
   383                 case opc_ixor:
   384                 case opc_lxor:
   385                     out.append("s.push(s.pop() ^ s.pop());");
   386                     break;
   387                 case opc_ineg:
   388                 case opc_lneg:
   389                 case opc_fneg:
   390                 case opc_dneg:
   391                     out.append("s.push(- s.pop());");
   392                     break;
   393                 case opc_ishl:
   394                 case opc_lshl:
   395                     out.append("{ var v = s.pop(); s.push(s.pop() << v); }");
   396                     break;
   397                 case opc_ishr:
   398                 case opc_lshr:
   399                     out.append("{ var v = s.pop(); s.push(s.pop() >> v); }");
   400                     break;
   401                 case opc_iushr:
   402                 case opc_lushr:
   403                     out.append("{ var v = s.pop(); s.push(s.pop() >>> v); }");
   404                     break;
   405                 case opc_iinc: {
   406                     final int varIndx = readByte(byteCodes, ++i);
   407                     final int incrBy = byteCodes[++i];
   408                     if (incrBy == 1) {
   409                         out.append("arg" + varIndx).append("++;");
   410                     } else {
   411                         out.append("arg" + varIndx).append(" += " + incrBy).append(";");
   412                     }
   413                     break;
   414                 }
   415                 case opc_return:
   416                     out.append("return;");
   417                     break;
   418                 case opc_ireturn:
   419                 case opc_lreturn:
   420                 case opc_freturn:
   421                 case opc_dreturn:
   422                 case opc_areturn:
   423                     out.append("return s.pop();");
   424                     break;
   425                 case opc_i2l:
   426                 case opc_i2f:
   427                 case opc_i2d:
   428                 case opc_l2i:
   429                     // max int check?
   430                 case opc_l2f:
   431                 case opc_l2d:
   432                 case opc_f2d:
   433                 case opc_d2f:
   434                     out.append("/* number conversion */");
   435                     break;
   436                 case opc_f2i:
   437                 case opc_f2l:
   438                 case opc_d2i:
   439                 case opc_d2l:
   440                     out.append("s.push(Math.floor(s.pop()));");
   441                     break;
   442                 case opc_i2b:
   443                 case opc_i2c:
   444                 case opc_i2s:
   445                     out.append("/* number conversion */");
   446                     break;
   447                 case opc_aconst_null:
   448                     out.append("s.push(null);");
   449                     break;
   450                 case opc_iconst_m1:
   451                     out.append("s.push(-1);");
   452                     break;
   453                 case opc_iconst_0:
   454                 case opc_dconst_0:
   455                 case opc_lconst_0:
   456                 case opc_fconst_0:
   457                     out.append("s.push(0);");
   458                     break;
   459                 case opc_iconst_1:
   460                 case opc_lconst_1:
   461                 case opc_fconst_1:
   462                 case opc_dconst_1:
   463                     out.append("s.push(1);");
   464                     break;
   465                 case opc_iconst_2:
   466                 case opc_fconst_2:
   467                     out.append("s.push(2);");
   468                     break;
   469                 case opc_iconst_3:
   470                     out.append("s.push(3);");
   471                     break;
   472                 case opc_iconst_4:
   473                     out.append("s.push(4);");
   474                     break;
   475                 case opc_iconst_5:
   476                     out.append("s.push(5);");
   477                     break;
   478                 case opc_ldc: {
   479                     int indx = readByte(byteCodes, ++i);
   480                     String v = encodeConstant(indx);
   481                     out.append("s.push(").append(v).append(");");
   482                     break;
   483                 }
   484                 case opc_ldc_w:
   485                 case opc_ldc2_w: {
   486                     int indx = readIntArg(byteCodes, i);
   487                     i += 2;
   488                     String v = encodeConstant(indx);
   489                     out.append("s.push(").append(v).append(");");
   490                     break;
   491                 }
   492                 case opc_lcmp:
   493                 case opc_fcmpl:
   494                 case opc_fcmpg:
   495                 case opc_dcmpl:
   496                 case opc_dcmpg: {
   497                     out.append("{ var delta = s.pop() - s.pop(); s.push(delta < 0 ?-1 : (delta == 0 ? 0 : 1)); }");
   498                     break;
   499                 }
   500                 case opc_if_acmpeq:
   501                     i = generateIf(byteCodes, i, "===");
   502                     break;
   503                 case opc_if_acmpne:
   504                     i = generateIf(byteCodes, i, "!=");
   505                     break;
   506                 case opc_if_icmpeq: {
   507                     i = generateIf(byteCodes, i, "==");
   508                     break;
   509                 }
   510                 case opc_ifeq: {
   511                     int indx = i + readIntArg(byteCodes, i);
   512                     out.append("if (s.pop() == 0) { gt = " + indx);
   513                     out.append("; continue; }");
   514                     i += 2;
   515                     break;
   516                 }
   517                 case opc_ifne: {
   518                     int indx = i + readIntArg(byteCodes, i);
   519                     out.append("if (s.pop() != 0) { gt = " + indx);
   520                     out.append("; continue; }");
   521                     i += 2;
   522                     break;
   523                 }
   524                 case opc_iflt: {
   525                     int indx = i + readIntArg(byteCodes, i);
   526                     out.append("if (s.pop() < 0) { gt = " + indx);
   527                     out.append("; continue; }");
   528                     i += 2;
   529                     break;
   530                 }
   531                 case opc_ifle: {
   532                     int indx = i + readIntArg(byteCodes, i);
   533                     out.append("if (s.pop() <= 0) { gt = " + indx);
   534                     out.append("; continue; }");
   535                     i += 2;
   536                     break;
   537                 }
   538                 case opc_ifgt: {
   539                     int indx = i + readIntArg(byteCodes, i);
   540                     out.append("if (s.pop() > 0) { gt = " + indx);
   541                     out.append("; continue; }");
   542                     i += 2;
   543                     break;
   544                 }
   545                 case opc_ifge: {
   546                     int indx = i + readIntArg(byteCodes, i);
   547                     out.append("if (s.pop() >= 0) { gt = " + indx);
   548                     out.append("; continue; }");
   549                     i += 2;
   550                     break;
   551                 }
   552                 case opc_ifnonnull: {
   553                     int indx = i + readIntArg(byteCodes, i);
   554                     out.append("if (s.pop() !== null) { gt = " + indx);
   555                     out.append("; continue; }");
   556                     i += 2;
   557                     break;
   558                 }
   559                 case opc_ifnull: {
   560                     int indx = i + readIntArg(byteCodes, i);
   561                     out.append("if (s.pop() === null) { gt = " + indx);
   562                     out.append("; continue; }");
   563                     i += 2;
   564                     break;
   565                 }
   566                 case opc_if_icmpne:
   567                     i = generateIf(byteCodes, i, "!=");
   568                     break;
   569                 case opc_if_icmplt:
   570                     i = generateIf(byteCodes, i, ">");
   571                     break;
   572                 case opc_if_icmple:
   573                     i = generateIf(byteCodes, i, ">=");
   574                     break;
   575                 case opc_if_icmpgt:
   576                     i = generateIf(byteCodes, i, "<");
   577                     break;
   578                 case opc_if_icmpge:
   579                     i = generateIf(byteCodes, i, "<=");
   580                     break;
   581                 case opc_goto: {
   582                     int indx = i + readIntArg(byteCodes, i);
   583                     out.append("gt = " + indx).append("; continue;");
   584                     i += 2;
   585                     break;
   586                 }
   587                 case opc_lookupswitch: {
   588                     int table = i / 4 * 4 + 4;
   589                     int dflt = i + readInt4(byteCodes, table);
   590                     table += 4;
   591                     int n = readInt4(byteCodes, table);
   592                     table += 4;
   593                     out.append("switch (s.pop()) {\n");
   594                     while (n-- > 0) {
   595                         int cnstnt = readInt4(byteCodes, table);
   596                         table += 4;
   597                         int offset = i + readInt4(byteCodes, table);
   598                         table += 4;
   599                         out.append("  case " + cnstnt).append(": gt = " + offset).append("; continue;\n");
   600                     }
   601                     out.append("  default: gt = " + dflt).append("; continue;\n}");
   602                     i = table - 1;
   603                     break;
   604                 }
   605                 case opc_tableswitch: {
   606                     int table = i / 4 * 4 + 4;
   607                     int dflt = i + readInt4(byteCodes, table);
   608                     table += 4;
   609                     int low = readInt4(byteCodes, table);
   610                     table += 4;
   611                     int high = readInt4(byteCodes, table);
   612                     table += 4;
   613                     out.append("switch (s.pop()) {\n");
   614                     while (low <= high) {
   615                         int offset = i + readInt4(byteCodes, table);
   616                         table += 4;
   617                         out.append("  case " + low).append(": gt = " + offset).append("; continue;\n");
   618                         low++;
   619                     }
   620                     out.append("  default: gt = " + dflt).append("; continue;\n}");
   621                     i = table - 1;
   622                     break;
   623                 }
   624                 case opc_invokeinterface: {
   625                     i = invokeVirtualMethod(byteCodes, i) + 2;
   626                     break;
   627                 }
   628                 case opc_invokevirtual:
   629                     i = invokeVirtualMethod(byteCodes, i);
   630                     break;
   631                 case opc_invokespecial:
   632                     i = invokeStaticMethod(byteCodes, i, false);
   633                     break;
   634                 case opc_invokestatic:
   635                     i = invokeStaticMethod(byteCodes, i, true);
   636                     break;
   637                 case opc_new: {
   638                     int indx = readIntArg(byteCodes, i);
   639                     String ci = jc.getClassName(indx);
   640                     out.append("s.push(new ");
   641                     out.append(ci.replace('/','_'));
   642                     out.append("());");
   643                     addReference(ci);
   644                     i += 2;
   645                     break;
   646                 }
   647                 case opc_newarray: {
   648                     int type = byteCodes[i++];
   649                     out.append("s.push(new Array(s.pop()).fillNulls());");
   650                     break;
   651                 }
   652                 case opc_anewarray: {
   653                     i += 2; // skip type of array
   654                     out.append("s.push(new Array(s.pop()).fillNulls());");
   655                     break;
   656                 }
   657                 case opc_multianewarray: {
   658                     i += 2;
   659                     int dim = readByte(byteCodes, ++i);
   660                     out.append("{ var a0 = new Array(s.pop()).fillNulls();");
   661                     for (int d = 1; d < dim; d++) {
   662                         out.append("\n  var l" + d).append(" = s.pop();");
   663                         out.append("\n  for (var i" + d).append (" = 0; i" + d).
   664                             append(" < a" + (d - 1)).
   665                             append(".length; i" + d).append("++) {");
   666                         out.append("\n    var a" + d).
   667                             append (" = new Array(l" + d).append(").fillNulls();");
   668                         out.append("\n    a" + (d - 1)).append("[i" + d).append("] = a" + d).
   669                             append(";");
   670                     }
   671                     for (int d = 1; d < dim; d++) {
   672                         out.append("\n  }");
   673                     }
   674                     out.append("\ns.push(a0); }");
   675                     break;
   676                 }
   677                 case opc_arraylength:
   678                     out.append("s.push(s.pop().length);");
   679                     break;
   680                 case opc_iastore:
   681                 case opc_lastore:
   682                 case opc_fastore:
   683                 case opc_dastore:
   684                 case opc_aastore:
   685                 case opc_bastore:
   686                 case opc_castore:
   687                 case opc_sastore: {
   688                     out.append("{ var value = s.pop(); var indx = s.pop(); s.pop()[indx] = value; }");
   689                     break;
   690                 }
   691                 case opc_iaload:
   692                 case opc_laload:
   693                 case opc_faload:
   694                 case opc_daload:
   695                 case opc_aaload:
   696                 case opc_baload:
   697                 case opc_caload:
   698                 case opc_saload: {
   699                     out.append("{ var indx = s.pop(); s.push(s.pop()[indx]); }");
   700                     break;
   701                 }
   702                 case opc_pop2:
   703                     out.append("s.pop();");
   704                 case opc_pop:
   705                     out.append("s.pop();");
   706                     break;
   707                 case opc_dup:
   708                     out.append("s.push(s[s.length - 1]);");
   709                     break;
   710                 case opc_dup_x1:
   711                     out.append("{ var v1 = s.pop(); var v2 = s.pop(); s.push(v1); s.push(v2); s.push(v1); }");
   712                     break;
   713                 case opc_dup_x2:
   714                     out.append("{ var v1 = s.pop(); var v2 = s.pop(); var v3 = s.pop(); s.push(v1); s.push(v3); s.push(v2); s.push(v1); }");
   715                     break;
   716                 case opc_bipush:
   717                     out.append("s.push(" + byteCodes[++i] + ");");
   718                     break;
   719                 case opc_sipush:
   720                     out.append("s.push(" + readIntArg(byteCodes, i) + ");");
   721                     i += 2;
   722                     break;
   723                 case opc_getfield: {
   724                     int indx = readIntArg(byteCodes, i);
   725                     String[] fi = jc.getFieldInfoName(indx);
   726                     out.append("s.push(s.pop().fld_").
   727                         append(fi[1]).append(");");
   728                     i += 2;
   729                     break;
   730                 }
   731                 case opc_getstatic: {
   732                     int indx = readIntArg(byteCodes, i);
   733                     String[] fi = jc.getFieldInfoName(indx);
   734                     out.append("s.push(").append(fi[0].replace('/', '_'));
   735                     out.append('.').append(fi[1]).append(");");
   736                     i += 2;
   737                     addReference(fi[0]);
   738                     break;
   739                 }
   740                 case opc_putstatic: {
   741                     int indx = readIntArg(byteCodes, i);
   742                     String[] fi = jc.getFieldInfoName(indx);
   743                     out.append(fi[0].replace('/', '_'));
   744                     out.append('.').append(fi[1]).append(" = s.pop();");
   745                     i += 2;
   746                     addReference(fi[0]);
   747                     break;
   748                 }
   749                 case opc_putfield: {
   750                     int indx = readIntArg(byteCodes, i);
   751                     String[] fi = jc.getFieldInfoName(indx);
   752                     out.append("{ var v = s.pop(); s.pop().fld_")
   753                        .append(fi[1]).append(" = v; }");
   754                     i += 2;
   755                     break;
   756                 }
   757                 case opc_checkcast: {
   758                     int indx = readIntArg(byteCodes, i);
   759                     final String type = jc.getClassName(indx);
   760                     if (!type.startsWith("[")) {
   761                         // no way to check arrays right now
   762                         out.append("if(s[s.length - 1] !== null && !s[s.length - 1].$instOf_")
   763                            .append(type.replace('/', '_'))
   764                            .append(") throw {};"); // XXX proper exception
   765                     }
   766                     i += 2;
   767                     break;
   768                 }
   769                 case opc_instanceof: {
   770                     int indx = readIntArg(byteCodes, i);
   771                     final String type = jc.getClassName(indx);
   772                     out.append("s.push(s.pop().$instOf_")
   773                        .append(type.replace('/', '_'))
   774                        .append(" ? 1 : 0);");
   775                     i += 2;
   776                     break;
   777                 }
   778                 case opc_athrow: {
   779                     out.append("{ var t = s.pop(); s = new Array(1); s[0] = t; throw t; }");
   780                     break;
   781                 }
   782                 default: {
   783                     out.append("throw 'unknown bytecode " + c + "';");
   784                 }
   785                     
   786             }
   787             out.append(" //");
   788             for (int j = prev; j <= i; j++) {
   789                 out.append(" ");
   790                 final int cc = readByte(byteCodes, j);
   791                 out.append(Integer.toString(cc));
   792             }
   793             out.append("\n");
   794         }
   795         out.append("  }\n");
   796     }
   797 
   798     private int generateIf(byte[] byteCodes, int i, final String test) throws IOException {
   799         int indx = i + readIntArg(byteCodes, i);
   800         out.append("if (s.pop() ").append(test).append(" s.pop()) { gt = " + indx);
   801         out.append("; continue; }");
   802         return i + 2;
   803     }
   804 
   805     private int readIntArg(byte[] byteCodes, int offsetInstruction) {
   806         final int indxHi = byteCodes[offsetInstruction + 1] << 8;
   807         final int indxLo = byteCodes[offsetInstruction + 2];
   808         return (indxHi & 0xffffff00) | (indxLo & 0xff);
   809     }
   810     private int readInt4(byte[] byteCodes, int offsetInstruction) {
   811         final int d = byteCodes[offsetInstruction + 0] << 24;
   812         final int c = byteCodes[offsetInstruction + 1] << 16;
   813         final int b = byteCodes[offsetInstruction + 2] << 8;
   814         final int a = byteCodes[offsetInstruction + 3];
   815         return (d & 0xff000000) | (c & 0xff0000) | (b & 0xff00) | (a & 0xff);
   816     }
   817     private int readByte(byte[] byteCodes, int offsetInstruction) {
   818         return (byteCodes[offsetInstruction] + 256) % 256;
   819     }
   820     
   821     private static void countArgs(String descriptor, boolean[] hasReturnType, StringBuilder sig, StringBuilder cnt) {
   822         int i = 0;
   823         Boolean count = null;
   824         boolean array = false;
   825         sig.append("__");
   826         int firstPos = sig.length();
   827         while (i < descriptor.length()) {
   828             char ch = descriptor.charAt(i++);
   829             switch (ch) {
   830                 case '(':
   831                     count = true;
   832                     continue;
   833                 case ')':
   834                     count = false;
   835                     continue;
   836                 case 'B': 
   837                 case 'C': 
   838                 case 'D': 
   839                 case 'F': 
   840                 case 'I': 
   841                 case 'J': 
   842                 case 'S': 
   843                 case 'Z': 
   844                     if (count) {
   845                         if (array) {
   846                             sig.append("_3");
   847                         }
   848                         sig.append(ch);
   849                         if (ch == 'J' || ch == 'D') {
   850                             cnt.append('1');
   851                         } else {
   852                             cnt.append('0');
   853                         }
   854                     } else {
   855                         hasReturnType[0] = true;
   856                         sig.insert(firstPos, ch);
   857                         if (array) {
   858                             sig.insert(firstPos, "_3");
   859                         }
   860                     }
   861                     array = false;
   862                     continue;
   863                 case 'V': 
   864                     assert !count;
   865                     hasReturnType[0] = false;
   866                     sig.insert(firstPos, 'V');
   867                     continue;
   868                 case 'L':
   869                     int next = descriptor.indexOf(';', i);
   870                     String realSig = mangleSig(descriptor, i - 1, next + 1);
   871                     if (count) {
   872                         if (array) {
   873                             sig.append("_3");
   874                         }
   875                         sig.append(realSig);
   876                         cnt.append('0');
   877                     } else {
   878                         sig.insert(firstPos, realSig);
   879                         if (array) {
   880                             sig.insert(firstPos, "_3");
   881                         }
   882                         hasReturnType[0] = true;
   883                     }
   884                     i = next + 1;
   885                     continue;
   886                 case '[':
   887                     array = true;
   888                     continue;
   889                 default:
   890                     throw new IllegalStateException("Invalid char: " + ch);
   891             }
   892         }
   893     }
   894     
   895     private static String mangleSig(String txt, int first, int last) {
   896         StringBuilder sb = new StringBuilder();
   897         for (int i = first; i < last; i++) {
   898             final char ch = txt.charAt(i);
   899             switch (ch) {
   900                 case '/': sb.append('_'); break;
   901                 case '_': sb.append("_1"); break;
   902                 case ';': sb.append("_2"); break;
   903                 case '[': sb.append("_3"); break;
   904                 default: sb.append(ch); break;
   905             }
   906         }
   907         return sb.toString();
   908     }
   909 
   910     private static String findMethodName(MethodData m, StringBuilder cnt) {
   911         StringBuilder name = new StringBuilder();
   912         if ("<init>".equals(m.getName())) { // NOI18N
   913             name.append("cons"); // NOI18N
   914         } else if ("<clinit>".equals(m.getName())) { // NOI18N
   915             name.append("class"); // NOI18N
   916         } else {
   917             name.append(m.getName());
   918         } 
   919         
   920         boolean hasReturn[] = { false };
   921         countArgs(m.getInternalSig(), hasReturn, name, cnt);
   922         return name.toString();
   923     }
   924 
   925     static String findMethodName(String[] mi, StringBuilder cnt, boolean[] hasReturn) {
   926         StringBuilder name = new StringBuilder();
   927         String descr = mi[2];//mi.getDescriptor();
   928         String nm= mi[1];
   929         if ("<init>".equals(nm)) { // NOI18N
   930             name.append("cons"); // NOI18N
   931         } else {
   932             name.append(nm);
   933         }
   934         countArgs(descr, hasReturn, name, cnt);
   935         return name.toString();
   936     }
   937 
   938     private int invokeStaticMethod(byte[] byteCodes, int i, boolean isStatic)
   939     throws IOException {
   940         int methodIndex = readIntArg(byteCodes, i);
   941         String[] mi = jc.getFieldInfoName(methodIndex);
   942         boolean[] hasReturn = { false };
   943         StringBuilder cnt = new StringBuilder();
   944         String mn = findMethodName(mi, cnt, hasReturn);
   945         out.append("{ ");
   946         for (int j = cnt.length() - 1; j >= 0; j--) {
   947             out.append("var v" + j).append(" = s.pop(); ");
   948         }
   949         
   950         if (hasReturn[0]) {
   951             out.append("s.push(");
   952         }
   953         final String in = mi[0];
   954         out.append(in.replace('/', '_'));
   955         out.append("(false).");
   956         out.append(mn);
   957         out.append('(');
   958         String sep = "";
   959         if (!isStatic) {
   960             out.append("s.pop()");
   961             sep = ", ";
   962         }
   963         for (int j = 0; j < cnt.length(); j++) {
   964             out.append(sep);
   965             out.append("v" + j);
   966             sep = ", ";
   967         }
   968         out.append(")");
   969         if (hasReturn[0]) {
   970             out.append(")");
   971         }
   972         out.append("; }");
   973         i += 2;
   974         addReference(in);
   975         return i;
   976     }
   977     private int invokeVirtualMethod(byte[] byteCodes, int i)
   978     throws IOException {
   979         int methodIndex = readIntArg(byteCodes, i);
   980         String[] mi = jc.getFieldInfoName(methodIndex);
   981         boolean[] hasReturn = { false };
   982         StringBuilder cnt = new StringBuilder();
   983         String mn = findMethodName(mi, cnt, hasReturn);
   984         out.append("{ ");
   985         for (int j = cnt.length() - 1; j >= 0; j--) {
   986             out.append("var v" + j).append(" = s.pop(); ");
   987         }
   988         out.append("var self = s.pop(); ");
   989         if (hasReturn[0]) {
   990             out.append("s.push(");
   991         }
   992         out.append("self.");
   993         out.append(mn);
   994         out.append('(');
   995         out.append("self");
   996         for (int j = 0; j < cnt.length(); j++) {
   997             out.append(", ");
   998             out.append("v" + j);
   999         }
  1000         out.append(")");
  1001         if (hasReturn[0]) {
  1002             out.append(")");
  1003         }
  1004         out.append("; }");
  1005         i += 2;
  1006         return i;
  1007     }
  1008     
  1009     private void addReference(String cn) throws IOException {
  1010         if (requireReference(cn)) {
  1011             out.append(" /* needs ").append(cn).append(" */");
  1012         }
  1013     }
  1014 
  1015     private void outType(String d, StringBuilder out) {
  1016         int arr = 0;
  1017         while (d.charAt(0) == '[') {
  1018             out.append('A');
  1019             d = d.substring(1);
  1020         }
  1021         if (d.charAt(0) == 'L') {
  1022             assert d.charAt(d.length() - 1) == ';';
  1023             out.append(d.replace('/', '_').substring(0, d.length() - 1));
  1024         } else {
  1025             out.append(d);
  1026         }
  1027     }
  1028 
  1029     private String encodeConstant(int entryIndex) throws IOException {
  1030         String[] classRef = { null };
  1031         String s = jc.stringValue(entryIndex, classRef);
  1032         if (classRef[0] != null) {
  1033             addReference(classRef[0]);
  1034         }
  1035         return s;
  1036     }
  1037 
  1038     private String javaScriptBody(String prefix, MethodData m, boolean isStatic) throws IOException {
  1039         byte[] arr = m.findAnnotationData(true);
  1040         if (arr == null) {
  1041             return null;
  1042         }
  1043         final String jvmType = "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;";
  1044         class P extends AnnotationParser {
  1045             public P() {
  1046                 super(false);
  1047             }
  1048             
  1049             int cnt;
  1050             String[] args = new String[30];
  1051             String body;
  1052             
  1053             @Override
  1054             protected void visitAttr(String type, String attr, String at, String value) {
  1055                 if (type.equals(jvmType)) {
  1056                     if ("body".equals(attr)) {
  1057                         body = value;
  1058                     } else if ("args".equals(attr)) {
  1059                         args[cnt++] = value;
  1060                     } else {
  1061                         throw new IllegalArgumentException(attr);
  1062                     }
  1063                 }
  1064             }
  1065         }
  1066         P p = new P();
  1067         p.parse(arr, jc);
  1068         if (p.body == null) {
  1069             return null;
  1070         }
  1071         StringBuilder cnt = new StringBuilder();
  1072         final String mn = findMethodName(m, cnt);
  1073         out.append(prefix).append(mn);
  1074         out.append(" = function(");
  1075         String space;
  1076         int index;
  1077         if (!isStatic) {                
  1078             out.append(p.args[0]);
  1079             space = ",";
  1080             index = 1;
  1081         } else {
  1082             space = "";
  1083             index = 0;
  1084         }
  1085         for (int i = 0; i < cnt.length(); i++) {
  1086             out.append(space);
  1087             out.append(p.args[index]);
  1088             index++;
  1089             space = ",";
  1090         }
  1091         out.append(") {").append("\n");
  1092         out.append(p.body);
  1093         out.append("\n}\n");
  1094         return mn;
  1095     }
  1096     private static String className(ClassData jc) {
  1097         //return jc.getName().getInternalName().replace('/', '_');
  1098         return jc.getClassName().replace('/', '_');
  1099     }
  1100     
  1101     private static String[] findAnnotation(
  1102         byte[] arr, ClassData cd, final String className, 
  1103         final String... attrNames
  1104     ) throws IOException {
  1105         if (arr == null) {
  1106             return null;
  1107         }
  1108         final String[] values = new String[attrNames.length];
  1109         final boolean[] found = { false };
  1110         final String jvmType = "L" + className.replace('.', '/') + ";";
  1111         AnnotationParser ap = new AnnotationParser(false) {
  1112             @Override
  1113             protected void visitAttr(String type, String attr, String at, String value) {
  1114                 if (type.equals(jvmType)) {
  1115                     found[0] = true;
  1116                     for (int i = 0; i < attrNames.length; i++) {
  1117                         if (attrNames[i].equals(attr)) {
  1118                             values[i] = value;
  1119                         }
  1120                     }
  1121                 }
  1122             }
  1123             
  1124         };
  1125         ap.parse(arr, cd);
  1126         return found[0] ? values : null;
  1127     }
  1128 
  1129     private CharSequence initField(FieldData v) {
  1130         final String is = v.getInternalSig();
  1131         if (is.length() == 1) {
  1132             switch (is.charAt(0)) {
  1133                 case 'S':
  1134                 case 'J':
  1135                 case 'B':
  1136                 case 'Z':
  1137                 case 'C':
  1138                 case 'I': return " = 0;";
  1139                 case 'F': 
  1140                 case 'D': return " = 0.0;";
  1141                 default:
  1142                     throw new IllegalStateException(is);
  1143             }
  1144         }
  1145         return " = null;";
  1146     }
  1147 
  1148     private static void generateAnno(ClassData cd, final Appendable out, byte[] data) throws IOException {
  1149         AnnotationParser ap = new AnnotationParser(true) {
  1150             int anno;
  1151             int cnt;
  1152             
  1153             @Override
  1154             protected void visitAnnotationStart(String type) throws IOException {
  1155                 if (anno++ > 0) {
  1156                     out.append(",");
  1157                 }
  1158                 out.append('"').append(type).append("\" : {\n");
  1159                 cnt = 0;
  1160             }
  1161 
  1162             @Override
  1163             protected void visitAnnotationEnd(String type) throws IOException {
  1164                 out.append("\n}\n");
  1165             }
  1166             
  1167             @Override
  1168             protected void visitAttr(String type, String attr, String attrType, String value) 
  1169             throws IOException {
  1170                 if (attr == null) {
  1171                     return;
  1172                 }
  1173                 if (cnt++ > 0) {
  1174                     out.append(",\n");
  1175                 }
  1176                 out.append(attr).append("__").append(attrType).append(" : ").append(value);
  1177             }
  1178         };
  1179         ap.parse(data, cd);
  1180     }
  1181 }