UNPKG

processing-js

Version:

For the project website, visit http://processingjs.org

1,745 lines • 69.9 kB
/**
 * The parser for turning Processing syntax into Pjs JavaScript.
 * This code is not trivial; unless you know what you're doing,
 * you shouldn't be changing things in here =)
 */
module.exports = function setupParser(Processing, options) {

  var defaultScope = options.defaultScope,
      PConstants = defaultScope.PConstants,
      aFunctions = options.aFunctions,
      Browser = options.Browser,
      document = Browser.document,
      undef;

  // Processing global methods and constants for the parser
  function getGlobalMembers() {
    // The names array contains the names of everything that is inside "p."
    // When something new is added to "p." it must also be added to this list.
    var names = [ /* this code is generated by jsglobals.js */
      "abs", "acos", "alpha", "ambient", "ambientLight", "append", "applyMatrix",
      "arc", "arrayCopy", "asin", "atan", "atan2", "background", "beginCamera",
      "beginDraw", "beginShape", "bezier", "bezierDetail", "bezierPoint",
      "bezierTangent", "bezierVertex", "binary", "blend", "blendColor",
      "blit_resize", "blue", "box", "breakShape", "brightness",
      "camera", "ceil", "Character", "color", "colorMode",
      "concat", "constrain", "copy", "cos", "createFont",
      "createGraphics", "createImage", "cursor", "curve", "curveDetail",
      "curvePoint", "curveTangent", "curveTightness", "curveVertex", "day",
      "degrees", "directionalLight", "disableContextMenu",
      "dist", "draw", "ellipse", "ellipseMode", "emissive", "enableContextMenu",
      "endCamera", "endDraw", "endShape", "exit", "exp", "expand", "externals",
      "fill", "filter", "floor", "focused", "frameCount", "frameRate", "frustum",
      "get", "glyphLook", "glyphTable", "green", "height", "hex", "hint", "hour",
      "hue", "image", "imageMode", "intersect", "join", "key",
      "keyCode", "keyPressed", "keyReleased", "keyTyped", "lerp", "lerpColor",
      "lightFalloff", "lights", "lightSpecular", "line", "link", "loadBytes",
      "loadFont", "loadGlyphs", "loadImage", "loadPixels", "loadShape", "loadXML",
      "loadStrings", "log", "loop", "mag", "map", "match", "matchAll", "max",
      "millis", "min", "minute", "mix", "modelX", "modelY", "modelZ", "modes",
      "month", "mouseButton", "mouseClicked", "mouseDragged", "mouseMoved",
      "mouseOut", "mouseOver", "mousePressed", "mouseReleased", "mouseScroll",
      "mouseScrolled", "mouseX", "mouseY", "name", "nf", "nfc", "nfp", "nfs",
      "noCursor", "noFill", "noise", "noiseDetail", "noiseSeed", "noLights",
      "noLoop", "norm", "normal", "noSmooth", "noStroke", "noTint", "ortho",
      "param", "parseBoolean", "parseByte", "parseChar", "parseFloat",
      "parseInt", "parseXML", "peg", "perspective", "PImage", "pixels",
      "PMatrix2D", "PMatrix3D", "PMatrixStack", "pmouseX", "pmouseY", "point",
      "pointLight", "popMatrix", "popStyle", "pow", "print", "printCamera",
      "println", "printMatrix", "printProjection", "PShape", "PShapeSVG",
      "pushMatrix", "pushStyle", "quad", "radians", "random", "randomGaussian",
      "randomSeed", "rect", "rectMode", "red", "redraw", "requestImage",
      "resetMatrix", "reverse", "rotate", "rotateX", "rotateY", "rotateZ",
      "round", "saturation", "save", "saveFrame", "saveStrings", "scale",
      "screenX", "screenY", "screenZ", "second", "set", "setup", "shape",
      "shapeMode", "shared", "shearX", "shearY", "shininess", "shorten", "sin", "size", "smooth",
      "sort", "specular", "sphere", "sphereDetail", "splice", "split",
      "splitTokens", "spotLight", "sq", "sqrt", "status", "str", "stroke",
      "strokeCap", "strokeJoin", "strokeWeight", "subset", "tan", "text",
      "textAlign", "textAscent", "textDescent", "textFont", "textLeading",
      "textMode", "textSize", "texture", "textureMode", "textWidth", "tint", "toImageData",
      "touchCancel", "touchEnd", "touchMove", "touchStart", "translate", "transform",
      "triangle", "trim", "unbinary", "unhex", "updatePixels", "use3DContext",
      "vertex", "width", "XMLElement", "XML", "year", "__contains", "__equals",
      "__equalsIgnoreCase", "__frameRate", "__hashCode", "__int_cast",
      "__instanceof", "__keyPressed", "__mousePressed", "__printStackTrace",
      "__replace", "__replaceAll", "__replaceFirst", "__toCharArray", "__split",
      "__codePointAt", "__startsWith", "__endsWith", "__matches"];

    // custom functions and properties are added here
    if(aFunctions) {
      Object.keys(aFunctions).forEach(function(name) {
        names.push(name);
      });
    }

    // custom libraries that were attached to Processing
    var members = {};
    var i, l;
    for (i = 0, l = names.length; i < l ; ++i) {
      members[names[i]] = null;
    }
    for (var lib in Processing.lib) {
      if (Processing.lib.hasOwnProperty(lib)) {
        if (Processing.lib[lib].exports) {
          var exportedNames = Processing.lib[lib].exports;
          for (i = 0, l = exportedNames.length; i < l; ++i) {
           members[exportedNames[i]] = null;
          }
        }
      }
    }
    return members;
  }

  /*

    Parser converts Java-like syntax into JavaScript.
    Creates an Abstract Syntax Tree -- "Light AST" from the Java-like code.

    It is an object tree. The root object is created from the AstRoot class, which contains statements.

    A statement object can be of type: AstForStatement, AstCatchStatement, AstPrefixStatement, AstMethod, AstClass,
    AstInterface, AstFunction, AstStatementBlock and AstLabel.

    AstPrefixStatement can be a statement of type: if, switch, while, with, do, else, finally, return, throw, try, break, and continue.

    These object's toString function returns the JavaScript code for the statement.

    Any processing calls need "processing." prepended to them.

    Similarly, calls from inside classes need "$this_1.", prepended to them,
    with 1 being the depth level for inner classes.
    This includes members passed down from inheritance.

    The resulting code is then eval'd and run.

  */

  function parseProcessing(code) {
    var globalMembers = getGlobalMembers();

    // masks parentheses, brackets and braces with '"A5"'
    // where A is the bracket type, and 5 is the index in an array containing all brackets split into atoms
    // 'while(true){}' -> 'while"B1""A2"'
    // parentheses() = B, brackets[] = C and braces{} = A
    function splitToAtoms(code) {
      var atoms = [];
      var items = code.split(/([\{\[\(\)\]\}])/);
      var result = items[0];

      var stack = [];
      for(var i=1; i < items.length; i += 2) {
        var item = items[i];
        if(item === '[' || item === '{' || item === '(') {
          stack.push(result); result = item;
        } else if(item === ']' || item === '}' || item === ')') {
          var kind = item === '}' ? 'A' : item === ')' ? 'B' : 'C';
          var index = atoms.length; atoms.push(result + item);
          result = stack.pop() + '"' + kind + (index + 1) + '"';
        }
        result += items[i + 1];
      }
      atoms.unshift(result);
      return atoms;
    }

    // replaces strings and regexs keyed by index with an array of strings
    function injectStrings(code, strings) {
      return code.replace(/'(\d+)'/g, function(all, index) {
        var val = strings[index];
        if(val.charAt(0) === "/") {
          return val;
        }
        return (/^'((?:[^'\\\n])|(?:\\.[0-9A-Fa-f]*))'$/).test(val) ? "(new $p.Character(" + val + "))" : val;
      });
    }

    // trims off leading and trailing spaces
    // returns an object. object.left, object.middle, object.right, object.untrim
    function trimSpaces(string) {
      var m1 = /^\s*/.exec(string), result;
      if(m1[0].length === string.length) {
        result = {left: m1[0], middle: "", right: ""};
      } else {
        var m2 = /\s*$/.exec(string);
        result = {left: m1[0], middle: string.substring(m1[0].length, m2.index), right: m2[0]};
      }
      result.untrim = function(t) { return this.left + t + this.right; };
      return result;
    }

    // simple trim of leading and trailing spaces
    function trim(string) {
      return string.replace(/^\s+/,'').replace(/\s+$/,'');
    }

    function appendToLookupTable(table, array) {
      for(var i=0,l=array.length;i<l;++i) {
        table[array[i]] = null;
      }
      return table;
    }

    function isLookupTableEmpty(table) {
      for(var i in table) {
        if(table.hasOwnProperty(i)) {
          return false;
        }
      }
      return true;
    }

    function getAtomIndex(templ) { return templ.substring(2, templ.length - 1); }

    // remove carriage returns "\r"
    var codeWoExtraCr = code.replace(/\r\n?|\n\r/g, "\n");

    // masks strings and regexs with "'5'", where 5 is the index in an array containing all strings and regexs
    // also removes all comments
    var strings = [];
    var codeWoStrings = codeWoExtraCr.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g,
    function(all, quoted, aposed, regexCtx, prefix, regex, singleComment, comment) {
      var index;
      if(quoted || aposed) { // replace strings
        index = strings.length; strings.push(all);
        return "'" + index + "'";
      }
      if(regexCtx) { // replace RegExps
        index = strings.length; strings.push(regex);
        return prefix + "'" + index + "'";
      }
      // kill comments
      return comment !== "" ? " " : "\n";
    });

    // protect character codes from namespace collision
    codeWoStrings = codeWoStrings.replace(/__x([0-9A-F]{4})/g, function(all, hexCode) {
      // $ = __x0024
      // _ = __x005F
      // this protects existing character codes from conversion
      // __x0024 = __x005F_x0024
      return "__x005F_x" + hexCode;
    });

    // convert dollar sign to character code
    codeWoStrings = codeWoStrings.replace(/\$/g, "__x0024");

    // Remove newlines after return statements
    codeWoStrings = codeWoStrings.replace(/return\s*[\n\r]+/g, "return ");

    // removes generics
    var genericsWereRemoved;
    var codeWoGenerics = codeWoStrings;
    var replaceFunc = function(all, before, types, after) {
      if(!!before || !!after) {
        return all;
      }
      genericsWereRemoved = true;
      return "";
    };

    do {
      genericsWereRemoved = false;
      codeWoGenerics = codeWoGenerics.replace(/([<]?)<\s*((?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\[\])*(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?(?:\s*,\s*(?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\[\])*(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?)*)\s*>([=]?)/g, replaceFunc);
    } while (genericsWereRemoved);

    var atoms = splitToAtoms(codeWoGenerics);
    var replaceContext;
    var declaredClasses = {}, currentClassId, classIdSeed = 0;

    function addAtom(text, type) {
      var lastIndex = atoms.length;
      atoms.push(text);
      return '"' + type + lastIndex + '"';
    }

    function generateClassId() {
      return "class" + (++classIdSeed);
    }

    function appendClass(class_, classId, scopeId) {
      class_.classId = classId;
      class_.scopeId = scopeId;
      declaredClasses[classId] = class_;
    }

    // functions defined below
    var transformClassBody, transformInterfaceBody, transformStatementsBlock, transformStatements, transformMain, transformExpression;

    var classesRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)(class|interface)\s+([A-Za-z_$][\w$]*\b)(\s+extends\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?(\s+implements\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?\s*("A\d+")/g;
    var methodsRegex = /\b((?:(?:public|private|final|protected|static|abstract|synchronized)\s+)*)((?!(?:else|new|return|throw|function|public|private|protected)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+"|;)/g;
    var fieldTest = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:else|new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*(?:"C\d+"\s*)*([=,]|$)/;
    var cstrsRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+")/g;
    var attrAndTypeRegex = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*/;
    var functionsRegex = /\bfunction(?:\s+([A-Za-z_$][\w$]*))?\s*("B\d+")\s*("A\d+")/g;

    // This converts classes, methods and functions into atoms, and adds them to the atoms array.
    // classes = E, methods = D and functions = H
    function extractClassesAndMethods(code) {
      var s = code;
      s = s.replace(classesRegex, function(all) {
        return addAtom(all, 'E');
      });
      s = s.replace(methodsRegex, function(all) {
        return addAtom(all, 'D');
      });
      s = s.replace(functionsRegex, function(all) {
        return addAtom(all, 'H');
      });
      return s;
    }

    // This converts constructors into atoms, and adds them to the atoms array.
    // constructors = G
    function extractConstructors(code, className) {
      var result = code.replace(cstrsRegex, function(all, attr, name, params, throws_, body) {
        if(name !== className) {
          return all;
        }
        return addAtom(all, 'G');
      });
      return result;
    }

    // AstParam contains the name of a parameter inside a function declaration
    function AstParam(name) {
      this.name = name;
    }
    AstParam.prototype.toString = function() {
      return this.name;
    };
    // AstParams contains an array of AstParam objects
    function AstParams(params, methodArgsParam) {
      this.params = params;
      this.methodArgsParam = methodArgsParam;
    }
    AstParams.prototype.getNames = function() {
      var names = [];
      for(var i=0,l=this.params.length;i<l;++i) {
        names.push(this.params[i].name);
      }
      return names;
    };
    AstParams.prototype.prependMethodArgs = function(body) {
      if (!this.methodArgsParam) {
        return body;
      }
      return "{\nvar " + this.methodArgsParam.name +
        " = Array.prototype.slice.call(arguments, " +
        this.params.length + ");\n" + body.substring(1);
    };
    AstParams.prototype.toString = function() {
      if(this.params.length === 0) {
        return "()";
      }
      var result = "(";
      for(var i=0,l=this.params.length;i<l;++i) {
        result += this.params[i] + ", ";
      }
      return result.substring(0, result.length - 2) + ")";
    };

    function transformParams(params) {
      var paramsWoPars = trim(params.substring(1, params.length - 1));
      var result = [], methodArgsParam = null;
      if(paramsWoPars !== "") {
        var paramList = paramsWoPars.split(",");
        for(var i=0; i < paramList.length; ++i) {
          var param = /\b([A-Za-z_$][\w$]*\b)(\s*"[ABC][\d]*")*\s*$/.exec(paramList[i]);
          if (i === paramList.length - 1 && paramList[i].indexOf('...') >= 0) {
            methodArgsParam = new AstParam(param[1]);
            break;
          }
          result.push(new AstParam(param[1]));
        }
      }
      return new AstParams(result, methodArgsParam);
    }

    function preExpressionTransform(expr) {
      var s = expr;
      // new type[] {...} --> {...}
      s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"C\d+")+\s*("A\d+")/g, function(all, type, init) {
        return init;
      });
      // new Runnable() {...} --> "F???"
      s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"B\d+")\s*("A\d+")/g, function(all, type, init) {
        return addAtom(all, 'F');
      });
      // function(...) { } --> "H???"
      s = s.replace(functionsRegex, function(all) {
        return addAtom(all, 'H');
      });
      // new type[?] --> createJavaArray('type', [?])
      s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*("C\d+"(?:\s*"C\d+")*)/g, function(all, type, index) {
        var args = index.replace(/"C(\d+)"/g, function(all, j) { return atoms[j]; })
          .replace(/\[\s*\]/g, "[null]").replace(/\s*\]\s*\[\s*/g, ", ");
        var arrayInitializer = "{" + args.substring(1, args.length - 1) + "}";
        var createArrayArgs = "('" + type + "', " + addAtom(arrayInitializer, 'A') + ")";
        return '$p.createJavaArray' + addAtom(createArrayArgs, 'B');
      });
      // .length() --> .length
      s = s.replace(/(\.\s*length)\s*"B\d+"/g, "$1");
      // #000000 --> 0x000000
      s = s.replace(/#([0-9A-Fa-f]{6})\b/g, function(all, digits) {
        return "0xFF" + digits;
      });
      // delete (type)???, except (int)???
      s = s.replace(/"B(\d+)"(\s*(?:[\w$']|"B))/g, function(all, index, next) {
        var atom = atoms[index];
        if(!/^\(\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\s*(?:"C\d+"\s*)*\)$/.test(atom)) {
          return all;
        }
        if(/^\(\s*int\s*\)$/.test(atom)) {
          return "(int)" + next;
        }
        var indexParts = atom.split(/"C(\d+)"/g);
        if(indexParts.length > 1) {
          // even items contains atom numbers, can check only first
          if(! /^\[\s*\]$/.test(atoms[indexParts[1]])) {
            return all; // fallback - not a cast
          }
        }
        return "" + next;
      });
      // (int)??? -> __int_cast(???)
      s = s.replace(/\(int\)([^,\]\)\}\?\:\*\+\-\/\^\|\%\&\~<\>\=]+)/g, function(all, arg) {
        var trimmed = trimSpaces(arg);
        return trimmed.untrim("__int_cast(" + trimmed.middle + ")");
      });
      // super() -> $superCstr(), super. -> $super.;
      s = s.replace(/\bsuper(\s*"B\d+")/g, "$$superCstr$1").replace(/\bsuper(\s*\.)/g, "$$super$1");
      // 000.43->0.43 and 0010f->10, but not 0010
      s = s.replace(/\b0+((\d*)(?:\.[\d*])?(?:[eE][\-\+]?\d+)?[fF]?)\b/, function(all, numberWo0, intPart) {
        if( numberWo0 === intPart) {
          return all;
        }
        return intPart === "" ? "0" + numberWo0 : numberWo0;
      });
      // 3.0f -> 3.0
      s = s.replace(/\b(\.?\d+\.?)[fF]\b/g, "$1");
      // Weird (?) parsing errors with %
      s = s.replace(/([^\s])%([^=\s])/g, "$1 % $2");
      // Since frameRate() and frameRate are different things,
      // we need to differentiate them somehow. So when we parse
      // the Processing.js source, replace frameRate so it isn't
      // confused with frameRate(), as well as keyPressed and mousePressed
      s = s.replace(/\b(frameRate|keyPressed|mousePressed)\b(?!\s*"B)/g, "__$1");
      // "boolean", "byte", "int", etc. => "parseBoolean", "parseByte", "parseInt", etc.
      s = s.replace(/\b(boolean|byte|char|float|int)\s*"B/g, function(all, name) {
        return "parse" + name.substring(0, 1).toUpperCase() + name.substring(1) + "\"B";
      });
      // "pixels" replacements:
      //   pixels[i] = c => pixels.setPixel(i,c) | pixels[i] => pixels.getPixel(i)
      //   pixels.length => pixels.getLength()
      //   pixels = ar => pixels.set(ar) | pixels => pixels.toArray()
      s = s.replace(/\bpixels\b\s*(("C(\d+)")|\.length)?(\s*=(?!=)([^,\]\)\}]+))?/g,
        function(all, indexOrLength, index, atomIndex, equalsPart, rightSide) {
          if(index) {
            var atom = atoms[atomIndex];
            if(equalsPart) {
              return "pixels.setPixel" + addAtom("(" +atom.substring(1, atom.length - 1) +
                "," + rightSide + ")", 'B');
            }
            return "pixels.getPixel" + addAtom("(" + atom.substring(1, atom.length - 1) +
              ")", 'B');
          }
          if(indexOrLength) {
            // length
            return "pixels.getLength" + addAtom("()", 'B');
          }
          if(equalsPart) {
            return "pixels.set" + addAtom("(" + rightSide + ")", 'B');
          }
          return "pixels.toArray" + addAtom("()", 'B');
        });
      // Java method replacements for: replace, replaceAll, replaceFirst, equals, hashCode, etc.
      //   xxx.replace(yyy) -> __replace(xxx, yyy)
      //   "xx".replace(yyy) -> __replace("xx", yyy)
      var repeatJavaReplacement;
      function replacePrototypeMethods(all, subject, method, atomIndex) {
        var atom = atoms[atomIndex];
        repeatJavaReplacement = true;
        var trimmed = trimSpaces(atom.substring(1, atom.length - 1));
        return "__" + method  + ( trimmed.middle === "" ? addAtom("(" + subject.replace(/\.\s*$/, "") + ")", 'B') :
          addAtom("(" + subject.replace(/\.\s*$/, "") + "," + trimmed.middle + ")", 'B') );
      }
      do {
        repeatJavaReplacement = false;
        s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*\.\s*(?:[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*\.\s*)*)(replace|replaceAll|replaceFirst|contains|equals|equalsIgnoreCase|hashCode|toCharArray|printStackTrace|split|startsWith|endsWith|codePointAt|matches)\s*"B(\d+)"/g,
          replacePrototypeMethods);
      } while (repeatJavaReplacement);
      // xxx instanceof yyy -> __instanceof(xxx, yyy)
      function replaceInstanceof(all, subject, type) {
        repeatJavaReplacement = true;
        return "__instanceof" + addAtom("(" + subject + ", " + type + ")", 'B');
      }
      do {
        repeatJavaReplacement = false;
        s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*(?:\.\s*[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*)*)instanceof\s+([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)/g,
          replaceInstanceof);
      } while (repeatJavaReplacement);
      // this() -> $constr()
      s = s.replace(/\bthis(\s*"B\d+")/g, "$$constr$1");

      return s;
    }

    function AstInlineClass(baseInterfaceName, body) {
      this.baseInterfaceName = baseInterfaceName;
      this.body = body;
      body.owner = this;
    }
    AstInlineClass.prototype.toString = function() {
      return "new (" + this.body + ")";
    };

    function transformInlineClass(class_) {
      var m = new RegExp(/\bnew\s*([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)\s*"B\d+"\s*"A(\d+)"/).exec(class_);
      var oldClassId = currentClassId, newClassId = generateClassId();
      currentClassId = newClassId;
      var uniqueClassName = m[1] + "$" + newClassId;
      var inlineClass = new AstInlineClass(uniqueClassName,
        transformClassBody(atoms[m[2]], uniqueClassName, "", "implements " + m[1]));
      appendClass(inlineClass, newClassId, oldClassId);
      currentClassId = oldClassId;
      return inlineClass;
    }

    function AstFunction(name, params, body) {
      this.name = name;
      this.params = params;
      this.body = body;
    }
    AstFunction.prototype.toString = function() {
      var oldContext = replaceContext;
      // saving "this." and parameters
      var names = appendToLookupTable({"this":null}, this.params.getNames());
      replaceContext = function (subject) {
        return names.hasOwnProperty(subject.name) ? subject.name : oldContext(subject);
      };
      var result = "function";
      if(this.name) {
        result += " " + this.name;
      }
      var body = this.params.prependMethodArgs(this.body.toString());
      result += this.params + " " + body;
      replaceContext = oldContext;
      return result;
    };

    function transformFunction(class_) {
      var m = new RegExp(/\b([A-Za-z_$][\w$]*)\s*"B(\d+)"\s*"A(\d+)"/).exec(class_);
      return new AstFunction( m[1] !== "function" ? m[1] : null,
        transformParams(atoms[m[2]]), transformStatementsBlock(atoms[m[3]]));
    }

    function AstInlineObject(members) {
      this.members = members;
    }
    AstInlineObject.prototype.toString = function() {
      var oldContext = replaceContext;
      replaceContext = function (subject) {
          return subject.name === "this" ? "this" : oldContext(subject); // saving "this."
      };
      var result = "";
      for(var i=0,l=this.members.length;i<l;++i) {
        if(this.members[i].label) {
          result += this.members[i].label + ": ";
        }
        result += this.members[i].value.toString() + ", ";
      }
      replaceContext = oldContext;
      return result.substring(0, result.length - 2);
    };

    function transformInlineObject(obj) {
      var members = obj.split(',');
      for(var i=0; i < members.length; ++i) {
        var label = members[i].indexOf(':');
        if(label < 0) {
          members[i] = { value: transformExpression(members[i]) };
        } else {
          members[i] = { label: trim(members[i].substring(0, label)),
            value: transformExpression( trim(members[i].substring(label + 1)) ) };
        }
      }
      return new AstInlineObject(members);
    }

    function expandExpression(expr) {
      if(expr.charAt(0) === '(' || expr.charAt(0) === '[') {
        return expr.charAt(0) + expandExpression(expr.substring(1, expr.length - 1)) + expr.charAt(expr.length - 1);
      }
      if(expr.charAt(0) === '{') {
        if(/^\{\s*(?:[A-Za-z_$][\w$]*|'\d+')\s*:/.test(expr)) {
          return "{" + addAtom(expr.substring(1, expr.length - 1), 'I') + "}";
        }
        return "[" + expandExpression(expr.substring(1, expr.length - 1)) + "]";
      }
      var trimmed = trimSpaces(expr);
      var result = preExpressionTransform(trimmed.middle);
      result = result.replace(/"[ABC](\d+)"/g, function(all, index) {
        return expandExpression(atoms[index]);
      });
      return trimmed.untrim(result);
    }

    function replaceContextInVars(expr) {
      return expr.replace(/(\.\s*)?((?:\b[A-Za-z_]|\$)[\w$]*)(\s*\.\s*([A-Za-z_$][\w$]*)(\s*\()?)?/g,
        function(all, memberAccessSign, identifier, suffix, subMember, callSign) {
          if(memberAccessSign) {
            return all;
          }
          var subject = { name: identifier, member: subMember, callSign: !!callSign };
          return replaceContext(subject) + (suffix === undef ? "" : suffix);
        });
    }

    function AstExpression(expr, transforms) {
      this.expr = expr;
      this.transforms = transforms;
    }
    AstExpression.prototype.toString = function() {
      var transforms = this.transforms;
      var expr = replaceContextInVars(this.expr);
      return expr.replace(/"!(\d+)"/g, function(all, index) {
        return transforms[index].toString();
      });
    };

    transformExpression = function(expr) {
      var transforms = [];
      var s = expandExpression(expr);
      s = s.replace(/"H(\d+)"/g, function(all, index) {
        transforms.push(transformFunction(atoms[index]));
        return '"!' + (transforms.length - 1) + '"';
      });
      s = s.replace(/"F(\d+)"/g, function(all, index) {
        transforms.push(transformInlineClass(atoms[index]));
        return '"!' + (transforms.length - 1) + '"';
      });
      s = s.replace(/"I(\d+)"/g, function(all, index) {
        transforms.push(transformInlineObject(atoms[index]));
        return '"!' + (transforms.length - 1) + '"';
      });

      return new AstExpression(s, transforms);
    };

    function AstVarDefinition(name, value, isDefault) {
      this.name = name;
      this.value = value;
      this.isDefault = isDefault;
    }
    AstVarDefinition.prototype.toString = function() {
      return this.name + ' = ' + this.value;
    };

    function transformVarDefinition(def, defaultTypeValue) {
      var eqIndex = def.indexOf("=");
      var name, value, isDefault;
      if(eqIndex < 0) {
        name = def;
        value = defaultTypeValue;
        isDefault = true;
      } else {
        name = def.substring(0, eqIndex);
        value = transformExpression(def.substring(eqIndex + 1));
        isDefault = false;
      }
      return new AstVarDefinition( trim(name.replace(/(\s*"C\d+")+/g, "")),
        value, isDefault);
    }

    function getDefaultValueForType(type) {
        if(type === "int" || type === "float") {
          return "0";
        }
        if(type === "boolean") {
          return "false";
        }
        if(type === "color") {
          return "0x00000000";
        }
        return "null";
    }

    function AstVar(definitions, varType) {
      this.definitions = definitions;
      this.varType = varType;
    }
    AstVar.prototype.getNames = function() {
      var names = [];
      for(var i=0,l=this.definitions.length;i<l;++i) {
        names.push(this.definitions[i].name);
      }
      return names;
    };
    AstVar.prototype.toString = function() {
      return "var " + this.definitions.join(",");
    };
    function AstStatement(expression) {
      this.expression = expression;
    }
    AstStatement.prototype.toString = function() {
      return this.expression.toString();
    };

    function transformStatement(statement) {
      if(fieldTest.test(statement)) {
        var attrAndType = attrAndTypeRegex.exec(statement);
        var definitions = statement.substring(attrAndType[0].length).split(",");
        var defaultTypeValue = getDefaultValueForType(attrAndType[2]);
        for(var i=0; i < definitions.length; ++i) {
          definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue);
        }
        return new AstVar(definitions, attrAndType[2]);
      }
      return new AstStatement(transformExpression(statement));
    }

    function AstForExpression(initStatement, condition, step) {
      this.initStatement = initStatement;
      this.condition = condition;
      this.step = step;
    }
    AstForExpression.prototype.toString = function() {
      return "(" + this.initStatement + "; " + this.condition + "; " + this.step + ")";
    };

    function AstForInExpression(initStatement, container) {
      this.initStatement = initStatement;
      this.container = container;
    }
    AstForInExpression.prototype.toString = function() {
      var init = this.initStatement.toString();
      if(init.indexOf("=") >= 0) { // can be without var declaration
        init = init.substring(0, init.indexOf("="));
      }
      return "(" + init + " in " + this.container + ")";
    };

    function AstForEachExpression(initStatement, container) {
      this.initStatement = initStatement;
      this.container = container;
    }
    AstForEachExpression.iteratorId = 0;
    AstForEachExpression.prototype.toString = function() {
      var init = this.initStatement.toString();
      var iterator = "$it" + (AstForEachExpression.iteratorId++);
      var variableName = init.replace(/^\s*var\s*/, "").split("=")[0];
      var initIteratorAndVariable = "var " + iterator + " = new $p.ObjectIterator(" + this.container + "), " +
         variableName + " = void(0)";
      var nextIterationCondition = iterator + ".hasNext() && ((" +
         variableName + " = " + iterator + ".next()) || true)";
      return "(" + initIteratorAndVariable + "; " + nextIterationCondition + ";)";
    };

    function transformForExpression(expr) {
      var content;
      if (/\bin\b/.test(expr)) {
        content = expr.substring(1, expr.length - 1).split(/\bin\b/g);
        return new AstForInExpression( transformStatement(trim(content[0])),
          transformExpression(content[1]));
      }
      if (expr.indexOf(":") >= 0 && expr.indexOf(";") < 0) {
        content = expr.substring(1, expr.length - 1).split(":");
        return new AstForEachExpression( transformStatement(trim(content[0])),
          transformExpression(content[1]));
      }
      content = expr.substring(1, expr.length - 1).split(";");
      return new AstForExpression( transformStatement(trim(content[0])),
        transformExpression(content[1]), transformExpression(content[2]));
    }

    function sortByWeight(array) {
      array.sort(function (a,b) {
        return b.weight - a.weight;
      });
    }

    function AstInnerInterface(name, body, isStatic) {
      this.name = name;
      this.body = body;
      this.isStatic = isStatic;
      body.owner = this;
    }
    AstInnerInterface.prototype.toString = function() {
      return "" + this.body;
    };
    function AstInnerClass(name, body, isStatic) {
      this.name = name;
      this.body = body;
      this.isStatic = isStatic;
      body.owner = this;
    }
    AstInnerClass.prototype.toString = function() {
      return "" + this.body;
    };

    function transformInnerClass(class_) {
      var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body
      classesRegex.lastIndex = 0;
      var isStatic = m[1].indexOf("static") >= 0;
      var body = atoms[getAtomIndex(m[6])], innerClass;
      var oldClassId = currentClassId, newClassId = generateClassId();
      currentClassId = newClassId;
      if(m[2] === "interface") {
        innerClass = new AstInnerInterface(m[3], transformInterfaceBody(body, m[3], m[4]), isStatic);
      } else {
        innerClass = new AstInnerClass(m[3], transformClassBody(body, m[3], m[4], m[5]), isStatic);
      }
      appendClass(innerClass, newClassId, oldClassId);
      currentClassId = oldClassId;
      return innerClass;
    }

    function AstClassMethod(name, params, body, isStatic) {
      this.name = name;
      this.params = params;
      this.body = body;
      this.isStatic = isStatic;
    }
    AstClassMethod.prototype.toString = function(){
      var paramNames = appendToLookupTable({}, this.params.getNames());
      var oldContext = replaceContext;
      replaceContext = function (subject) {
        return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject);
      };
      var body = this.params.prependMethodArgs(this.body.toString());
      var result = "function " + this.methodId + this.params + " " + body +"\n";
      replaceContext = oldContext;
      return result;
    };

    function transformClassMethod(method) {
      var m = methodsRegex.exec(method);
      methodsRegex.lastIndex = 0;
      var isStatic = m[1].indexOf("static") >= 0;
      var body = m[6] !== ';' ? atoms[getAtomIndex(m[6])] : "{}";
      return new AstClassMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]),
        transformStatementsBlock(body), isStatic );
    }

    function AstClassField(definitions, fieldType, isStatic) {
      this.definitions = definitions;
      this.fieldType = fieldType;
      this.isStatic = isStatic;
    }
    AstClassField.prototype.getNames = function() {
      var names = [];
      for(var i=0,l=this.definitions.length;i<l;++i) {
        names.push(this.definitions[i].name);
      }
      return names;
    };
    AstClassField.prototype.toString = function() {
      var thisPrefix = replaceContext({ name: "[this]" });
      if(this.isStatic) {
        var className = this.owner.name;
        var staticDeclarations = [];
        for(var i=0,l=this.definitions.length;i<l;++i) {
          var definition = this.definitions[i];
          var name = definition.name, staticName = className + "." + name;
          var declaration = "if(" + staticName + " === void(0)) {\n" +
            " " + staticName + " = " + definition.value + "; }\n" +
            "$p.defineProperty(" + thisPrefix + ", " +
            "'" + name + "', { get: function(){return " + staticName + ";}, " +
            "set: function(val){" + staticName + " = val;} });\n";
          staticDeclarations.push(declaration);
        }
        return staticDeclarations.join("");
      }
      return thisPrefix + "." + this.definitions.join("; " + thisPrefix + ".");
    };

    function transformClassField(statement) {
      var attrAndType = attrAndTypeRegex.exec(statement);
      var isStatic = attrAndType[1].indexOf("static") >= 0;
      var definitions = statement.substring(attrAndType[0].length).split(/,\s*/g);
      var defaultTypeValue = getDefaultValueForType(attrAndType[2]);
      for(var i=0; i < definitions.length; ++i) {
        definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue);
      }
      return new AstClassField(definitions, attrAndType[2], isStatic);
    }

    function AstConstructor(params, body) {
      this.params = params;
      this.body = body;
    }
    AstConstructor.prototype.toString = function() {
      var paramNames = appendToLookupTable({}, this.params.getNames());
      var oldContext = replaceContext;
      replaceContext = function (subject) {
        return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject);
      };
      var prefix = "function $constr_" + this.params.params.length + this.params.toString();
      var body = this.params.prependMethodArgs(this.body.toString());
      if(!/\$(superCstr|constr)\b/.test(body)) {
        body = "{\n$superCstr();\n" + body.substring(1);
      }
      replaceContext = oldContext;
      return prefix + body + "\n";
    };

    function transformConstructor(cstr) {
      var m = new RegExp(/"B(\d+)"\s*"A(\d+)"/).exec(cstr);
      var params = transformParams(atoms[m[1]]);

      return new AstConstructor(params, transformStatementsBlock(atoms[m[2]]));
    }

    function AstInterfaceBody(name, interfacesNames, methodsNames, fields, innerClasses, misc) {
      var i,l;
      this.name = name;
      this.interfacesNames = interfacesNames;
      this.methodsNames = methodsNames;
      this.fields = fields;
      this.innerClasses = innerClasses;
      this.misc = misc;
      for(i=0,l=fields.length; i<l; ++i) {
        fields[i].owner = this;
      }
    }
    AstInterfaceBody.prototype.getMembers = function(classFields, classMethods, classInners) {
      if(this.owner.base) {
        this.owner.base.body.getMembers(classFields, classMethods, classInners);
      }
      var i, j, l, m;
      for(i=0,l=this.fields.length;i<l;++i) {
        var fieldNames = this.fields[i].getNames();
        for(j=0,m=fieldNames.length;j<m;++j) {
          classFields[fieldNames[j]] = this.fields[i];
        }
      }
      for(i=0,l=this.methodsNames.length;i<l;++i) {
        var methodName = this.methodsNames[i];
        classMethods[methodName] = true;
      }
      for(i=0,l=this.innerClasses.length;i<l;++i) {
        var innerClass = this.innerClasses[i];
        classInners[innerClass.name] = innerClass;
      }
    };
    AstInterfaceBody.prototype.toString = function() {
      function getScopeLevel(p) {
        var i = 0;
        while(p) {
          ++i;
          p=p.scope;
        }
        return i;
      }

      var scopeLevel = getScopeLevel(this.owner);

      var className = this.name;
      var staticDefinitions = "";
      var metadata = "";

      var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {};
      this.getMembers(thisClassFields, thisClassMethods, thisClassInners);

      var i, l, j, m;

      if (this.owner.interfaces) {
        // interface name can be present, but interface is not
        var resolvedInterfaces = [], resolvedInterface;
        for (i = 0, l = this.interfacesNames.length; i < l; ++i) {
          if (!this.owner.interfaces[i]) {
            continue;
          }
          resolvedInterface = replaceContext({name: this.interfacesNames[i]});
          resolvedInterfaces.push(resolvedInterface);
          staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n";
        }
        metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n";
      }
      metadata += className + ".$isInterface = true;\n";
      metadata += className + ".$methods = [\'" + this.methodsNames.join("\', \'") + "\'];\n";

      sortByWeight(this.innerClasses);
      for (i = 0, l = this.innerClasses.length; i < l; ++i) {
        var innerClass = this.innerClasses[i];
        if (innerClass.isStatic) {
          staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n";
        }
      }

      for (i = 0, l = this.fields.length; i < l; ++i) {
        var field = this.fields[i];
        if (field.isStatic) {
          staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n";
        }
      }

      return "(function() {\n" +
        "function " + className + "() { throw \'Unable to create the interface\'; }\n" +
        staticDefinitions +
        metadata +
        "return " + className + ";\n" +
        "})()";
    };

    transformInterfaceBody = function(body, name, baseInterfaces) {
      var declarations = body.substring(1, body.length - 1);
      declarations = extractClassesAndMethods(declarations);
      declarations = extractConstructors(declarations, name);
      var methodsNames = [], classes = [];
      declarations = declarations.replace(/"([DE])(\d+)"/g, function(all, type, index) {
        if(type === 'D') { methodsNames.push(index); }
        else if(type === 'E') { classes.push(index); }
        return "";
      });
      var fields = declarations.split(/;(?:\s*;)*/g);
      var baseInterfaceNames;
      var i, l;

      if(baseInterfaces !== undef) {
        baseInterfaceNames = baseInterfaces.replace(/^\s*extends\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g);
      }

      for(i = 0, l = methodsNames.length; i < l; ++i) {
        var method = transformClassMethod(atoms[methodsNames[i]]);
        methodsNames[i] = method.name;
      }
      for(i = 0, l = fields.length - 1; i < l; ++i) {
        var field = trimSpaces(fields[i]);
        fields[i] = transformClassField(field.middle);
      }
      var tail = fields.pop();
      for(i = 0, l = classes.length; i < l; ++i) {
        classes[i] = transformInnerClass(atoms[classes[i]]);
      }

      return new AstInterfaceBody(name, baseInterfaceNames, methodsNames, fields, classes, { tail: tail });
    };

    function AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, innerClasses, misc) {
      var i,l;
      this.name = name;
      this.baseClassName = baseClassName;
      this.interfacesNames = interfacesNames;
      this.functions = functions;
      this.methods = methods;
      this.fields = fields;
      this.cstrs = cstrs;
      this.innerClasses = innerClasses;
      this.misc = misc;
      for(i=0,l=fields.length; i<l; ++i) {
        fields[i].owner = this;
      }
    }
    AstClassBody.prototype.getMembers = function(classFields, classMethods, classInners) {
      if(this.owner.base) {
        this.owner.base.body.getMembers(classFields, classMethods, classInners);
      }
      var i, j, l, m;
      for(i=0,l=this.fields.length;i<l;++i) {
        var fieldNames = this.fields[i].getNames();
        for(j=0,m=fieldNames.length;j<m;++j) {
          classFields[fieldNames[j]] = this.fields[i];
        }
      }
      for(i=0,l=this.methods.length;i<l;++i) {
        var method = this.methods[i];
        classMethods[method.name] = method;
      }
      for(i=0,l=this.innerClasses.length;i<l;++i) {
        var innerClass = this.innerClasses[i];
        classInners[innerClass.name] = innerClass;
      }
    };
    AstClassBody.prototype.toString = function() {
      function getScopeLevel(p) {
        var i = 0;
        while(p) {
          ++i;
          p=p.scope;
        }
        return i;
      }

      var scopeLevel = getScopeLevel(this.owner);

      var selfId = "$this_" + scopeLevel;
      var className = this.name;
      var result = "var " + selfId + " = this;\n";
      var staticDefinitions = "";
      var metadata = "";

      var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {};
      this.getMembers(thisClassFields, thisClassMethods, thisClassInners);

      var oldContext = replaceContext;
      replaceContext = function (subject) {
        var name = subject.name;
        if(name === "this") {
          // returns "$this_N.$self" pointer instead of "this" in cases:
          // "this()", "this.XXX()", "this", but not for "this.XXX"
          return subject.callSign || !subject.member ? selfId + ".$self" : selfId;
        }
        if(thisClassFields.hasOwnProperty(name)) {
          return thisClassFields[name].isStatic ? className + "." + name : selfId + "." + name;
        }
        if(thisClassInners.hasOwnProperty(name)) {
          return selfId + "." + name;
        }
        if(thisClassMethods.hasOwnProperty(name)) {
          return thisClassMethods[name].isStatic ? className + "." + name : selfId + ".$self." + name;
        }
        return oldContext(subject);
      };

      var resolvedBaseClassName;
      if (this.baseClassName) {
        resolvedBaseClassName = oldContext({name: this.baseClassName});
        result += "var $super = { $upcast: " + selfId + " };\n";
        result += "function $superCstr(){" + resolvedBaseClassName +
          ".apply($super,arguments);if(!('$self' in $super)) $p.extendClassChain($super)}\n";
        metadata += className + ".$base = " + resolvedBaseClassName + ";\n";
      } else {
        result += "function $superCstr(){$p.extendClassChain("+ selfId +")}\n";
      }

      if (this.owner.base) {
        // base class name can be present, but class is not
        staticDefinitions += "$p.extendStaticMembers(" + className + ", " + resolvedBaseClassName + ");\n";
      }

      var i, l, j, m;

      if (this.owner.interfaces) {
        // interface name can be present, but interface is not
        var resolvedInterfaces = [], resolvedInterface;
        for (i = 0, l = this.interfacesNames.length; i < l; ++i) {
          if (!this.owner.interfaces[i]) {
            continue;
          }
          resolvedInterface = oldContext({name: this.interfacesNames[i]});
          resolvedInterfaces.push(resolvedInterface);
          staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n";
        }
        metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n";
      }

      if (this.functions.length > 0) {
        result += this.functions.join('\n') + '\n';
      }

      sortByWeight(this.innerClasses);
      for (i = 0, l = this.innerClasses.length; i < l; ++i) {
        var innerClass = this.innerClasses[i];
        if (innerClass.isStatic) {
          staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n";
          result += selfId + "." + innerClass.name + " = " + className + "." + innerClass.name + ";\n";
        } else {
          result += selfId + "." + innerClass.name + " = " + innerClass + ";\n";
        }
      }

      for (i = 0, l = this.fields.length; i < l; ++i) {
        var field = this.fields[i];
        if (field.isStatic) {
          staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n";
          for (j = 0, m = field.definitions.length; j < m; ++j) {
            var fieldName = field.definitions[j].name, staticName = className + "." + fieldName;
            result += "$p.defineProperty(" + selfId + ", '" + fieldName + "', {" +
              "get: function(){return " + staticName + "}, " +
              "set: function(val){" + staticName + " = val}});\n";
          }
        } else {
          result += selfId + "." + field.definitions.join(";\n" + selfId + ".") + ";\n";
        }
      }
      var methodOverloads = {};
      for (i = 0, l = this.methods.length; i < l; ++i) {
        var method = this.methods[i];
        var overload = methodOverloads[method.name];
        var methodId = method.name + "$" + method.params.params.length;
        var hasMethodArgs = !!method.params.methodArgsParam;
        if (overload) {
          ++overload;
          methodId += "_" + overload;
        } else {
          overload = 1;
        }
        method.methodId = methodId;
        methodOverloads[method.name] = overload;
        if (method.isStatic) {
          staticDefinitions += method;
          staticDefinitions += "$p.addMethod(" + className + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n";
          result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n";
        } else {
          result += method;
          result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ", " + hasMethodArgs + ");\n";
        }
      }
      result += trim(this.misc.tail);

      if (this.cstrs.length > 0) {
        result += this.cstrs.join('\n') + '\n';
      }

      result += "function $constr() {\n";
      var cstrsIfs = [];
      for (i = 0, l = this.cstrs.length; i < l; ++i) {
        var paramsLength = this.cstrs[i].params.params.length;
        var methodArgsPresent = !!this.cstrs[i].params.methodArgsParam;
        cstrsIfs.push("if(arguments.length " + (methodArgsPresent ? ">=" : "===") +
          " " + paramsLength + ") { " +
          "$constr_" + paramsLength + ".apply(" + selfId + ", arguments); }");
      }
      if(cstrsIfs.length > 0) {
        result += cstrsIfs.join(" else ") + " else ";
      }
      // ??? add check if length is 0, otherwise fail
      result += "$superCstr();\n}\n";
      result += "$constr.apply(null, arguments);\n";

      replaceContext = oldContext;
      return "(function() {\n" +
        "function " + className + "() {\n" + result + "}\n" +
        staticDefinitions +
        metadata +
        "return " + className + ";\n" +
        "})()";
    };

    transformClassBody = function(body, name, baseName, interfaces) {
      var declarations = body.substring(1, body.length - 1);
      declarations = extractClassesAndMethods(declarations);
      declarations = extractConstructors(declarations, name);
      var methods = [], classes = [], cstrs = [], functions = [];
      declarations = declarations.replace(/"([DEGH])(\d+)"/g, function(all, type, index) {
        if(type === 'D') { methods.push(index); }
        else if(type === 'E') { classes.push(index); }
        else if(type === 'H') { functions.push(index); }
        else { cstrs.push(index); }
        return "";
      });
      var fields = declarations.replace(/^(?:\s*;)+/, "").split(/;(?:\s*;)*/g);
      var baseClassName, interfacesNames;
      var i;

      if(baseName !== undef) {
        baseClassName = baseName.replace(/^\s*extends\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*$/g, "$1");
      }

      if(interfaces !== undef) {
        interfacesNames = interfaces.replace(/^\s*implements\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g);
      }

      for(i = 0; i < functions.length; ++i) {
        functions[i] = transformFunction(atoms[functions[i]]);
      }
      for(i = 0; i < methods.length; ++i) {
        methods[i] = transformClassMethod(atoms[methods[i]]);
      }
      for(i = 0; i < fields.length - 1; ++i) {
        var field = trimSpaces(fields[i]);
        fields[i] = transformClassField(field.middle);
      }
      var tail = fields.pop();
      for(i = 0; i < cstrs.length; ++i) {
        cstrs[i] = transformConstructor(atoms[cstrs[i]]);
      }
      for(i = 0; i < classes.length; ++i) {
        classes[i] = transformInnerClass(atoms[classes[i]]);
      }

      return new AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs,
        classes, { tail: tail });
    };

    function AstInterface(name, body) {
      this.name = name;
      this.body = body;
      body.owner = this;
    }
    AstInterface.prototype.toString = function() {
      return "var " + this.name + " = " + this.body + ";\n" +
        "$p." + this.name + " = " + this.name + ";\n";
    };
    function AstClass(name, body) {
      this.name = name;
      this.body = body;
      body.owner = this;
    }
    AstClass.prototype.toString = function() {
      return "var " + this.name + " = " + this.body + ";\n" +
        "$p." + this.name + " = " + this.name + ";\n";
    };

    function transformGlobalClass(class_) {
      var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body
      classesRegex.lastIndex = 0;
      var body = atoms[getAtomIndex(m[6])];
      var oldClassId = currentClassId, newClassId = generateClassId();
      currentClassId = newClassId;
      var globalClass;
      if(m[2] === "interface") {
        globalClass = new AstInterface(m[3], transformInterfaceBody(body, m[3], m[4]) );
      } else {
        globalClass = new AstClass(m[3], transformClassBody(body, m[3], m[4], m[5]) );
      }
      appendClass(globalClass, newClassId, oldClassId);
      currentClassId = oldClassId;
      return globalClass;
    }

    function AstMethod(name, params, body) {
      this.name = name;
      this.params = params;
      this.body = body;
    }
    AstMethod.prototype.toString = function(){
      var paramNames = appendToLookupTable({}, this.params.getNames());
      var oldContext = replaceContext;
      replaceContext = function (subject) {
        return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject);
      };
      var body = this.params.prependMethodArgs(this.body.toString());
      var result = "function " + this.name + this.params + " " + body + "\n" +
                   "$p." + this.name + " = " + this.name + ";\n" +
                   this.name + " = " + this.name + ".bind($p);";
