<commit_before>/*
 * ObjJAcornCompiler.js
 * Objective-J
 *
 * Created by <NAME>.
 * Copyright 2013, <NAME>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

var Scope = function(prev, base)
{
    this.vars = Object.create(null);
    if (base) for (var key in base) this[key] = base[key];
    this.prev = prev;
    if (prev) this.compiler = prev.compiler;
}

Scope.prototype.compiler = function()
{
    return this.compiler;
}

Scope.prototype.rootScope = function()
{
    return this.prev ? this.prev.rootScope() : this;
}

Scope.prototype.isRootScope = function()
{
    return !this.prev;
}

Scope.prototype.currentClassName = function()
{
    return this.classDef ? this.classDef.className : this.prev ? this.prev.currentClassName() : null;
}

Scope.prototype.getIvarForCurrentClass = function(/* String */ ivarName)
{
    if (this.ivars)
    {
        var ivar = this.ivars[ivarName];
        if (ivar)
            return ivar;
    }

    var prev = this.prev;

    // Stop at the class declaration
    if (prev && !this.classDef)
        return prev.getIvarForCurrentClass(ivarName);

    return null;
}

Scope.prototype.getLvar = function(/* String */ lvarName, /* BOOL */ stopAtMethod)
{
    if (this.vars)
    {
        var lvar = this.vars[lvarName];
        if (lvar)
            return lvar;
    }

    var prev = this.prev;

    // Stop at the method declaration
    if (prev && (!stopAtMethod || !this.methodType))
        return prev.getLvar(lvarName, stopAtMethod);

    return null;
}

Scope.prototype.currentMethodType = function()
{
    return this.methodType ? this.methodType : this.prev ? this.prev.currentMethodType() : null;
}

Scope.prototype.copyAddedSelfToIvarsToParent = function()
{
  if (this.prev && this.addedSelfToIvars) for (var key in this.addedSelfToIvars)
  {
    var addedSelfToIvar = this.addedSelfToIvars[key],
        scopeAddedSelfToIvar = (this.prev.addedSelfToIvars || (this.prev.addedSelfToIvars = Object.create(null)))[key] || (this.prev.addedSelfToIvars[key] = []);

    scopeAddedSelfToIvar.push.apply(scopeAddedSelfToIvar, addedSelfToIvar);   // Append at end in parent scope
  }
}

Scope.prototype.addMaybeWarning = function(warning)
{
    var rootScope = this.rootScope();

    (rootScope._maybeWarnings || (rootScope._maybeWarnings = [])).push(warning);
}

Scope.prototype.maybeWarnings = function()
{
    return this.rootScope()._maybeWarnings;
}

var GlobalVariableMaybeWarning = function(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code)
{
    this.message = createMessage(aMessage, node, code);
    this.node = node;
}

GlobalVariableMaybeWarning.prototype.checkIfWarning = function(/* Scope */ st)
{
    var identifier = this.node.name;
    return !st.getLvar(identifier) && typeof global[identifier] === "undefined" && typeof window[identifier] === "undefined" && !st.compiler.getClassDef(identifier);
}

// This is for IE8 support. It doesn't have the Object.create function
if (typeof Object.create !== 'function')
{
   Object.create = function (o)
   {
       function F() {}
       F.prototype = o;
       return new F();
   };
}

var currentCompilerFlags = "";

var reservedIdentifiers = exports.acorn.makePredicate("self _cmd undefined localStorage arguments");

var wordPrefixOperators = exports.acorn.makePredicate("delete in instanceof new typeof void");

var isLogicalBinary = exports.acorn.makePredicate("LogicalExpression BinaryExpression");
var isInInstanceof = exports.acorn.makePredicate("in instanceof");

var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs)
{
    this.source = aString;
    this.URL = new CFURL(aURL);
	this.pass = pass;
	this.jsBuffer = new StringBuffer();
    this.imBuffer = null;
    this.cmBuffer = null;
    this.warnings = [];

    try {
        this.tokens = exports.acorn.parse(aString);
    }
    catch (e) {
        if (e.lineStart)
        {
            var message = this.prettifyMessage(e, "ERROR");
#ifdef BROWSER
            console.log(message);
#else
            print(message);
#endif
        }
        throw e;
    }

    this.dependencies = [];
    this.flags = flags | ObjJAcornCompiler.Flags.IncludeDebugSymbols;
    this.classDefs = classDefs ? classDefs : Object.create(null);
    this.lastPos = 0;
    if (currentCompilerFlags & ObjJAcornCompiler.Flags.Generate)
        this.generate = true;

    compile(this.tokens, new Scope(null ,{ compiler: this }), pass === 2 ? pass2 : pass1);
}

exports.ObjJAcornCompiler = ObjJAcornCompiler;

exports.ObjJAcornCompiler.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags)
{
    ObjJAcornCompiler.currentCompileFile = aURL;
    return new ObjJAcornCompiler(aString, aURL, flags, 2).executable();
}

exports.ObjJAcornCompiler.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, classDefs)
{
    return new ObjJAcornCompiler(aString, aURL, flags, 2, classDefs).IMBuffer();
}

exports.ObjJAcornCompiler.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags)
{
    ObjJAcornCompiler.currentCompileFile = aURL;
    return new ObjJAcornCompiler(aString, aURL, flags, 1).executable();
}

ObjJAcornCompiler.prototype.compilePass2 = function()
{
    ObjJAcornCompiler.currentCompileFile = this.URL;
	this.pass = 2;
	this.jsBuffer = new StringBuffer();
    this.warnings = [];
    compile(this.tokens, new Scope(null ,{ compiler: this }), pass2);

    for (var i = 0; i < this.warnings.length; i++)
    {
       var message = this.prettifyMessage(this.warnings[i], "WARNING");
#ifdef BROWSER
        console.log(message);
#else
        print(message);
#endif
    }

	return this.jsBuffer.toString();
}

var currentCompilerFlags = "";

exports.setCurrentCompilerFlags = function(/*String*/ compilerFlags)
{
    currentCompilerFlags = compilerFlags;
}

exports.currentCompilerFlags = function(/*String*/ compilerFlags)
{
    return currentCompilerFlags;
}

ObjJAcornCompiler.Flags = { };

ObjJAcornCompiler.Flags.IncludeDebugSymbols   = 1 << 0;
ObjJAcornCompiler.Flags.IncludeTypeSignatures = 1 << 1;
ObjJAcornCompiler.Flags.Generate              = 1 << 2;

