<commit_before>/*
 * compiler.js
 *
 * Created by <NAME>.
 * Copyright 2013, <NAME>.
 *
 * Additional work by <NAME>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the MIT license (http://opensource.org/licenses/MIT).
 */

"use strict";

var acorn = require("objj-acorn"),
    chalk = require("chalk"),
    exceptions = require("./exceptions"),
    globals = require("./globals"),
    language = require("./language"),
    formats = require("./formats"),
    indentation = require("./indentation"),
    path = require("path"),
    Scope = require("./scope"),
    StringBuffer = require("./stringbuffer"),
    util = require("util");

require("./utils");  // for Object properties, don't need the module

var ClassDef = language.ClassDef,
    MethodDef = language.MethodDef;

exports.version = "1.0.0-beta";
exports.acorn = acorn;

var isLogicalOrBinaryExpression = acorn.makePredicate("LogicalExpression BinaryExpression");

// Options may be passed to further configure the compiler. These options are recognized:

exports.defaultOptions = {
    // Acorn (parser) options. For more information see objj-acorn.
    // We use a function here to create a new object every time we copy the default options.
    acornOptions: function() { return Object.create(null); },

    // If true, generates a source map for the compiled file.
    sourceMap: false,

    // A URL to the root directory of the source files, used by the source map.
    sourceRoot: ".",

    // Pass in class definitions. New class definitions in the source file will be added to this when compiling.
    classDefs: function() { return Object.create(null); },

    // Pass in protocol definitions. New protocol definitions in the source file will be added to this when compiling.
    protocolDefs: function() { return Object.create(null); },

    // Pass in typedef definitions. New typedef definitions in the source file will be added to this when compiling.
    typedefs: function() { return Object.create(null); },

    // The compiler uses JSON format objects which determine how the source code is formatted.
    // Example formats are located in the formats directory.
    format: "cappuccino",

    // The string to use to indent. Defaults to a single space.
    indentString: " ",

    // How many indentStrings to use when indenting generated code.
    indentWidth: 4,

    // If true, comments are included when generating code and the acorn options
    // trackComments and trackCommentsIncludeLineBreak are set true.
    includeComments: false,

    // The environment in which the code is expected to run.
    // This determines the set of predefined globals.
    environment: "browser",

    // The maximum number of errors that can occur before compilation is aborted.
    maxErrors: 20,

    // We support this here as the old Objective-J compiler (Not a real compiler, Preprocessor.js) transformed
    // named function declarations to assignments.
    // Example: 'function f(x) { return x }' transforms to: 'f = function(x) { return x }'
    transformNamedFunctionToAssignment: false,

    // Objective-J methods are implemented as functions. If this option is true, the functions
    // are named $<class>_<method>, where <class> is the class name, and <method> is the method name.
    // If this option is false, the function is anonymous.
    generateMethodFunctionNames: true,

    // If true, the compiler generates type information for method arguments.
    generateMethodArgumentTypeSignatures: true,

    // If true, the compiler generates type information for ivars.
    generateIvarTypeSignatures: true,

    // Supported compiler warnings and their default values.
    warnings: {
        "debugger": true,
        "shadowed-vars": true,
        "implicit-globals": true,
        "unknown-identifiers": true,
        "parameter-types": false,
        "unknown-types": true
    }
};