//        "$p." + this.name + " = " + this.name + ";";
      replaceContext = oldContext;
      return result;
    };

    function transformGlobalMethod(method) {
      var m = methodsRegex.exec(method);
      var result =
      methodsRegex.lastIndex = 0;
      return new AstMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]),
        transformStatementsBlock(atoms[getAtomIndex(m[6])]));
    }

    function preStatementsTransform(statements) {
      var s = statements;
      // turns multiple catch blocks into one, because we have no way to properly get into them anyway.
      s = s.replace(/\b(catch\s*"B\d+"\s*"A\d+")(\s*catch\s*"B\d+"\s*"A\d+")+/g, "$1");
      return s;
    }

    function AstForStatement(argument, misc) {
      this.argument = argument;
      this.misc = misc;
    }
    AstForStatement.prototype.toString = function() {
      return this.misc.prefix + this.argument.toString();
    };
    function AstCatchStatement(argument, misc) {
      this.argument = argument;
      this.misc = misc;
    }
    AstCatchStatement.prototype.toString = function() {
      return this.misc.prefix + this.argument.toString();
    };
    function AstPrefixStatement(name, argument, misc) {
      this.name = name;
      this.argument = argument;
      this.misc = misc;
    }
    AstPrefixStatement.prototype.toString = function() {
      var result = this.misc.prefix;
      if(this.argument !== undef) {
        result += this.argument.toString();
      }
      return result;
    };
    function AstSwitchCase(expr) {
      this.expr = expr;
    }
    AstSwitchCase.prototype.toString = function() {
      return "case " + this.expr + ":";
    };
    function AstLabel(label) {
      this.label = label;
    }
    AstLabel.prototype.toString = function() {
      return this.label;
    };

    transformStatements = function(statements, transformMethod, transformClass) {
      var nextStatement = new RegExp(/\b(catch|for|if|switch|while|with)\s*"B(\d+)"|\b(do|else|finally|return|throw|try|break|continue)\b|("[ADEH](\d+)")|\b(case)\s+([^:]+):|\b([A-Za-z_$][\w$]*\s*:)|(;)/g);
      var res = [];
      statements = preStatementsTransform(statements);
      var lastIndex = 0, m, space;
      // m contains the matches from the nextStatement regexp, null if there are no matches.
      // nextStatement.exec starts searching at nextStatement.lastIndex.
      while((m = nextStatement.exec(statements)) !== null) {
        if(m[1] !== undef) { // catch, for ...
          var i = statements.lastIndexOf('"B', nextStatement.lastIndex);
          var statementsPrefix = statements.substring(lastIndex, i);
          if(m[1] === "for") {
            res.push(new AstForStatement(transformForExpression(atoms[m[2]]),
              { prefix: statementsPrefix }) );
          } else if(m[1] === "catch") {
            res.push(new AstCatchStatement(transformParams(atoms[m[2]]),
              { prefix: statementsPrefix }) );
          } else {
            res.push(new AstPrefixStatement(m[1], transformExpression(atoms[m[2]]),
              { prefix: statementsPrefix }) );
          }
        } else if(m[3] !== undef) { // do, else, ...
            res.push(new AstPrefixStatement(m[3], undef,
              { prefix: statements.substring(lastIndex, nextStatement.lastIndex) }) );
        } else if(m[4] !== undef) { // block, class and methods
          space = statements.substring(lastIndex, nextStatement.lastIndex - m[4].length);
          if(trim(space).length !== 0) { continue; } // avoiding new type[] {} construct
          res.push(space);
          var kind = m[4].charAt(1), atomIndex = m[5];
          if(kind === 'D') {
            res.push(transformMethod(atoms[atomIndex]));
          } else if(kind === 'E') {
            res.push(transformClass(atoms[atomIndex]));
          } else if(kind === 'H') {
            res.push(transformFunction(atoms[atomIndex]));
          } else {
            res.push(transformStatementsBlock(atoms[atomIndex]));
          }
        } else if(m[6] !== undef) { // switch case
          res.push(new AstSwitchCase(transformExpression(trim(m[7]))));
        } else if(m[8] !== undef) { // label
          space = statements.substring(lastIndex, nextStatement.lastIndex - m[8].length);
          if(trim(space).length !== 0) { continue; } // avoiding ?: construct
          res.push(new AstLabel(statements.substring(lastIndex, nextStatement.lastIndex)) );
        } else { // semicolon
          var statement = trimSpaces(statements.substring(lastIndex, nextStatement.lastIndex - 1));
          res.push(statement.left);
          res.push(transformStatement(statement.middle));
          res.push(statement.right + ";");
        }
        lastIndex = nextStatement.lastIndex;
      }
      var statementsTail = trimSpaces(statements.substring(lastIndex));
      res.push(statementsTail.left);
      if(statementsTail.middle !== "") {
        res.push(transformStatement(statementsTail.middle));
        res.push(";" + statementsTail.right);
      }
      return res;
    };

    function getLocalNames(statements) {
      var localNames = [];
      for(var i=0,l=statements.length;i<l;++i) {
        var statement = statements[i];
        if(statement instanceof AstVar) {
          localNames = localNames.concat(statement.getNames());
        } else if(statement instanceof AstForStatement &&
          statement.argument.initStatement instanceof AstVar) {
          localNames = localNames.concat(statement.argument.initStatement.getNames());
        } else if(statement instanceof AstInnerInterface || statement instanceof AstInnerClass ||
          statement instanceof AstInterface || statement instanceof AstClass ||
          statement instanceof AstMethod || statement instanceof AstFunction) {
          localNames.push(statement.name);
        }
      }
      return appendToLookupTable({}, localNames);
    }

    function AstStatementsBlock(statements) {
      this.statements = statements;
    }
    AstStatementsBlock.prototype.toString = function() {
      var localNames = getLocalNames(this.statements);
      var oldContext = replaceContext;

      // replacing context only when necessary
      if(!isLookupTableEmpty(localNames)) {
        replaceContext = function (subject) {
          return localNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject);
        };
      }

      var result = "{\n" + this.statements.join('') + "\n}";
      replaceContext = oldContext;
      return result;
    };

    transformStatementsBlock = function(block) {
      var content = trimSpaces(block.substring(1, block.length - 1));
      return new AstStatementsBlock(transformStatements(content.middle));
    };

    function AstRoot(statements) {
      this.statements = statements;
    }
    AstRoot.prototype.toString = function() {
      var classes = [], otherStatements = [], statement;
      for (var i = 0, len = this.statements.length; i < len; ++i) {
        statement = this.statements[i];
        if (statement instanceof AstClass || statement instanceof AstInterface) {
          classes.push(statement);
        } else {
          otherStatements.push(statement);
        }
      }
      sortByWeight(classes);

      var localNames = getLocalNames(this.statements);
      replaceContext = function (subject) {
        var name = subject.name;
        if(localNames.hasOwnProperty(name)) {
          return name;
        }
        if(globalMembers.hasOwnProperty(name) ||
           PConstants.hasOwnProperty(name) ||
           defaultScope.hasOwnProperty(name)) {
          return "$p." + name;
        }
        return name;
      };
      var result = "// this code was autogenerated from PJS\n" +
        "(function($p) {\n" +
        classes.join('') + "\n" +
        otherStatements.join('') + "\n})";
      replaceContext = null;
      return result;
    };

    transformMain = function() {
      var statements = extractClassesAndMethods(atoms[0]);
      statements = statements.replace(/\bimport\s+[^;]+;/g, "");
      return new AstRoot( transformStatements(statements,
        transformGlobalMethod, transformGlobalClass) );
    };

    function generateMetadata(ast) {
      var globalScope = {};
      var id, class_;
      for(id in declaredClasses) {
        if(declaredClasses.hasOwnProperty(id)) {
          class_ = declaredClasses[id];
          var scopeId = class_.scopeId, name = class_.name;
          if(scopeId) {
            var scope = declaredClasses[scopeId];
            class_.scope = scope;
            if(scope.inScope === undef) {
              scope.inScope = {};
            }
            scope.inScope[name] = class_;
          } else {
            globalScope[name] = class_;
          }
        }
      }

      function findInScopes(class_, name) {
        var parts = name.split('.');
        var currentScope = class_.scope, found;
        while(currentScope) {
          if(currentScope.hasOwnProperty(parts[0])) {
            found = currentScope[parts[0]]; break;
          }
          currentScope = currentScope.scope;
        }
        if(found === undef) {
          found = globalScope[parts[0]];
        }
        for(var i=1,l=parts.length;i<l && found;++i) {
          found = found.inScope[parts[i]];
        }
        return found;
      }

      for(id in declaredClasses) {
        if(declaredClasses.hasOwnProperty(id)) {
          class_ = declaredClasses[id];
          var baseClassName = class_.body.baseClassName;
          if(baseClassName) {
            var parent = findInScopes(class_, baseClassName);
            if (parent) {
              class_.base = parent;
              if (!parent.derived) {
                parent.derived = [];
              }
              parent.derived.push(class_);
            }
          }
          var interfacesNames = class_.body.interfacesNames,
            interfaces = [], i, l;
          if (interfacesNames && interfacesNames.length > 0) {
            for (i = 0, l = interfacesNames.length; i < l; ++i) {
              var interface_ = findInScopes(class_, interfacesNames[i]);
              interfaces.push(interface_);
              if (!interface_) {
                continue;
              }
              if (!interface_.derived) {
                interface_.derived = [];
              }
              interface_.derived.push(class_);
            }
            if (interfaces.length > 0) {
              class_.interfaces = interfaces;
            }
          }
        }
      }
    }

    function setWeight(ast) {
      var queue = [], tocheck = {};
      var id, scopeId, class_;
      // queue most inner and non-inherited
      for (id in declaredClasses) {
        if (declaredClasses.hasOwnProperty(id)) {
          class_ = declaredClasses[id];
          if (!class_.inScope && !class_.derived) {
            queue.push(id);
            class_.weight = 0;
          } else {
            var dependsOn = [];
            if (class_.inScope) {
              for (scopeId in class_.inScope) {
                if (class_.inScope.hasOwnProperty(scopeId)) {
                  dependsOn.push(class_.inScope[scopeId]);
                }
              }
            }
            if (class_.derived) {
              dependsOn = dependsOn.concat(class_.derived);
            }
            tocheck[id] = dependsOn;
          }
        }
      }
      function removeDependentAndCheck(targetId, from) {
        var dependsOn = tocheck[targetId];
        if (!dependsOn) {
          return false; // no need to process
        }
        var i = dependsOn.indexOf(from);
        if (i < 0) {
          return false;
        }
        dependsOn.splice(i, 1);
        if (dependsOn.length > 0) {
          return false;
        }
        delete tocheck[targetId];
        return true;
      }
      while (queue.length > 0) {
        id = queue.shift();
        class_ = declaredClasses[id];
        if (class_.scopeId && removeDependentAndCheck(class_.scopeId, class_)) {
          queue.push(class_.scopeId);
          declaredClasses[class_.scopeId].weight = class_.weight + 1;
        }
        if (class_.base && removeDependentAndCheck(class_.base.classId, class_)) {
          queue.push(class_.base.classId);
          class_.base.weight = class_.weight + 1;
        }
        if (class_.interfaces) {
          var i, l;
          for (i = 0, l = class_.interfaces.length; i < l; ++i) {
            if (!class_.interfaces[i] ||
                !removeDependentAndCheck(class_.interfaces[i].classId, class_)) {
              continue;
            }
            queue.push(class_.interfaces[i].classId);
            class_.interfaces[i].weight = class_.weight + 1;
          }
        }
      }
    }

    var transformed = transformMain();
    generateMetadata(transformed);
    setWeight(transformed);

    var redendered = transformed.toString();

    // remove empty extra lines with space
    redendered = redendered.replace(/\s*\n(?:[\t ]*\n)+/g, "\n\n");

    // convert character codes to characters
    redendered = redendered.replace(/__x([0-9A-F]{4})/g, function(all, hexCode) {
      return String.fromCharCode(parseInt(hexCode,16));
    });

    return injectStrings(redendered, strings);
  }// Parser ends

  function preprocessCode(aCode, sketch) {
    // Parse out @pjs directive, if any.
    var dm = new RegExp(/\/\*\s*@pjs\s+((?:[^\*]|\*+[^\*\/])*)\*\//g).exec(aCode);
    if (dm && dm.length === 2) {
      // masks contents of a JSON to be replaced later
      // to protect the contents from further parsing
      var jsonItems = [],
          directives = dm.splice(1, 2)[0].replace(/\{([\s\S]*?)\}/g, (function() {
            return function(all, item) {
              jsonItems.push(item);
              return "{" + (jsonItems.length-1) + "}";
            };
          }())).replace('\n', '').replace('\r', '').split(";");

      // We'll L/RTrim, and also remove any surrounding double quotes (e.g., just take string contents)
      var clean = function(s) {
        return s.replace(/^\s*["']?/, '').replace(/["']?\s*$/, '');
      };

      for (var i = 0, dl = directives.length; i < dl; i++) {
        var pair = directives[i].split('=');
        if (pair && pair.length === 2) {
          var key = clean(pair[0]),
              value = clean(pair[1]),
              list = [];
          // A few directives require work beyond storying key/value pairings
          if (key === "preload") {
            list = value.split(',');
            // All pre-loaded images will get put in imageCache, keyed on filename
            for (var j = 0, jl = list.length; j < jl; j++) {
              var imageName = clean(list[j]);
              sketch.imageCache.add(imageName);
            }
          // fonts can be declared as a string containing a url,
          // or a JSON object, containing a font name, and a url
          } else if (key === "font") {
            list = value.split(",");
            for (var x = 0, xl = list.length; x < xl; x++) {
              var fontName = clean(list[x]),
                  index = /^\{(\d*?)\}$/.exec(fontName);
              // if index is not null, send JSON, otherwise, send string
              PFont.preloading.add(index ? JSON.parse("{" + jsonItems[index[1]] + "}") : fontName);
            }
          } else if (key === "pauseOnBlur") {
            sketch.options.pauseOnBlur = value === "true";
          } else if (key === "globalKeyEvents") {
            sketch.options.globalKeyEvents = value === "true";
          } else if (key.substring(0, 6) === "param-") {
            sketch.params[key.substring(6)] = value;
          } else {
            sketch.options[key] = value;
          }
        }
      }
    }
    return aCode;
  }

  // Parse/compiles Processing (Java-like) syntax to JavaScript syntax
  Processing.compile = function(pdeCode) {
    var sketch = new Processing.Sketch();
    var code = preprocessCode(pdeCode, sketch);
    var compiledPde = parseProcessing(code);
    sketch.sourceCode = compiledPde;
    return sketch;
  };

  var PjsConsole = require("../Helpers/PjsConsole");
  Processing.logger = new PjsConsole(document);

  // done
  return Processing;
};