ObjJAcornCompiler.prototype.addWarning = function(/* Warning */ aWarning)
{
    this.warnings.push(aWarning);
}

ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* Scope */ scope)
{
    var ivar = scope.getIvarForCurrentClass(ivarName);

    if (ivar)
        return ivar;

    var c = this.getClassDef(scope.currentClassName());

    while (c)
    {
        var ivars = c.ivars;
        if (ivars)
        {
            var ivarDef = ivars[ivarName];
            if (ivarDef)
                return ivarDef;
        }
        c = this.getClassDef(c.superClassName);
    }
}

ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName)
{
    if (!aClassName) return null;

	var	c = this.classDefs[aClassName];

	if (c) return c;

	if (objj_getClass)
	{
		var aClass = objj_getClass(aClassName);
		if (aClass)
		{
			var ivars = class_copyIvarList(aClass),
				ivarSize = ivars.length,
				myIvars = Object.create(null),
				superClass = aClass.super_class;

			for (var i = 0; i < ivarSize; i++)
			{
				var ivar = ivars[i];

			    myIvars[ivar.name] = {"type": ivar.type, "name": ivar.name};
			}
			c = {"className": aClassName, "ivars": myIvars};

			if (superClass)
				c.superClassName = superClass.name;
			this.classDefs[aClassName] = c;
			return c;
		}
	}

	return null;
//	classDef = {"className": className, "superClassName": superClassName, "ivars": Object.create(null), "methods": Object.create(null)};
}

ObjJAcornCompiler.prototype.executable = function()
{
    if (!this._executable)
        this._executable = new Executable(this.jsBuffer ? this.jsBuffer.toString() : null, this.dependencies, this.URL, null, this);
    return this._executable;
}

ObjJAcornCompiler.prototype.IMBuffer = function()
{
    return this.imBuffer;
}

ObjJAcornCompiler.prototype.JSBuffer = function()
{
    return this.jsBuffer;
}

ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage, /* String */ messageType)
{
    var line = this.source.substring(aMessage.lineStart, aMessage.lineEnd),
        message = "\n" + line;

    message += (new Array(aMessage.column + 1)).join(" ");
    message += (new Array(Math.min(1, line.length) + 1)).join("^") + "\n";
    message += messageType + " line " + aMessage.line + " in " + this.URL + ": " + aMessage.message;

    return message;
}

ObjJAcornCompiler.prototype.error_message = function(errorMessage, node)
{
    var pos = exports.acorn.getLineInfo(this.source, node.start),
        syntaxError = {message: errorMessage, line: pos.line, column: pos.column, lineStart: pos.lineStart, lineEnd: pos.lineEnd};

    return new SyntaxError(this.prettifyMessage(syntaxError, "ERROR"));
}

ObjJAcornCompiler.prototype.pushImport = function(url)
{
    if (!ObjJAcornCompiler.importStack) ObjJAcornCompiler.importStack = [];  // This is used to keep track of imports. Each time the compiler imports a file the url is pushed here.

    ObjJAcornCompiler.importStack.push(url);
}

ObjJAcornCompiler.prototype.popImport = function()
{
    ObjJAcornCompiler.importStack.pop();
}

function createMessage(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code)
{
    var message = exports.acorn.getLineInfo(code, node.start);

    message.message = aMessage;

    return message;
}

function compile(node, state, visitor) {
    function c(node, st, override) {
        visitor[override || node.type](node, st, c);
    }
    c(node, state);
};

function isIdempotentExpression(node) {
    switch (node.type) {
        case "Literal":
        case "Identifier":
            return true;

        case "ArrayExpression":
            for (var i = 0; i < node.elements.length; ++i) {
                if (!isIdempotentExpression(node.elements[i]))
                    return false;
            }

            return true;

        case "DictionaryLiteral":
            for (var i = 0; i < node.keys.length; ++i) {
                if (!isIdempotentExpression(node.keys[i]))
                    return false;
                if (!isIdempotentExpression(node.values[i]))
                    return false;
            }

            return true;

        case "ObjectExpression":
            for (var i = 0; i < node.properties.length; ++i)
                if (!isIdempotentExpression(node.properties[i].value))
                    return false;

            return true;

        case "FunctionExpression":
            for (var i = 0; i < node.params.length; ++i)
                if (!isIdempotentExpression(node.params[i]))
                    return false;

            return true;

        case "SequenceExpression":
            for (var i = 0; i < node.expressions.length; ++i)
                if (!isIdempotentExpression(node.expressions[i]))
                    return false;

            return true;

        case "UnaryExpression":
            return isIdempotentExpression(node.argument);

        case "BinaryExpression":
            return isIdempotentExpression(node.left) && isIdempotentExpression(node.right);

        case "ConditionalExpression":
            return isIdempotentExpression(node.test) && isIdempotentExpression(node.consequent) && isIdempotentExpression(node.alternate);

        case "MemberExpression":
            return isIdempotentExpression(node.object) && (!node.computed || isIdempotentExpression(node.property));

        case "Dereference":
            return isIdempotentExpression(node.expr);

        case "Reference":
            return isIdempotentExpression(node.element);

        default:
            return false;
    }
}

// We do not allow dereferencing of expressions with side effects because we might need to evaluate the expression twice in certain uses of deref, which is not obvious when you look at the deref operator in plain code.
function checkCanDereference(st, node) {
    if (!isIdempotentExpression(node))
        throw st.compiler.error_message("Dereference of expression with side effects", node);
}

// Surround expression with parentheses
function surroundExpression(c) {
    return function(node, st, override) {
      CONCAT(st.compiler.jsBuffer, "(");
      c(node, st, override);
      CONCAT(st.compiler.jsBuffer, ")");
    }
}

var operatorPrecedence = {
    // MemberExpression
    // These two are never used as they are a MemberExpression with the attribute 'computed' which tells what operator it uses.
    //".": 0, "[]": 0,
    // NewExpression
    // This is never used.
    //"new": 1,
    // All these are UnaryExpression or UpdateExpression and never used.
    //"!": 2, "~": 2, "-": 2, "+": 2, "++": 2, "--": 2, "typeof": 2, "void": 2, "delete": 2,
    // BinaryExpression
    "*": 3, "/": 3, "%": 3,
    "+": 4, "-": 4,
    "<<": 5, ">>": 5, ">>>": 5,
    "<": 6, "<=": 6, ">": 6, ">=": 6, "in": 6, "instanceof": 6,
    "==": 7, "!=": 7, "===": 7, "!==": 7,
    "&": 8,
    "^": 9,
    "|": 10,
    // LogicalExpression
    "&&": 11,
    "||": 12
    // ConditionalExpression
    // AssignmentExpression
}

var expressionTypePrecedence = {
    MemberExpression: 0,
    CallExpression: 1,
    NewExpression: 2,
    FunctionExpression: 3,
    UnaryExpression: 4, UpdateExpression: 4,
    BinaryExpression: 5,
    LogicalExpression: 6,
    ConditionalExpression: 7,
    AssignmentExpression: 8
}

// Returns true if subNode has higher precedence the the root node.
// Special case when operator is '+': It can be a string append to a numeric addition. Now we just return true to add extra parentheses.
function nodePrecedence(node, subNode) {
    var nodeType = node.type,
        nodePrecedence = expressionTypePrecedence[nodeType] || -1,
        subNodePrecedence = expressionTypePrecedence[subNode.type] || -1,
        nodeOperatorPrecedence,
        subNodeOperatorPrecedence;
    return nodePrecedence < subNodePrecedence || (nodePrecedence === subNodePrecedence && isLogicalBinary(nodeType) && ((nodeOperatorPrecedence = operatorPrecedence[node.operator]) <= (subNodeOperatorPrecedence = operatorPrecedence[subNode.operator])) || (node.operator === "+" && nodeOperatorPrecedence === subNodeOperatorPrecedence));
}