function setupWarnings(/* Array */ warnings)
{
    var result = Object.create(null),
        warningMode = "none";

    Object.merge(result, exports.defaultOptions.warnings);

    warnings.every(function(warning)
    {
        var value = true;

        if (warning === "all" || warning === "none")
        {
            value = warning === "all";

            Object.keys(result).forEach(function(key)
            {
                result[key] = value;
            });

            return false;  // abort every()
        }

        //noinspection IfStatementWithTooManyBranchesJS
        if (warning.startsWith("no-"))
        {
            if (warningMode === "set")
            {
                warningMode = "mixed";
            }
            else
            {
                warning = warning.substring("no-".length);
                value = false;
                warningMode = "exclusive";
            }
        }
        else if (warning.startsWith("+"))
        {
            if (warningMode === "set")
            {
                warningMode = "mixed";
            }
            else
            {
                warning = warning.substring(1);
                value = true;
                warningMode = "inclusive";
            }
        }
        else if (warningMode === "none")
        {
            // On the first "set" warning, reset all to false
            Object.keys(result).forEach(function(key)
            {
                result[key] = false;
            });

            warningMode = "set";
        }
        else if (warningMode !== "set")
        {
            warningMode = "mixed";
        }

        if (warningMode === "mixed")
        {
            console.warn("objjc: " + chalk.yellow("warning: ") + chalk.gray("listing specific compiler warnings may not be mixed with inclusive/exclusive warnings"));
            return false;  // abort every()
        }

        if (exports.defaultOptions.warnings.hasOwnProperty(warning))
            result[warning] = value;
        else
            console.warn("objjc: " + chalk.yellow("warning: ") + chalk.gray("unknown compiler warning '%s'"), warning);

        return true;
    });

    return result;
}

function setupOptions(options)
{
    options = options || {};

    var newOptions = {};

    for (var option in exports.defaultOptions)
    {
        if (options.hasOwnProperty(option))
        {
            if (option === "warnings" && Array.isArray(options.warnings))
                newOptions[option] = setupWarnings(options.warnings);
            else
                newOptions[option] = options[option];
        }
        else
        {
            var defaultOption = exports.defaultOptions[option];
            newOptions[option] = typeof defaultOption === "function" ? defaultOption() : defaultOption;
        }
    }

    return newOptions;
}

var operatorPrecedence = {
    // MemberExpression
    // These two are never used, since the "computed" attribute of the MemberExpression
    // determines which one to use.
    // ".": 0, "[]": 0,

    // NewExpression
    // This is never used.
    // "new": 1,

    // All these are UnaryExpression or UpdateExpression and are 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
};

function isIdempotentExpression(node)
{
    var result,
        i;

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

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

            return true;

        case "DictionaryLiteral":
            for (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 (i = 0; i < node.properties.length; i++)
                if (!isIdempotentExpression(node.properties[i].value))
                    return false;

            return true;

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

            return true;

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

            return true;

        case "UnaryExpression":
            result = isIdempotentExpression(node.argument);
            break;

        case "BinaryExpression":
            result = isIdempotentExpression(node.left) && isIdempotentExpression(node.right);
            break;

        case "ConditionalExpression":
            result = isIdempotentExpression(node.test) && isIdempotentExpression(node.consequent) && isIdempotentExpression(node.alternate);
            break;

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

        case "Dereference":
            result = isIdempotentExpression(node.expr);
            break;

        case "Reference":
            result = isIdempotentExpression(node.element);
            break;

        default:
            result = false;
    }

    return result;
}

var Compiler = function(source, sourcePath, destPath, ast, options)
{
    this.source = source;
    this.sourcePath = sourcePath;
    this.destPath = destPath;
    this.AST = ast;
    options = setupOptions(options);
    this.options = options;
    this.classDefs = options.classDefs;
    this.typedefs = options.typedefs;
    this.protocolDefs = options.protocolDefs;
    this.format = typeof options.format === "string" ? formats.load(options.format) : options.format;
    this.imBuffer = null;
    this.cmBuffer = null;
    this.initPredefinedGlobals();
    this.dependencies = [];
    this.importStack = Compiler.importStack;
    this.issues = [];
    this.errorCount = 0;
    this.lastPos = 0;
    this.jsBuffer = new StringBuffer(this);
    this.compiledCode = null;
};

exports.Compiler = Compiler;

Compiler.importStack = [];

