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