var pass1 = exports.acorn.walk.make({
ImportStatement: function(node, st, c) {
    var urlString = node.filename.value;

    st.compiler.dependencies.push(new FileDependency(new CFURL(urlString), node.localfilepath));
}
});

var pass2 = exports.acorn.walk.make({
Program: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    for (var i = 0; i < node.body.length; ++i) {
      c(node.body[i], st, "Statement");
    }
    if (!generate) CONCAT(compiler.jsBuffer,compiler.source.substring(compiler.lastPos, node.end));

    // Check maybe warnings
    var maybeWarnings = st.maybeWarnings();
    if (maybeWarnings) for (var i = 0; i < maybeWarnings.length; i++) {
        var maybeWarning = maybeWarnings[i];
        if (maybeWarning.checkIfWarning(st)) {
            compiler.addWarning(maybeWarning.message);
        }
    }
},
BlockStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "{\n");
    for (var i = 0; i < node.body.length; ++i) {
      c(node.body[i], st, "Statement");
    }
    if (generate) CONCAT(compiler.jsBuffer, "}\n");
},
ExpressionStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    c(node.expression, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ";\n");
},
IfStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "if (");
    c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.consequent, st, "Statement");
    if (node.alternate) {
      if (generate) CONCAT(compiler.jsBuffer, "else ");
      c(node.alternate, st, "Statement");
    }
},
LabeledStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) {
      CONCAT(compiler.jsBuffer, node.label.name);
      CONCAT(compiler.jsBuffer, ": ");
    }
    c(node.body, st, "Statement");
},
BreakStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) {
      if (node.label) {
        CONCAT(compiler.jsBuffer, "break ");
        CONCAT(compiler.jsBuffer, node.label.name);
        CONCAT(compiler.jsBuffer, ";\n");
      } else
        CONCAT(compiler.jsBuffer, "break;\n");
    }
},
ContinueStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) {
      if (node.label) {
        CONCAT(compiler.jsBuffer, "continue ");
        CONCAT(compiler.jsBuffer, node.label.name);
        CONCAT(compiler.jsBuffer, ";\n");
      } else
        CONCAT(compiler.jsBuffer, "continue;\n");
    }
},
WithStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "with(");
    c(node.object, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.body, st, "Statement");
},
SwitchStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "switch(");
    c(node.discriminant, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") {\n");
    for (var i = 0; i < node.cases.length; ++i) {
      var cs = node.cases[i];
      if (cs.test) {
        if (generate) CONCAT(compiler.jsBuffer, "case ");
        c(cs.test, st, "Expression");
        if (generate) CONCAT(compiler.jsBuffer, ":\n");
      } else
        if (generate) CONCAT(compiler.jsBuffer, "default: ");
      for (var j = 0; j < cs.consequent.length; ++j)
        c(cs.consequent[j], st, "Statement");
    }
    if (generate) CONCAT(compiler.jsBuffer, "}\n");
},
ReturnStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "return");
    if (node.argument) {
      if (generate) CONCAT(compiler.jsBuffer, " ");
      c(node.argument, st, "Expression");
    }
    if (generate) CONCAT(compiler.jsBuffer, ";\n");
},
ThrowStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "throw ");
    c(node.argument, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ";\n");
},
TryStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "try");
      c(node.block, st, "Statement");
    for (var i = 0; i < node.handlers.length; ++i) {
      var handler = node.handlers[i], inner = new Scope(st),
          param = handler.param,
          name = param.name;
      inner.vars[name] = {type: "catch clause", node: param};
      if (generate) CONCAT(compiler.jsBuffer, "catch(");
      if (generate) CONCAT(compiler.jsBuffer, name);
      if (generate) CONCAT(compiler.jsBuffer, ")");
      c(handler.body, inner, "ScopeBody");
      inner.copyAddedSelfToIvarsToParent();
    }
    if (node.finalizer) {
      if (generate) CONCAT(compiler.jsBuffer, "finally");
      c(node.finalizer, st, "Statement");
    }
},
WhileStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "while(");
    c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ")");
    c(node.body, st, "Statement");
},
DoWhileStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "do ");
    c(node.body, st, "Statement");
    if (generate) CONCAT(compiler.jsBuffer, "while(");
    c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ");\n");
},
ForStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "for(");
    if (node.init) c(node.init, st, "ForInit");
    if (generate) CONCAT(compiler.jsBuffer, "; ");
    if (node.test) c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, "; ");
    if (node.update) c(node.update, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.body, st, "Statement");
},
ForInStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "for(");
    c(node.left, st, "ForInit");
    if (generate) CONCAT(compiler.jsBuffer, " in ");
    c(node.right, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.body, st, "Statement");
},
ForInit: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (node.type === "VariableDeclaration") {
        st.isFor = true;
        c(node, st);
        delete st.isFor;
    } else
      c(node, st, "Expression");
},
DebuggerStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) CONCAT(compiler.jsBuffer, "debugger;\n");
},
Function: function(node, st, c) {
  var compiler = st.compiler,
      generate = compiler.generate,
      inner = new Scope(st);
  for (var i = 0; i < node.params.length; ++i)
    inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
  if (node.id) {
    var decl = node.type == "FunctionDeclaration";
    (decl ? st : inner).vars[node.id.name] =
      {type: decl ? "function" : "function name", node: node.id};
    if (generate) {
      CONCAT(compiler.jsBuffer, node.id.name);
      CONCAT(compiler.jsBuffer, " = ");
    } else {
      CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.start));
      CONCAT(compiler.jsBuffer, node.id.name);
      CONCAT(compiler.jsBuffer, " = function");
      compiler.lastPos = node.id.end;
    }
  }
  if (generate) {
    CONCAT(compiler.jsBuffer, "function(");
    for (var i = 0; i < node.params.length; ++i) {
      if (i)
        CONCAT(compiler.jsBuffer, ", ");
      CONCAT(compiler.jsBuffer, node.params[i].name);
    }
    CONCAT(compiler.jsBuffer, ")");
  }
  c(node.body, inner, "ScopeBody");
  inner.copyAddedSelfToIvarsToParent();
},
VariableDeclaration: function(node, st, c) {
  var compiler = st.compiler,
      generate = compiler.generate;
  if (generate) CONCAT(compiler.jsBuffer, "var ");
  for (var i = 0; i < node.declarations.length; ++i) {
    var decl = node.declarations[i],
        identifier = decl.id.name;
    if (i !== 0)
      if (generate) CONCAT(compiler.jsBuffer, ", ");
    st.vars[identifier] = {type: "var", node: decl.id};
    if (generate) CONCAT(compiler.jsBuffer, identifier);
    if (decl.init) {
      if (generate) CONCAT(compiler.jsBuffer, " = ");
      c(decl.init, st, "Expression");
    }
    // FIXME: Extract to function
    if (st.addedSelfToIvars) {
      var addedSelfToIvar = st.addedSelfToIvars[identifier];
      if (addedSelfToIvar) {
        var buffer = st.compiler.jsBuffer.atoms;
        for (var i = 0; i < addedSelfToIvar.length; i++) {
          var dict = addedSelfToIvar[i];
          buffer[dict.index] = "";
          compiler.addWarning(createMessage("Local declaration of '" + identifier + "' hides instance variable", dict.node, compiler.source));
        }
        st.addedSelfToIvars[identifier] = [];
      }
    }
  }
  if (generate && !st.isFor) CONCAT(compiler.jsBuffer, ";\n"); // Don't add ';' if this is a for statement but do it if this is a statement
},
ThisExpression: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) CONCAT(compiler.jsBuffer, "this");
},
ArrayExpression: function(node, st, c) {
  var compiler = st.compiler,
      generate = compiler.generate;
  if (generate) CONCAT(compiler.jsBuffer, "[");
    for (var i = 0; i < node.elements.length; ++i) {
      var elt = node.elements[i];
      if (i !== 0)
          if (generate) CONCAT(compiler.jsBuffer, ", ");

      if (elt) c(elt, st, "Expression");
    }
  if (generate) CONCAT(compiler.jsBuffer, "]");
},
ObjectExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "{");
    for (var i = 0; i < node.properties.length; ++i)
    {
        var prop = node.properties[i];
        if (generate) {
          if (i)
            CONCAT(compiler.jsBuffer, ", ");
          st.isPropertyKey = true;
          c(prop.key, st, "Expression");
          delete st.isPropertyKey;
          CONCAT(compiler.jsBuffer, ": ");
        } else if (prop.key.raw && prop.key.raw.charAt(0) === "@") {
          CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, prop.key.start));
          compiler.lastPos = prop.key.start + 1;
        }

        c(prop.value, st, "Expression");
    }
    if (generate) CONCAT(compiler.jsBuffer, "}");
},
SequenceExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "(");
    for (var i = 0; i < node.expressions.length; ++i) {
      if (generate && i !== 0)
        CONCAT(compiler.jsBuffer, ", ");
      c(node.expressions[i], st, "Expression");
    }
    if (generate) CONCAT(compiler.jsBuffer, ")");
},
UnaryExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate,
        argument = node.argument;
    if (generate) {
      if (node.prefix) {
        CONCAT(compiler.jsBuffer, node.operator);
        if (wordPrefixOperators(node.operator))
          CONCAT(compiler.jsBuffer, " ");
        (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression");
      } else {
        (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression");
        CONCAT(compiler.jsBuffer, node.operator);
      }
    } else {
      c(argument, st, "Expression");
    }
},
UpdateExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (node.argument.type === "Dereference") {
        checkCanDereference(st, node.argument);

        // @deref(x)++ and ++@deref(x) require special handling.
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.start));

        // Output the dereference function, "(...)(z)"
        CONCAT(compiler.jsBuffer, (node.prefix ? "" : "(") + "(");

        // The thing being dereferenced.
        if (!generate) compiler.lastPos = node.argument.expr.start;
        c(node.argument.expr, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.argument.expr.end));
        CONCAT(compiler.jsBuffer, ")(");

        if (!generate) compiler.lastPos = node.argument.start;
        c(node.argument, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.argument.end));
        CONCAT(compiler.jsBuffer, " " + node.operator.substring(0, 1) + " 1)" + (node.prefix ? "" : node.operator == '++' ? " - 1)" : " + 1)"));

        if (!generate) compiler.lastPos = node.end;
        return;
    }

    if (node.prefix) {
      if (generate) {
        CONCAT(compiler.jsBuffer, node.operator);
        if (wordPrefixOperators(node.operator))
          CONCAT(compiler.jsBuffer, " ");
      }
      (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression");
    } else {
      (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression");
      if (generate) CONCAT(compiler.jsBuffer, node.operator);
    }
},
BinaryExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate,
        operatorType = isInInstanceof(node.operator);
    (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression");
    if (operatorType) CONCAT(compiler.jsBuffer, " ");
    if (generate) CONCAT(compiler.jsBuffer, node.operator);
    if (operatorType) CONCAT(compiler.jsBuffer, " ");
    (generate && nodePrecedence(node, node.right) ? surroundExpression(c) : c)(node.right, st, "Expression");
},
LogicalExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, node.operator);
    (generate && nodePrecedence(node, node.right) ? surroundExpression(c) : c)(node.right, st, "Expression");
},
AssignmentExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate,
        saveAssignment = st.assignment;

    if (node.left.type === "Dereference") {
        checkCanDereference(st, node.left);

        // @deref(x) = z    -> x(z) etc
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.start));

        // Output the dereference function, "(...)(z)"
        CONCAT(compiler.jsBuffer, "(");
        // What's being dereferenced could itself be an expression, such as when dereferencing a deref.
        if (!generate) compiler.lastPos = node.left.expr.start;
        c(node.left.expr, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.left.expr.end));
        CONCAT(compiler.jsBuffer, ")(");

        // Now "(x)(...)". We have to manually expand +=, -=, *= etc.
        if (node.operator !== "=") {
            // Output the whole .left, not just .left.expr.
            if (!generate) compiler.lastPos = node.left.start;
            c(node.left, st, "Expression");
            if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.left.end));
            CONCAT(compiler.jsBuffer, " " + node.operator.substring(0, 1) + " ");
        }

        if (!generate) compiler.lastPos = node.right.start;
        c(node.right, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.right.end));
        CONCAT(compiler.jsBuffer, ")");

        if (!generate) compiler.lastPos = node.end;

        return;
    }

    var saveAssignment = st.assignment;
    st.assignment = true;
    (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, node.operator);
    st.assignment = saveAssignment;
    (generate && nodePrecedence(node, node.right) ? surroundExpression(c) : c)(node.right, st, "Expression");
    if (st.isRootScope() && node.left.type === "Identifier" && !st.getLvar(node.left.name))
        st.vars[node.left.name] = {type: "global", node: node.left};
},
ConditionalExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    (generate && nodePrecedence(node, node.test) ? surroundExpression(c) : c)(node.test, st, "Expression");
    if (generate)
      CONCAT(compiler.jsBuffer, " ? ");
    c(node.consequent, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, " : ");
    c(node.alternate, st, "Expression");
},
NewExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "new ");
    (generate && nodePrecedence(node, node.callee) ? surroundExpression(c) : c)(node.callee, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, "(");
    if (node.arguments) {<commit_msg>Fixed: Does not add extra parentheses for expression operators with the same precedence<commit_after>/*
 * ObjJAcornCompiler.js
 * Objective-J
 *
 * Created by <NAME>.
 * Copyright 2013, <NAME>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

var Scope = function(prev, base)
{
    this.vars = Object.create(null);
    if (base) for (var key in base) this[key] = base[key];
    this.prev = prev;
    if (prev) this.compiler = prev.compiler;
}

Scope.prototype.compiler = function()
{
    return this.compiler;
}

Scope.prototype.rootScope = function()
{
    return this.prev ? this.prev.rootScope() : this;
}

Scope.prototype.isRootScope = function()
{
    return !this.prev;
}

Scope.prototype.currentClassName = function()
{
    return this.classDef ? this.classDef.className : this.prev ? this.prev.currentClassName() : null;
}

Scope.prototype.getIvarForCurrentClass = function(/* String */ ivarName)
{
    if (this.ivars)
    {
        var ivar = this.ivars[ivarName];
        if (ivar)
            return ivar;
    }

    var prev = this.prev;

    // Stop at the class declaration
    if (prev && !this.classDef)
        return prev.getIvarForCurrentClass(ivarName);

    return null;
}