Compiler.prototype.initPredefinedGlobals = function()
{
    this.predefinedGlobals = Object.create(null);
    Object.merge(this.predefinedGlobals, globals.reserved);
    Object.merge(this.predefinedGlobals, globals.nonstandard);
    Object.merge(this.predefinedGlobals, globals.ecmaIdentifiers);
    Object.merge(this.predefinedGlobals, globals.newEcmaIdentifiers);
    Object.merge(this.predefinedGlobals, globals[this.options.environment]);

    if (this.options.environment === "browser")
        Object.merge(this.predefinedGlobals, globals.devel);
};

// Returns true if subnode has higher precedence than node.
// If subnode is the right subnode of a binary expression, right is true.
Compiler.prototype.subnodeHasPrecedence = function(node, subnode, right)
{
    var nodeType = node.type,
        nodePrecedence = expressionTypePrecedence[nodeType] || -1,
        subnodePrecedence = expressionTypePrecedence[subnode.type] || -1;

    if (subnodePrecedence > nodePrecedence)
        return true;

    if (nodePrecedence === subnodePrecedence && isLogicalOrBinaryExpression(nodeType))
    {
        var subnodeOperatorPrecedence = operatorPrecedence[subnode.operator],
            nodeOperatorPrecedence = operatorPrecedence[node.operator];

        return (
            subnodeOperatorPrecedence > nodeOperatorPrecedence ||
            (right === true && nodeOperatorPrecedence === subnodeOperatorPrecedence)
        );
    }

    return false;
};

Compiler.prototype.createClass = function(node, name, superclassNode, ivars, instanceMethods, classMethods, protocols)
{
    // To be an @implementation declaration it must have method and ivar dictionaries.
    // Otherwise it's a @class declaration.

    var superclassDef = superclassNode ? this.getClassDef(superclassNode.name) : null;

    if (superclassNode && !superclassDef)
        this.addError(
            superclassNode,
            "cannot find implementation declaration for '%s', superclass of '%s'",
            superclassNode.name,
            name
        );

    return new ClassDef(node, name, superclassNode, superclassDef, ivars, instanceMethods, classMethods, protocols);
};

Compiler.prototype.compileParenthesizedExpression = function(node, scope, compileNode)
{
    var buffer = this.jsBuffer;

    if (compileNode)
    {
        buffer.concatLeftParens(node, scope);
        compileNode(node, scope);
        buffer.concatRightParens(node, scope);
    }
    else
    {
        buffer.concatWithFormats(node, scope, "before-left-parens", "(");
        compileNode(node, scope);
        buffer.concatWithFormats(node, scope, null, ")", "after-left-parens");
    }
};

Compiler.prototype.concatParenthesizedExpression = function(node, scope, compileNode, nodeToCompile)
{
    var buffer = this.jsBuffer;

    buffer.concatLeftParens(node, scope);
    compileNode(nodeToCompile || node, scope);
    buffer.concatRightParens(node, scope);
};

Compiler.prototype.concatPrecedenceExpression = function(node, subnode, scope, compileNode, right)
{
    if (this.subnodeHasPrecedence(node, subnode, right))
        this.concatParenthesizedExpression(subnode, scope, compileNode);
    else
        compileNode(subnode, scope);
};

/*
    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.
*/
Compiler.prototype.checkCanDereference = function(node)
{
    if (!isIdempotentExpression(node))
        this.addError(node, "dereference expressions may not have side effects");
};

// Helper for codeGenerator.MethodDeclaration
Compiler.prototype.makeSelector = function(scope, compileNode, nodeArguments, types, selectors)  // -> selector
{
    var selector = selectors[0].name;

    for (var i = 0; i < nodeArguments.length; i++)
    {
        var argument = nodeArguments[i],
            argumentType = argument.type,
            argumentProtocols = argumentType ? argumentType.protocols : null;

        types.push(argumentType ? argumentType.name : "id");

        if (i === 0)
            selector += ":";
        else
            selector += (selectors[i] ? selectors[i].name : "") + ":";

        if (argumentProtocols)
        {
            for (var j = 0; j < argumentProtocols.length; j++)
            {
                var argumentProtocol = argumentProtocols[j];

                if (!this.getProtocolDef(argumentProtocol.name))
                    this.addWarning(argumentProtocol, "cannot find protocol declaration for '%s'", argumentProtocol.name);
            }
        }
    }

    return selector;
};

