/* vim: set sw=4 ts=8 et tw=78 ft=javascript: */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the TraceMonkey IMacro Assembler.
 *
 * The Initial Developer of the Original Code is
 * Brendan Eich <brendan@mozilla.org>.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * An imacro (interpreter-macro) assembler in JS, with a light dusting of C
 * pre-processor. We depend on the snarf function from the js shell, defined
 * originally for the Narcissus metacircular evaluator, now unconditionally
 * compiled into the shell.
 *
 * Filename suffix conventions, used by Makefile.in rules:
 *   .js.in     C-pre-processed JS source
 *   .jsasm     SpiderMonkey JS assembly source, which could be input to other
 *              assemblers than imacro_asm.js, hence the generic suffix!
 *   .c.out     C source output by imacro_asm.js
 */

#define ASSERT(cond)    _ASSERT(cond, #cond)

function _ASSERT(cond, message) {
    if (!cond)
        throw new Error("Assertion failed: " + message);
}

const js_arguments_str  = "arguments";
const js_new_str        = "new";
const js_typeof_str     = "typeof";
const js_void_str       = "void";
const js_null_str       = "null";
const js_this_str       = "this";
const js_false_str      = "false";
const js_true_str       = "true";
const js_throw_str      = "throw";
const js_in_str         = "in";
const js_instanceof_str = "instanceof";
const js_getter_str     = "getter";
const js_setter_str     = "setter";