Scope.prototype.getLvar = function(/* String */ lvarName, /* BOOL */ stopAtMethod)
{
    if (this.vars)
    {
        var lvar = this.vars[lvarName];
        if (lvar)
            return lvar;
    }

    var prev = this.prev;

    // Stop at the method declaration
    if (prev && (!stopAtMethod || !this.methodType))
        return prev.getLvar(lvarName, stopAtMethod);

    return null;
}

Scope.prototype.currentMethodType = function()
{
    return this.methodType ? this.methodType : this.prev ? this.prev.currentMethodType() : null;
}

Scope.prototype.copyAddedSelfToIvarsToParent = function()
{
  if (this.prev && this.addedSelfToIvars) for (var key in this.addedSelfToIvars)
  {
    var addedSelfToIvar = this.addedSelfToIvars[key],
        scopeAddedSelfToIvar = (this.prev.addedSelfToIvars || (this.prev.addedSelfToIvars = Object.create(null)))[key] || (this.prev.addedSelfToIvars[key] = []);

    scopeAddedSelfToIvar.push.apply(scopeAddedSelfToIvar, addedSelfToIvar);   // Append at end in parent scope
  }
}

Scope.prototype.addMaybeWarning = function(warning)
{
    var rootScope = this.rootScope();

    (rootScope._maybeWarnings || (rootScope._maybeWarnings = [])).push(warning);
}