Compiler.makeIssue = function(Class, source, sourcePath, node, args)
{
    var issue;

    if (arguments.length > 1)
    {
        issue = new Class(
            source,
            Compiler.getRelativeSourcePath(null, sourcePath),
            node,
            util.format.apply(this, args)
        );
    }
    else
        issue = Class;

    return issue;
};

Compiler.prototype.makeIssue = function(Class, node, args)
{
    return Compiler.makeIssue(
                Class,
                this.source,
                this.sourcePath,
                node,
                Array.prototype.slice.call(args, 1)
            );
};

Compiler.prototype.addIssue = function(Class, node, args)
{
    if (typeof args === "string")
        args = Array.prototype.slice.call(arguments, 1);

    this.issues.push(this.makeIssue(Class, node, args));
};

Compiler.prototype.addNote = function(node)
{
    return this.addIssue(exceptions.CompilerNote, node, arguments);
};

Compiler.prototype.addWarning = function(node)
{
    return this.addIssue(exceptions.CompilerWarning, node, arguments);
};

Compiler.prototype.addError = function(node)
{
    ++this.errorCount;

    if (this.errorCount > this.options.maxErrors)
        throw new exceptions.TooManyErrors(this.options.maxErrors);

    return this.addIssue(exceptions.CompilerError, node, arguments);
};

Compiler.prototype.addInternalError = function()
{
    this.addError.apply(this, arguments);
    throw new exceptions.InternalError();
};

Compiler.prototype.addIssueHighlight = function(node)
{
    this.issues.last().addHighlight(node);
};

Compiler.prototype.addIssueHighlights = function(nodes)
{
    nodes.forEach(function(node) { this.addIssueHighlight(node); }, this);
};

Compiler.prototype.getErrorCount = function()
{
    return this.errorCount;
};

Compiler.prototype.getIvarForClass = function(ivarName, scope)
{
    var ivar = scope.getIvarForCurrentClass(ivarName);

    if (ivar)
        return ivar;

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

    while (classDef)
    {
        var ivars = classDef.ivars;

        if (ivars)
        {
            var ivarDef = ivars[ivarName];

            if (ivarDef)
                return ivarDef;
        }

        classDef = classDef.superclassDef;
    }

    return null;
};

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

    return this.classDefs[name] || null;
};

Compiler.prototype.findClassDef = function(/* String */ name)
{
    name = name.toLowerCase();

    var names = Object.keys(this.classDefs);

    for (var i = 0; i < names.length; ++i)
    {
        if (name === names[i].toLowerCase())
            return this.classDefs[names[i]];
    }

    return null;
};