#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format)               \
    {jsop: #op, opcode: val, opname: name, opsrc: token, oplen: length,       \
     pops: nuses, pushes: ndefs, precedence: prec, flags: #format},

const NULL = null;

const opinfo = [
#include "jsopcode.tbl"
];

const opname2info = {};
const jsop2opcode = {};

for (let i = 0; i < opinfo.length; i++) {
    let info = opinfo[i];
    ASSERT(info.opcode == i);
    opname2info[info.opname] = info;
    jsop2opcode[info.jsop] = info.opcode;
}

function formatoffset(n, w) {
    let s = n.toString();
    while (s.length < w)
        s = ' ' + s;
    return s;
}

function immediate(op) {
    let info = op.info;
    let imm1Expr = /^\(/.test(op.imm1);
    if (info.flags.indexOf("JOF_ATOM") >= 0) {
        if (/^(?:void|object|function|string|number|boolean)$/.test(op.imm1))
            return "0, COMMON_TYPE_ATOM_INDEX(JSTYPE_" + op.imm1.toUpperCase() + ")";
        return "0, COMMON_ATOM_INDEX(" + op.imm1 + ")";
    }
    if (info.flags.indexOf("JOF_JUMP") >= 0) {
        ASSERT(!imm1Expr);
        return ((op.target >> 8) & 0xff) + ", " + (op.target & 0xff);
    }
    if (info.flags.indexOf("JOF_UINT8") >= 0 ||
        info.flags.indexOf("JOF_INT8") >= 0) {
        if (imm1Expr)
            return op.imm1;
        if (isNaN(Number(op.imm1)) || Number(op.imm1) != parseInt(op.imm1))
            throw new Error("invalid 8-bit operand: " + op.imm1);
        return (op.imm1 & 0xff);
    }
    if (info.flags.indexOf("JOF_UINT16") >= 0) {
        if (imm1Expr)
            return '(_ & 0xff00) >> 8, (_ & 0xff)'.replace(/_/g, op.imm1);
        return ((op.imm1 & 0xff00) >> 8) + ", " + (op.imm1 & 0xff);
    }
    throw new Error(info.jsop + " format not yet implemented");
}

/*
 * Syntax (spaces are significant only to delimit tokens):
 *
 *   Assembly   ::= (Directive? '\n')*
 *   Directive  ::= (name ':')? Operation
 *   Operation  ::= opname Operands?
 *   Operands   ::= Operand (',' Operand)*
 *   Operand    ::= name | number | '(' Expr ')'
 *   Expr       ::= a constant-expression in the C++ language
 *                  containing no parentheses
 *
 * We simplify given line structure and the maximum of one immediate operand,
 * by parsing using split and regexps.  For ease of parsing, parentheses are
 * banned in an Expr for now, even in quotes or a C++ comment.
 *
 * Pseudo-ops start with . and include .igroup and .imacro, terminated by .end.
 * .imacro must nest in .igroup, neither nests in itself. See imacros.jsasm for
 * examples.
 */
const line_regexp_parts = [
    "^(?:(\\w+):)?",                    // optional label at start of line
    "\\s*(\\.?\\w+)",                   // optional spaces, (pseudo-)opcode
    "(?:\\s+(\\w+|\\([^)]*\\)))?",      // optional first immediate operand
    "(?:\\s+([\\w-]+|\\([^)]*\\)))?",   // optional second immediate operand
    "(?:\\s*(?:#.*))?$"                 // optional spaces and comment
];

const line_regexp = new RegExp(line_regexp_parts.join(""));

function assemble(filename) {
    let igroup = null, imacro = null;
    let opcode2extra = [];

    print("/* GENERATED BY imacro_asm.js -- DO NOT EDIT!!! */");

    let s = snarf(filename);
    let a = s.split('\n');
    for (let i = 0; i < a.length; i++) {
        if (/^\s*(?:#.*)?$/.test(a[i]))
            continue;
        let m = line_regexp.exec(a[i]);
        if (!m)
            throw new Error(a[i]);

        let [, label, opname, imm1, imm2] = m;

        if (opname[0] == '.') {
            if (label)
                throw new Error("invalid label " + label + " before " + opname);

            switch (opname) {
              case ".igroup":
                if (!imm1)
                    throw new Error("missing .igroup name");
                if (igroup)
                    throw new Error("nested .igroup " + imm1);
                let oprange = imm2.match(/^(\w+)(?:-(\w+))?$/);
                if (!oprange)
                    throw new Error("invalid igroup operator range " + imm2);
                let firstop = jsop2opcode[oprange[1]];
                igroup = {
                    name:    imm1,
                    firstop: firstop,
                    lastop:  oprange[2] ? jsop2opcode[oprange[2]] : firstop,
                    imacros: []
                };
                break;

              case ".imacro":
                if (!igroup)
                    throw new Error(".imacro outside of .igroup");
                if (!imm1)
                    throw new Error("missing .imacro name");
                if (imacro)
                    throw new Error("nested .imacro " + imm1);
                imacro = {
                    name:      imm1,
                    offset:    0,
                    code:      [],
                    labeldefs: {},
                    labelrefs: {}
                };
                break;

              case ".end":
                if (!imacro) {
                    if (!igroup)
                        throw new Error(".end without prior .igroup or .imacro");
                    if (imm1 && (imm1 != igroup.name || imm2))
                        throw new Error(".igroup/.end name mismatch");

                    let maxdepth = 0;

                    print("static struct {");
                    for (let j = 0; j < igroup.imacros.length; j++) {
                        imacro = igroup.imacros[j];
                        print("    jsbytecode " + imacro.name + "[" + imacro.offset + "];");
                    }
                    print("} " + igroup.name + "_imacros = {");

                    for (let j = 0; j < igroup.imacros.length; j++) {
                        let depth = 0;

                        imacro = igroup.imacros[j];
                        print("    {");
                        for (let k = 0; k < imacro.code.length; k++) {
                            let op = imacro.code[k];

                            print("/*" + formatoffset(op.offset,2) + "*/  " + op.info.jsop +
                                  (op.imm1 ? ", " + immediate(op) : "") + ",");

                            depth -= (op.info.pops < 0) ? 2 + op.imm1 : op.info.pops;
                            depth += op.info.pushes;
                            if (depth > maxdepth)
                                maxdepth = depth;
                        }
                        print("    },");
                    }

                    print("};");

                    let opcode = igroup.firstop;
                    let oplast = igroup.lastop;
                    do {
                        opcode2extra[opcode] = maxdepth;
                    } while (opcode++ != oplast);
                    igroup = null;
                } else {
                    ASSERT(igroup);

                    if (imm1 && imm1 != imacro.name)
                        throw new Error(".imacro/.end name mismatch");

                    // Backpatch the forward references to labels that must now be defined.
                    for (label in imacro.labelrefs) {
                        if (!imacro.labelrefs.hasOwnProperty(label))
                            continue;
                        if (!imacro.labeldefs.hasOwnProperty(label))
                            throw new Error("label " + label + " used but not defined");
                        let link = imacro.labelrefs[label];
                        ASSERT(link >= 0);
                        for (;;) {
                            let op = imacro.code[link];
                            ASSERT(op);
                            ASSERT(op.hasOwnProperty('target'));
                            let next = op.target;
                            op.target = imacro.labeldefs[label] - op.offset;
                            if (next < 0)
                                break;
                            link = next;
                        }
                    }

                    igroup.imacros.push(imacro);
                }
                imacro = null;
                break;

              default:
                throw new Error("unknown pseudo-op " + opname);
            }
            continue;
        }

        if (!opname2info.hasOwnProperty(opname))
            throw new Error("unknown opcode " + opname + (label ? " (label " + label + ")" : ""));

        let info = opname2info[opname];
        if (info.oplen == -1)
            throw new Error("unimplemented opcode " + opname);

        if (!imacro)
            throw new Error("opcode " + opname + " outside of .imacro");

        if (label)
            imacro.labeldefs[label] = imacro.offset;

        let op = {offset: imacro.offset, info: info, imm1: imm1, imm2: imm2};
        if (info.flags.indexOf("JOF_JUMP") >= 0) {
            if (imacro.labeldefs.hasOwnProperty(imm1)) {
                // Backward reference can be resolved right away, no backpatching needed.
                op.target = imacro.labeldefs[imm1] - op.offset;
            } else {
                // Link op into the .target-linked backpatch chain at labelrefs[imm1].
                // The linked list terminates with a -1 sentinel.
                op.target = imacro.labelrefs.hasOwnProperty(imm1) ? imacro.labelrefs[imm1] : -1;
                imacro.labelrefs[imm1] = imacro.code.length;
            }
        }

        imacro.code.push(op);
        imacro.offset += info.oplen;
    }

    print("uint8 js_opcode2extra[JSOP_LIMIT] = {");
    for (let i = 0; i < opinfo.length; i++) {
        print("    " + ((i in opcode2extra) ? opcode2extra[i] : "0") +
              ",  /* " + opinfo[i].jsop + " */");
    }
    print("};");

    print("#define JSOP_IS_IMACOP(x) (0 \\");
    for (let i in opcode2extra)
        print(" || x == " + opinfo[i].jsop + " \\");
    print(")");
}

for (let i = 0; i < arguments.length; i++) {
    try {
        assemble(arguments[i]);
    } catch (e) {
        print(e.name + ": " + e.message + "\n" + e.stack);
    }
}