Scope.prototype.maybeWarnings = function()
{
    return this.rootScope()._maybeWarnings;
}

var GlobalVariableMaybeWarning = function(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code)
{
    this.message = createMessage(aMessage, node, code);
    this.node = node;
}

GlobalVariableMaybeWarning.prototype.checkIfWarning = function(/* Scope */ st)
{
    var identifier = this.node.name;
    return !st.getLvar(identifier) && typeof global[identifier] === "undefined" && typeof window[identifier] === "undefined" && !st.compiler.getClassDef(identifier);
}

// This is for IE8 support. It doesn't have the Object.create function
if (typeof Object.create !== 'function')
{
   Object.create = function (o)
   {
       function F() {}
       F.prototype = o;
       return new F();
   };
}

var currentCompilerFlags = "";

var reservedIdentifiers = exports.acorn.makePredicate("self _cmd undefined localStorage arguments");

var wordPrefixOperators = exports.acorn.makePredicate("delete in instanceof new typeof void");

var isLogicalBinary = exports.acorn.makePredicate("LogicalExpression BinaryExpression");
var isInInstanceof = exports.acorn.makePredicate("in instanceof");

var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs)
{
    this.source = aString;
    this.URL = new CFURL(aURL);
	this.pass = pass;
	this.jsBuffer = new StringBuffer();
    this.imBuffer = null;
    this.cmBuffer = null;
    this.warnings = [];

    try {
        this.tokens = exports.acorn.parse(aString);
    }
    catch (e) {
        if (e.lineStart)
        {
            var message = this.prettifyMessage(e, "ERROR");
#ifdef BROWSER
            console.log(message);
#else
            print(message);
#endif
        }
        throw e;
    }

    this.dependencies = [];
    this.flags = flags | ObjJAcornCompiler.Flags.IncludeDebugSymbols;
    this.classDefs = classDefs ? classDefs : Object.create(null);
    this.lastPos = 0;
    if (currentCompilerFlags & ObjJAcornCompiler.Flags.Generate)
        this.generate = true;

    compile(this.tokens, new Scope(null ,{ compiler: this }), pass === 2 ? pass2 : pass1);
}

exports.ObjJAcornCompiler = ObjJAcornCompiler;

exports.ObjJAcornCompiler.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags)
{
    ObjJAcornCompiler.currentCompileFile = aURL;
    return new ObjJAcornCompiler(aString, aURL, flags, 2).executable();
}

exports.ObjJAcornCompiler.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, classDefs)
{
    return new ObjJAcornCompiler(aString, aURL, flags, 2, classDefs).IMBuffer();
}

exports.ObjJAcornCompiler.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags)
{
    ObjJAcornCompiler.currentCompileFile = aURL;
    return new ObjJAcornCompiler(aString, aURL, flags, 1).executable();
}

ObjJAcornCompiler.prototype.compilePass2 = function()
{
    ObjJAcornCompiler.currentCompileFile = this.URL;
	this.pass = 2;
	this.jsBuffer = new StringBuffer();
    this.warnings = [];
    compile(this.tokens, new Scope(null ,{ compiler: this }), pass2);

    for (var i = 0; i < this.warnings.length; i++)
    {
       var message = this.prettifyMessage(this.warnings[i], "WARNING");
#ifdef BROWSER
        console.log(message);
#else
        print(message);
#endif
    }

	return this.jsBuffer.toString();
}

var currentCompilerFlags = "";

exports.setCurrentCompilerFlags = function(/*String*/ compilerFlags)
{
    currentCompilerFlags = compilerFlags;
}

exports.currentCompilerFlags = function(/*String*/ compilerFlags)
{
    return currentCompilerFlags;
}

ObjJAcornCompiler.Flags = { };

ObjJAcornCompiler.Flags.IncludeDebugSymbols   = 1 << 0;
ObjJAcornCompiler.Flags.IncludeTypeSignatures = 1 << 1;
ObjJAcornCompiler.Flags.Generate              = 1 << 2;