Compiler.prototype.getProtocolDef = function(/* String */ name)
{
    if (!name)
        return null;

    var protocolDef = this.protocolDefs[name] || null;

    if (protocolDef)
        return protocolDef;

    /*
    if (typeof objj_getProtocol === "function")
    {
        var protocol = objj_getProtocol(name);

        if (protocol)
        {
            var protocolName = protocol_getName(protocol),
                requiredInstanceMethods = protocol_copyMethodDescriptionList(protocol, true, true),
                requiredInstanceMethodDefs = Compiler.methodDefsFromMethodList(requiredInstanceMethods),
                requiredClassMethods = protocol_copyMethodDescriptionList(protocol, true, false),
                requiredClassMethodDefs = Compiler.methodDefsFromMethodList(requiredClassMethods),
                protocols = protocol.protocols,
                inheritedProtocols = [];

            if (protocols)
            {
                for (var i = 0; i < protocols.length; i++)
                    inheritedProtocols.push(this.getProtocolDef(protocols[i].name));
            }

            protocolDef = new ProtocolDef(protocolName, inheritedProtocols, requiredInstanceMethodDefs, requiredClassMethodDefs);
            this.protocolDefs[name] = protocolDef;
        }
    }
    */

    return protocolDef;<commit_msg>Eliminated code that references Objective-J runtime<commit_after>/*
 * compiler.js
 *
 * Created by <NAME>.
 * Copyright 2013, <NAME>.
 *
 * Additional work by <NAME>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the MIT license (http://opensource.org/licenses/MIT).
 */

"use strict";

var acorn = require("objj-acorn"),
    chalk = require("chalk"),
    exceptions = require("./exceptions"),
    globals = require("./globals"),
    language = require("./language"),
    formats = require("./formats"),
    indentation = require("./indentation"),
    path = require("path"),
    Scope = require("./scope"),
    StringBuffer = require("./stringbuffer"),
    util = require("util");

require("./utils");  // for Object properties, don't need the module

var ClassDef = language.ClassDef,
    MethodDef = language.MethodDef;

exports.version = "1.0.0-beta";
exports.acorn = acorn;

var isLogicalOrBinaryExpression = acorn.makePredicate("LogicalExpression BinaryExpression");

// Options may be passed to further configure the compiler. These options are recognized:

exports.defaultOptions = {
    // Acorn (parser) options. For more information see objj-acorn.
    // We use a function here to create a new object every time we copy the default options.
    acornOptions: function() { return Object.create(null); },

    // If true, generates a source map for the compiled file.
    sourceMap: false,

    // A URL to the root directory of the source files, used by the source map.
    sourceRoot: ".",

    // Pass in class definitions. New class definitions in the source file will be added to this when compiling.
    classDefs: function() { return Object.create(null); },

    // Pass in protocol definitions. New protocol definitions in the source file will be added to this when compiling.
    protocolDefs: function() { return Object.create(null); },

    // Pass in typedef definitions. New typedef definitions in the source file will be added to this when compiling.
    typedefs: function() { return Object.create(null); },

    // The compiler uses JSON format objects which determine how the source code is formatted.
    // Example formats are located in the formats directory.
    format: "cappuccino",

    // The string to use to indent. Defaults to a single space.
    indentString: " ",

    // How many indentStrings to use when indenting generated code.
    indentWidth: 4,

    // If true, comments are included when generating code and the acorn options
    // trackComments and trackCommentsIncludeLineBreak are set true.
    includeComments: false,

    // The environment in which the code is expected to run.
    // This determines the set of predefined globals.
    environment: "browser",

    // The maximum number of errors that can occur before compilation is aborted.
    maxErrors: 20,

    // We support this here as the old Objective-J compiler (Not a real compiler, Preprocessor.js) transformed
    // named function declarations to assignments.
    // Example: 'function f(x) { return x }' transforms to: 'f = function(x) { return x }'
    transformNamedFunctionToAssignment: false,

    // Objective-J methods are implemented as functions. If this option is true, the functions
    // are named $<class>_<method>, where <class> is the class name, and <method> is the method name.
    // If this option is false, the function is anonymous.
    generateMethodFunctionNames: true,

    // If true, the compiler generates type information for method arguments.
    generateMethodArgumentTypeSignatures: true,

    // If true, the compiler generates type information for ivars.
    generateIvarTypeSignatures: true,

    // Supported compiler warnings and their default values.
    warnings: {
        "debugger": true,
        "shadowed-vars": true,
        "implicit-globals": true,
        "unknown-identifiers": true,
        "parameter-types": false,
        "unknown-types": true
    }
};

function setupWarnings(/* Array */ warnings)
{
    var result = Object.create(null),
        warningMode = "none";

    Object.merge(result, exports.defaultOptions.warnings);

    warnings.every(function(warning)
    {
        var value = true;

        if (warning === "all" || warning === "none")
        {
            value = warning === "all";

            Object.keys(result).forEach(function(key)
            {
                result[key] = value;
            });

            return false;  // abort every()
        }

        //noinspection IfStatementWithTooManyBranchesJS
        if (warning.startsWith("no-"))
        {
            if (warningMode === "set")
            {
                warningMode = "mixed";
            }
            else
            {
                warning = warning.substring("no-".length);
                value = false;
                warningMode = "exclusive";
            }
        }
        else if (warning.startsWith("+"))
        {
            if (warningMode === "set")
            {
                warningMode = "mixed";
            }
            else
            {
                warning = warning.substring(1);
                value = true;
                warningMode = "inclusive";
            }
        }
        else if (warningMode === "none")
        {
            // On the first "set" warning, reset all to false
            Object.keys(result).forEach(function(key)
            {
                result[key] = false;
            });

            warningMode = "set";
        }
        else if (warningMode !== "set")
        {
            warningMode = "mixed";
        }

        if (warningMode === "mixed")
        {
            console.warn("objjc: " + chalk.yellow("warning: ") + chalk.gray("listing specific compiler warnings may not be mixed with inclusive/exclusive warnings"));
            return false;  // abort every()
        }

        if (exports.defaultOptions.warnings.hasOwnProperty(warning))
            result[warning] = value;
        else
            console.warn("objjc: " + chalk.yellow("warning: ") + chalk.gray("unknown compiler warning '%s'"), warning);

        return true;
    });

    return result;
}

function setupOptions(options)
{
    options = options || {};

    var newOptions = {};

    for (var option in exports.defaultOptions)
    {
        if (options.hasOwnProperty(option))
        {
            if (option === "warnings" && Array.isArray(options.warnings))
                newOptions[option] = setupWarnings(options.warnings);
            else
                newOptions[option] = options[option];
        }
        else
        {
            var defaultOption = exports.defaultOptions[option];
            newOptions[option] = typeof defaultOption === "function" ? defaultOption() : defaultOption;
        }
    }

    return newOptions;
}

var operatorPrecedence = {
    // MemberExpression
    // These two are never used, since the "computed" attribute of the MemberExpression
    // determines which one to use.
    // ".": 0, "[]": 0,

    // NewExpression
    // This is never used.
    // "new": 1,

    // All these are UnaryExpression or UpdateExpression and are 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
};

function isIdempotentExpression(node)
{
    var result,
        i;

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

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

            return true;

        case "DictionaryLiteral":
            for (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 (i = 0; i < node.properties.length; i++)
                if (!isIdempotentExpression(node.properties[i].value))
                    return false;

            return true;

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

            return true;

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

            return true;

        case "UnaryExpression":
            result = isIdempotentExpression(node.argument);
            break;

        case "BinaryExpression":
            result = isIdempotentExpression(node.left) && isIdempotentExpression(node.right);
            break;

        case "ConditionalExpression":
            result = isIdempotentExpression(node.test) && isIdempotentExpression(node.consequent) && isIdempotentExpression(node.alternate);
            break;

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

        case "Dereference":
            result = isIdempotentExpression(node.expr);
            break;

        case "Reference":
            result = isIdempotentExpression(node.element);
            break;

        default:
            result = false;
    }

    return result;
}

var Compiler = function(source, sourcePath, destPath, ast, options)
{
    this.source = source;
    this.sourcePath = sourcePath;
    this.destPath = destPath;
    this.AST = ast;
    options = setupOptions(options);
    this.options = options;
    this.classDefs = options.classDefs;
    this.typedefs = options.typedefs;
    this.protocolDefs = options.protocolDefs;
    this.format = typeof options.format === "string" ? formats.load(options.format) : options.format;
    this.imBuffer = null;
    this.cmBuffer = null;
    this.initPredefinedGlobals();
    this.dependencies = [];
    this.importStack = Compiler.importStack;
    this.issues = [];
    this.errorCount = 0;
    this.lastPos = 0;
    this.jsBuffer = new StringBuffer(this);
    this.compiledCode = null;
};

exports.Compiler = Compiler;

Compiler.importStack = [];

Compiler.prototype.initPredefinedGlobals = function()
{
    this.predefinedGlobals = Object.create(null);
    Object.merge(this.predefinedGlobals, globals.reserved);
    Object.merge(this.predefinedGlobals, globals.nonstandard);
    Object.merge(this.predefinedGlobals, globals.ecmaIdentifiers);
    Object.merge(this.predefinedGlobals, globals.newEcmaIdentifiers);
    Object.merge(this.predefinedGlobals, globals[this.options.environment]);

    if (this.options.environment === "browser")
        Object.merge(this.predefinedGlobals, globals.devel);
};

// Returns true if subnode has higher precedence than node.
// If subnode is the right subnode of a binary expression, right is true.
Compiler.prototype.subnodeHasPrecedence = function(node, subnode, right)
{
    var nodeType = node.type,
        nodePrecedence = expressionTypePrecedence[nodeType] || -1,
        subnodePrecedence = expressionTypePrecedence[subnode.type] || -1;

    if (subnodePrecedence > nodePrecedence)
        return true;

    if (nodePrecedence === subnodePrecedence && isLogicalOrBinaryExpression(nodeType))
    {
        var subnodeOperatorPrecedence = operatorPrecedence[subnode.operator],
            nodeOperatorPrecedence = operatorPrecedence[node.operator];

        return (
            subnodeOperatorPrecedence > nodeOperatorPrecedence ||
            (right === true && nodeOperatorPrecedence === subnodeOperatorPrecedence)
        );
    }

    return false;
};

Compiler.prototype.createClass = function(node, name, superclassNode, ivars, instanceMethods, classMethods, protocols)
{
    // To be an @implementation declaration it must have method and ivar dictionaries.
    // Otherwise it's a @class declaration.

    var superclassDef = superclassNode ? this.getClassDef(superclassNode.name) : null;

    if (superclassNode && !superclassDef)
        this.addError(
            superclassNode,
            "cannot find implementation declaration for '%s', superclass of '%s'",
            superclassNode.name,
            name
        );

    return new ClassDef(node, name, superclassNode, superclassDef, ivars, instanceMethods, classMethods, protocols);
};

Compiler.prototype.compileParenthesizedExpression = function(node, scope, compileNode)
{
    var buffer = this.jsBuffer;

    if (compileNode)
    {
        buffer.concatLeftParens(node, scope);
        compileNode(node, scope);
        buffer.concatRightParens(node, scope);
    }
    else
    {
        buffer.concatWithFormats(node, scope, "before-left-parens", "(");
        compileNode(node, scope);
        buffer.concatWithFormats(node, scope, null, ")", "after-left-parens");
    }
};

Compiler.prototype.concatParenthesizedExpression = function(node, scope, compileNode, nodeToCompile)
{
    var buffer = this.jsBuffer;

    buffer.concatLeftParens(node, scope);
    compileNode(nodeToCompile || node, scope);
    buffer.concatRightParens(node, scope);
};

Compiler.prototype.concatPrecedenceExpression = function(node, subnode, scope, compileNode, right)
{
    if (this.subnodeHasPrecedence(node, subnode, right))
        this.concatParenthesizedExpression(subnode, scope, compileNode);
    else
        compileNode(subnode, scope);
};

/*
    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.
*/
Compiler.prototype.checkCanDereference = function(node)
{
    if (!isIdempotentExpression(node))
        this.addError(node, "dereference expressions may not have side effects");
};

// Helper for codeGenerator.MethodDeclaration
Compiler.prototype.makeSelector = function(scope, compileNode, nodeArguments, types, selectors)  // -> selector
{
    var selector = selectors[0].name;

    for (var i = 0; i < nodeArguments.length; i++)
    {
        var argument = nodeArguments[i],
            argumentType = argument.type,
            argumentProtocols = argumentType ? argumentType.protocols : null;

        types.push(argumentType ? argumentType.name : "id");

        if (i === 0)
            selector += ":";
        else
            selector += (selectors[i] ? selectors[i].name : "") + ":";

        if (argumentProtocols)
        {
            for (var j = 0; j < argumentProtocols.length; j++)
            {
                var argumentProtocol = argumentProtocols[j];

                if (!this.getProtocolDef(argumentProtocol.name))
                    this.addWarning(argumentProtocol, "cannot find protocol declaration for '%s'", argumentProtocol.name);
            }
        }
    }

    return selector;
};

Compiler.makeIssue = function(Class, source, sourcePath, node, args)
{
    var issue;

    if (arguments.length > 1)
    {
        issue = new Class(
            source,
            Compiler.getRelativeSourcePath(null, sourcePath),
            node,
            util.format.apply(this, args)
        );
    }
    else
        issue = Class;

    return issue;
};

Compiler.prototype.makeIssue = function(Class, node, args)
{
    return Compiler.makeIssue(
                Class,
                this.source,
                this.sourcePath,
                node,
                Array.prototype.slice.call(args, 1)
            );
};

Compiler.prototype.addIssue = function(Class, node, args)
{
    if (typeof args === "string")
        args = Array.prototype.slice.call(arguments, 1);

    this.issues.push(this.makeIssue(Class, node, args));
};

Compiler.prototype.addNote = function(node)
{
    return this.addIssue(exceptions.CompilerNote, node, arguments);
};

Compiler.prototype.addWarning = function(node)
{
    return this.addIssue(exceptions.CompilerWarning, node, arguments);
};

Compiler.prototype.addError = function(node)
{
    ++this.errorCount;

    if (this.errorCount > this.options.maxErrors)
        throw new exceptions.TooManyErrors(this.options.maxErrors);

    return this.addIssue(exceptions.CompilerError, node, arguments);
};

Compiler.prototype.addInternalError = function()
{
    this.addError.apply(this, arguments);
    throw new exceptions.InternalError();
};

Compiler.prototype.addIssueHighlight = function(node)
{
    this.issues.last().addHighlight(node);
};

Compiler.prototype.addIssueHighlights = function(nodes)
{
    nodes.forEach(function(node) { this.addIssueHighlight(node); }, this);
};

Compiler.prototype.getErrorCount = function()
{
    return this.errorCount;
};

Compiler.prototype.getIvarForClass = function(ivarName, scope)
{
    var ivar = scope.getIvarForCurrentClass(ivarName);

    if (ivar)
        return ivar;

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

    while (classDef)
    {
        var ivars = classDef.ivars;

        if (ivars)
        {
            var ivarDef = ivars[ivarName];

            if (ivarDef)
                return ivarDef;
        }

        classDef = classDef.superclassDef;
    }

    return null;
};

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

    return this.classDefs[name] || null;
};

Compiler.prototype.findClassDef = function(/* String */ name)
{
    name = name.toLowerCase();

    var names = Object.keys(this.classDefs);

    for (var i = 0; i < names.length; ++i)
    {
        if (name === names[i].toLowerCase())
            return this.classDefs[names[i]];
    }

    return null;
};

Compiler.prototype.getProtocolDef = function(/* String */ name)
{
    if (!name)
        return null;

    return this.protocolDefs[name] || null;
{'fraction_non_alphanumeric': 0.11497096429780475, 'fraction_numerical': 0.003984271242936382, 'mean_word_length': 2.4299749910682387, 'pattern_counts': {'":': 112, '<': 42, '<?xml version=': 0, '>': 41, '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': '2305737', 'n_tokens_mistral': 10946, 'n_tokens_neox': 10407, 'n_words': 2740}