ObjJAcornCompiler.prototype.addWarning = function(/* Warning */ aWarning)
{
    this.warnings.push(aWarning);
}

ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* Scope */ scope)
{
    var ivar = scope.getIvarForCurrentClass(ivarName);

    if (ivar)
        return ivar;

    var c = this.getClassDef(scope.currentClassName());

    while (c)
    {
        var ivars = c.ivars;
        if (ivars)
        {
            var ivarDef = ivars[ivarName];
            if (ivarDef)
                return ivarDef;
        }
        c = this.getClassDef(c.superClassName);
    }
}

ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName)
{
    if (!aClassName) return null;

	var	c = this.classDefs[aClassName];

	if (c) return c;

	if (objj_getClass)
	{
		var aClass = objj_getClass(aClassName);
		if (aClass)
		{
			var ivars = class_copyIvarList(aClass),
				ivarSize = ivars.length,
				myIvars = Object.create(null),
				superClass = aClass.super_class;

			for (var i = 0; i < ivarSize; i++)
			{
				var ivar = ivars[i];

			    myIvars[ivar.name] = {"type": ivar.type, "name": ivar.name};
			}
			c = {"className": aClassName, "ivars": myIvars};

			if (superClass)
				c.superClassName = superClass.name;
			this.classDefs[aClassName] = c;
			return c;
		}
	}

	return null;
//	classDef = {"className": className, "superClassName": superClassName, "ivars": Object.create(null), "methods": Object.create(null)};
}

ObjJAcornCompiler.prototype.executable = function()
{
    if (!this._executable)
        this._executable = new Executable(this.jsBuffer ? this.jsBuffer.toString() : null, this.dependencies, this.URL, null, this);
    return this._executable;
}

ObjJAcornCompiler.prototype.IMBuffer = function()
{
    return this.imBuffer;
}

ObjJAcornCompiler.prototype.JSBuffer = function()
{
    return this.jsBuffer;
}

ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage, /* String */ messageType)
{
    var line = this.source.substring(aMessage.lineStart, aMessage.lineEnd),
        message = "\n" + line;

    message += (new Array(aMessage.column + 1)).join(" ");
    message += (new Array(Math.min(1, line.length) + 1)).join("^") + "\n";
    message += messageType + " line " + aMessage.line + " in " + this.URL + ": " + aMessage.message;

    return message;
}

ObjJAcornCompiler.prototype.error_message = function(errorMessage, node)
{
    var pos = exports.acorn.getLineInfo(this.source, node.start),
        syntaxError = {message: errorMessage, line: pos.line, column: pos.column, lineStart: pos.lineStart, lineEnd: pos.lineEnd};

    return new SyntaxError(this.prettifyMessage(syntaxError, "ERROR"));
}

ObjJAcornCompiler.prototype.pushImport = function(url)
{
    if (!ObjJAcornCompiler.importStack) ObjJAcornCompiler.importStack = [];  // This is used to keep track of imports. Each time the compiler imports a file the url is pushed here.

    ObjJAcornCompiler.importStack.push(url);
}

ObjJAcornCompiler.prototype.popImport = function()
{
    ObjJAcornCompiler.importStack.pop();
}

function createMessage(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code)
{
    var message = exports.acorn.getLineInfo(code, node.start);

    message.message = aMessage;

    return message;
}

function compile(node, state, visitor) {
    function c(node, st, override) {
        visitor[override || node.type](node, st, c);
    }
    c(node, state);
};

function isIdempotentExpression(node) {
    switch (node.type) {
        case "Literal":
        case "Identifier":
            return true;

        case "ArrayExpression":
            for (var i = 0; i < node.elements.length; ++i) {
                if (!isIdempotentExpression(node.elements[i]))
                    return false;
            }

            return true;

        case "DictionaryLiteral":
            for (var i = 0; i < node.keys.length; ++i) {
                if (!isIdempotentExpression(node.keys[i]))
                    return false;
                if (!isIdempotentExpression(node.values[i]))
                    return false;
            }

            return true;

        case "ObjectExpression":
            for (var i = 0; i < node.properties.length; ++i)
                if (!isIdempotentExpression(node.properties[i].value))
                    return false;

            return true;

        case "FunctionExpression":
            for (var i = 0; i < node.params.length; ++i)
                if (!isIdempotentExpression(node.params[i]))
                    return false;

            return true;

        case "SequenceExpression":
            for (var i = 0; i < node.expressions.length; ++i)
                if (!isIdempotentExpression(node.expressions[i]))
                    return false;

            return true;

        case "UnaryExpression":
            return isIdempotentExpression(node.argument);

        case "BinaryExpression":
            return isIdempotentExpression(node.left) && isIdempotentExpression(node.right);

        case "ConditionalExpression":
            return isIdempotentExpression(node.test) && isIdempotentExpression(node.consequent) && isIdempotentExpression(node.alternate);

        case "MemberExpression":
            return isIdempotentExpression(node.object) && (!node.computed || isIdempotentExpression(node.property));

        case "Dereference":
            return isIdempotentExpression(node.expr);

        case "Reference":
            return isIdempotentExpression(node.element);

        default:
            return false;
    }
}

// We do not allow dereferencing of expressions with side effects because we might need to evaluate the expression twice in certain uses of deref, which is not obvious when you look at the deref operator in plain code.
function checkCanDereference(st, node) {
    if (!isIdempotentExpression(node))
        throw st.compiler.error_message("Dereference of expression with side effects", node);
}

// Surround expression with parentheses
function surroundExpression(c) {
    return function(node, st, override) {
      CONCAT(st.compiler.jsBuffer, "(");
      c(node, st, override);
      CONCAT(st.compiler.jsBuffer, ")");
    }
}

var operatorPrecedence = {
    // MemberExpression
    // These two are never used as they are a MemberExpression with the attribute 'computed' which tells what operator it uses.
    //".": 0, "[]": 0,
    // NewExpression
    // This is never used.
    //"new": 1,
    // All these are UnaryExpression or UpdateExpression and never used.
    //"!": 2, "~": 2, "-": 2, "+": 2, "++": 2, "--": 2, "typeof": 2, "void": 2, "delete": 2,
    // BinaryExpression
    "*": 3, "/": 3, "%": 3,
    "+": 4, "-": 4,
    "<<": 5, ">>": 5, ">>>": 5,
    "<": 6, "<=": 6, ">": 6, ">=": 6, "in": 6, "instanceof": 6,
    "==": 7, "!=": 7, "===": 7, "!==": 7,
    "&": 8,
    "^": 9,
    "|": 10,
    // LogicalExpression
    "&&": 11,
    "||": 12
    // ConditionalExpression
    // AssignmentExpression
}

var expressionTypePrecedence = {
    MemberExpression: 0,
    CallExpression: 1,
    NewExpression: 2,
    FunctionExpression: 3,
    UnaryExpression: 4, UpdateExpression: 4,
    BinaryExpression: 5,
    LogicalExpression: 6,
    ConditionalExpression: 7,
    AssignmentExpression: 8
}

// Returns true if subNode has higher precedence the the root node.
// If the subNode is the right (as in left/right) subNode
function nodePrecedence(node, subNode, right) {
    var nodeType = node.type,
        nodePrecedence = expressionTypePrecedence[nodeType] || -1,
        subNodePrecedence = expressionTypePrecedence[subNode.type] || -1,
        nodeOperatorPrecedence,
        subNodeOperatorPrecedence;
    return nodePrecedence < subNodePrecedence || (nodePrecedence === subNodePrecedence && isLogicalBinary(nodeType) && ((nodeOperatorPrecedence = operatorPrecedence[node.operator]) < (subNodeOperatorPrecedence = operatorPrecedence[subNode.operator]) || (right && nodeOperatorPrecedence === subNodeOperatorPrecedence)));
}

var pass1 = exports.acorn.walk.make({
ImportStatement: function(node, st, c) {
    var urlString = node.filename.value;

    st.compiler.dependencies.push(new FileDependency(new CFURL(urlString), node.localfilepath));
}
});

var pass2 = exports.acorn.walk.make({
Program: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    for (var i = 0; i < node.body.length; ++i) {
      c(node.body[i], st, "Statement");
    }
    if (!generate) CONCAT(compiler.jsBuffer,compiler.source.substring(compiler.lastPos, node.end));

    // Check maybe warnings
    var maybeWarnings = st.maybeWarnings();
    if (maybeWarnings) for (var i = 0; i < maybeWarnings.length; i++) {
        var maybeWarning = maybeWarnings[i];
        if (maybeWarning.checkIfWarning(st)) {
            compiler.addWarning(maybeWarning.message);
        }
    }
},
BlockStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "{\n");
    for (var i = 0; i < node.body.length; ++i) {
      c(node.body[i], st, "Statement");
    }
    if (generate) CONCAT(compiler.jsBuffer, "}\n");
},
ExpressionStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    c(node.expression, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ";\n");
},
IfStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "if (");
    c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.consequent, st, "Statement");
    if (node.alternate) {
      if (generate) CONCAT(compiler.jsBuffer, "else ");
      c(node.alternate, st, "Statement");
    }
},
LabeledStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) {
      CONCAT(compiler.jsBuffer, node.label.name);
      CONCAT(compiler.jsBuffer, ": ");
    }
    c(node.body, st, "Statement");
},
BreakStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) {
      if (node.label) {
        CONCAT(compiler.jsBuffer, "break ");
        CONCAT(compiler.jsBuffer, node.label.name);
        CONCAT(compiler.jsBuffer, ";\n");
      } else
        CONCAT(compiler.jsBuffer, "break;\n");
    }
},
ContinueStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) {
      if (node.label) {
        CONCAT(compiler.jsBuffer, "continue ");
        CONCAT(compiler.jsBuffer, node.label.name);
        CONCAT(compiler.jsBuffer, ";\n");
      } else
        CONCAT(compiler.jsBuffer, "continue;\n");
    }
},
WithStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "with(");
    c(node.object, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.body, st, "Statement");
},
SwitchStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "switch(");
    c(node.discriminant, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") {\n");
    for (var i = 0; i < node.cases.length; ++i) {
      var cs = node.cases[i];
      if (cs.test) {
        if (generate) CONCAT(compiler.jsBuffer, "case ");
        c(cs.test, st, "Expression");
        if (generate) CONCAT(compiler.jsBuffer, ":\n");
      } else
        if (generate) CONCAT(compiler.jsBuffer, "default: ");
      for (var j = 0; j < cs.consequent.length; ++j)
        c(cs.consequent[j], st, "Statement");
    }
    if (generate) CONCAT(compiler.jsBuffer, "}\n");
},
ReturnStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "return");
    if (node.argument) {
      if (generate) CONCAT(compiler.jsBuffer, " ");
      c(node.argument, st, "Expression");
    }
    if (generate) CONCAT(compiler.jsBuffer, ";\n");
},
ThrowStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "throw ");
    c(node.argument, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ";\n");
},
TryStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "try");
      c(node.block, st, "Statement");
    for (var i = 0; i < node.handlers.length; ++i) {
      var handler = node.handlers[i], inner = new Scope(st),
          param = handler.param,
          name = param.name;
      inner.vars[name] = {type: "catch clause", node: param};
      if (generate) CONCAT(compiler.jsBuffer, "catch(");
      if (generate) CONCAT(compiler.jsBuffer, name);
      if (generate) CONCAT(compiler.jsBuffer, ")");
      c(handler.body, inner, "ScopeBody");
      inner.copyAddedSelfToIvarsToParent();
    }
    if (node.finalizer) {
      if (generate) CONCAT(compiler.jsBuffer, "finally");
      c(node.finalizer, st, "Statement");
    }
},
WhileStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "while(");
    c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ")");
    c(node.body, st, "Statement");
},
DoWhileStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "do ");
    c(node.body, st, "Statement");
    if (generate) CONCAT(compiler.jsBuffer, "while(");
    c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ");\n");
},
ForStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "for(");
    if (node.init) c(node.init, st, "ForInit");
    if (generate) CONCAT(compiler.jsBuffer, "; ");
    if (node.test) c(node.test, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, "; ");
    if (node.update) c(node.update, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.body, st, "Statement");
},
ForInStatement: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "for(");
    c(node.left, st, "ForInit");
    if (generate) CONCAT(compiler.jsBuffer, " in ");
    c(node.right, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, ") ");
    c(node.body, st, "Statement");
},
ForInit: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (node.type === "VariableDeclaration") {
        st.isFor = true;
        c(node, st);
        delete st.isFor;
    } else
      c(node, st, "Expression");
},
DebuggerStatement: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) CONCAT(compiler.jsBuffer, "debugger;\n");
},
Function: function(node, st, c) {
  var compiler = st.compiler,
      generate = compiler.generate,
      inner = new Scope(st);
  for (var i = 0; i < node.params.length; ++i)
    inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
  if (node.id) {
    var decl = node.type == "FunctionDeclaration";
    (decl ? st : inner).vars[node.id.name] =
      {type: decl ? "function" : "function name", node: node.id};
    if (generate) {
      CONCAT(compiler.jsBuffer, node.id.name);
      CONCAT(compiler.jsBuffer, " = ");
    } else {
      CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.start));
      CONCAT(compiler.jsBuffer, node.id.name);
      CONCAT(compiler.jsBuffer, " = function");
      compiler.lastPos = node.id.end;
    }
  }
  if (generate) {
    CONCAT(compiler.jsBuffer, "function(");
    for (var i = 0; i < node.params.length; ++i) {
      if (i)
        CONCAT(compiler.jsBuffer, ", ");
      CONCAT(compiler.jsBuffer, node.params[i].name);
    }
    CONCAT(compiler.jsBuffer, ")");
  }
  c(node.body, inner, "ScopeBody");
  inner.copyAddedSelfToIvarsToParent();
},
VariableDeclaration: function(node, st, c) {
  var compiler = st.compiler,
      generate = compiler.generate;
  if (generate) CONCAT(compiler.jsBuffer, "var ");
  for (var i = 0; i < node.declarations.length; ++i) {
    var decl = node.declarations[i],
        identifier = decl.id.name;
    if (i !== 0)
      if (generate) CONCAT(compiler.jsBuffer, ", ");
    st.vars[identifier] = {type: "var", node: decl.id};
    if (generate) CONCAT(compiler.jsBuffer, identifier);
    if (decl.init) {
      if (generate) CONCAT(compiler.jsBuffer, " = ");
      c(decl.init, st, "Expression");
    }
    // FIXME: Extract to function
    if (st.addedSelfToIvars) {
      var addedSelfToIvar = st.addedSelfToIvars[identifier];
      if (addedSelfToIvar) {
        var buffer = st.compiler.jsBuffer.atoms;
        for (var i = 0; i < addedSelfToIvar.length; i++) {
          var dict = addedSelfToIvar[i];
          buffer[dict.index] = "";
          compiler.addWarning(createMessage("Local declaration of '" + identifier + "' hides instance variable", dict.node, compiler.source));
        }
        st.addedSelfToIvars[identifier] = [];
      }
    }
  }
  if (generate && !st.isFor) CONCAT(compiler.jsBuffer, ";\n"); // Don't add ';' if this is a for statement but do it if this is a statement
},
ThisExpression: function(node, st, c) {
    var compiler = st.compiler;
    if (compiler.generate) CONCAT(compiler.jsBuffer, "this");
},
ArrayExpression: function(node, st, c) {
  var compiler = st.compiler,
      generate = compiler.generate;
  if (generate) CONCAT(compiler.jsBuffer, "[");
    for (var i = 0; i < node.elements.length; ++i) {
      var elt = node.elements[i];
      if (i !== 0)
          if (generate) CONCAT(compiler.jsBuffer, ", ");

      if (elt) c(elt, st, "Expression");
    }
  if (generate) CONCAT(compiler.jsBuffer, "]");
},
ObjectExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "{");
    for (var i = 0; i < node.properties.length; ++i)
    {
        var prop = node.properties[i];
        if (generate) {
          if (i)
            CONCAT(compiler.jsBuffer, ", ");
          st.isPropertyKey = true;
          c(prop.key, st, "Expression");
          delete st.isPropertyKey;
          CONCAT(compiler.jsBuffer, ": ");
        } else if (prop.key.raw && prop.key.raw.charAt(0) === "@") {
          CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, prop.key.start));
          compiler.lastPos = prop.key.start + 1;
        }

        c(prop.value, st, "Expression");
    }
    if (generate) CONCAT(compiler.jsBuffer, "}");
},
SequenceExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "(");
    for (var i = 0; i < node.expressions.length; ++i) {
      if (generate && i !== 0)
        CONCAT(compiler.jsBuffer, ", ");
      c(node.expressions[i], st, "Expression");
    }
    if (generate) CONCAT(compiler.jsBuffer, ")");
},
UnaryExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate,
        argument = node.argument;
    if (generate) {
      if (node.prefix) {
        CONCAT(compiler.jsBuffer, node.operator);
        if (wordPrefixOperators(node.operator))
          CONCAT(compiler.jsBuffer, " ");
        (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression");
      } else {
        (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression");
        CONCAT(compiler.jsBuffer, node.operator);
      }
    } else {
      c(argument, st, "Expression");
    }
},
UpdateExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (node.argument.type === "Dereference") {
        checkCanDereference(st, node.argument);

        // @deref(x)++ and ++@deref(x) require special handling.
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.start));

        // Output the dereference function, "(...)(z)"
        CONCAT(compiler.jsBuffer, (node.prefix ? "" : "(") + "(");

        // The thing being dereferenced.
        if (!generate) compiler.lastPos = node.argument.expr.start;
        c(node.argument.expr, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.argument.expr.end));
        CONCAT(compiler.jsBuffer, ")(");

        if (!generate) compiler.lastPos = node.argument.start;
        c(node.argument, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.argument.end));
        CONCAT(compiler.jsBuffer, " " + node.operator.substring(0, 1) + " 1)" + (node.prefix ? "" : node.operator == '++' ? " - 1)" : " + 1)"));

        if (!generate) compiler.lastPos = node.end;
        return;
    }

    if (node.prefix) {
      if (generate) {
        CONCAT(compiler.jsBuffer, node.operator);
        if (wordPrefixOperators(node.operator))
          CONCAT(compiler.jsBuffer, " ");
      }
      (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression");
    } else {
      (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression");
      if (generate) CONCAT(compiler.jsBuffer, node.operator);
    }
},
BinaryExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate,
        operatorType = isInInstanceof(node.operator);
    (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression");
    if (operatorType) CONCAT(compiler.jsBuffer, " ");
    if (generate) CONCAT(compiler.jsBuffer, node.operator);
    if (operatorType) CONCAT(compiler.jsBuffer, " ");
    (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression");
},
LogicalExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, node.operator);
    (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression");
},
AssignmentExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate,
        saveAssignment = st.assignment;

    if (node.left.type === "Dereference") {
        checkCanDereference(st, node.left);

        // @deref(x) = z    -> x(z) etc
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.start));

        // Output the dereference function, "(...)(z)"
        CONCAT(compiler.jsBuffer, "(");
        // What's being dereferenced could itself be an expression, such as when dereferencing a deref.
        if (!generate) compiler.lastPos = node.left.expr.start;
        c(node.left.expr, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.left.expr.end));
        CONCAT(compiler.jsBuffer, ")(");

        // Now "(x)(...)". We have to manually expand +=, -=, *= etc.
        if (node.operator !== "=") {
            // Output the whole .left, not just .left.expr.
            if (!generate) compiler.lastPos = node.left.start;
            c(node.left, st, "Expression");
            if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.left.end));
            CONCAT(compiler.jsBuffer, " " + node.operator.substring(0, 1) + " ");
        }

        if (!generate) compiler.lastPos = node.right.start;
        c(node.right, st, "Expression");
        if (!generate) CONCAT(compiler.jsBuffer, compiler.source.substring(compiler.lastPos, node.right.end));
        CONCAT(compiler.jsBuffer, ")");

        if (!generate) compiler.lastPos = node.end;

        return;
    }

    var saveAssignment = st.assignment;
    st.assignment = true;
    (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, node.operator);
    st.assignment = saveAssignment;
    (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression");
    if (st.isRootScope() && node.left.type === "Identifier" && !st.getLvar(node.left.name))
        st.vars[node.left.name] = {type: "global", node: node.left};
},
ConditionalExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    (generate && nodePrecedence(node, node.test) ? surroundExpression(c) : c)(node.test, st, "Expression");
    if (generate)
      CONCAT(compiler.jsBuffer, " ? ");
    c(node.consequent, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, " : ");
    c(node.alternate, st, "Expression");
},
NewExpression: function(node, st, c) {
    var compiler = st.compiler,
        generate = compiler.generate;
    if (generate) CONCAT(compiler.jsBuffer, "new ");
    (generate && nodePrecedence(node, node.callee) ? surroundExpression(c) : c)(node.callee, st, "Expression");
    if (generate) CONCAT(compiler.jsBuffer, "(");
    if (node.arguments) {
{'fraction_non_alphanumeric': 0.14695061445691168, 'fraction_numerical': 0.0036943744752308983, 'mean_word_length': 3.104900363454067, 'pattern_counts': {'":': 120, '<': 71, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6118794', 'n_tokens_mistral': 20986, 'n_tokens_neox': 20230, 'n_words': 4740}