/*
JSECAL - advanced ECAL assembler for Windows

Copyright © Robert A Bradley 18/3/2005
*/

LabelDefineMessage=true;
EnableExtensions=true;
SparseDisassemble=true;

function Null() {
    this.valueOf=new Function("return 0;");
    this.toString=this.valueOf;
    this.unused=true;
    this.Line=0;
}

function InitEnv() {
    pc=0;
    consts=new Object();
    consts.length=0;
    
    labels=new Array();
    sLine=0;
    bytecode=new Array();
    for (i=0;i<256;i++) {
        bytecode[i]=new Null();
    }
    lines=new Array();
    
    AddLabel("or",255);
    lastVar=255;
    if (EnableExtensions) {
        AddLabel("tmp",254);
        lastVar--;
    }
    
    opcodes=new Array();
    opcodes['ca']=opcode_ClearAccumulator;// 01
    opcodes['sl']=opcode_ShiftLeft;// 08
    opcodes['no']=opcode_NoOp;//04
    opcodes['sr']=opcode_ShiftRight;// 02
    opcodes['se']=opcode_StoreExt;//10
    opcodes['jc']=opcode_JumpNonZero;//20
    opcodes['ss']=opcode_StartSubroutine;//40
    opcodes['su']=opcode_Sub;//60
    opcodes['ju']=opcode_Jump;//80
    opcodes['sa']=opcode_StoreAccumulator;//a0
    opcodes['an']=opcode_And;//c0
    opcodes['ad']=opcode_Add;//e0
    opcodes['ht']=opcode_Halt;//00
    if (EnableExtensions) {
        // Enable aliases and macros - be warned, these are *all* inline.
        opcodes['ldint']=opcode_LoadInteger; // CA+addint
        opcodes['addint']=opcode_AddInteger; // Add integer to accumulator (low level manipulation)
        opcodes['subint']=opcode_SubInteger; // Subtract integer from accumulator
        opcodes['write']=opcode_WriteRegister;// i.e. mov r0,0x00-0xff
        opcodes['clone']=opcode_CloneRegister;// mov r0,r1
        opcodes['ldr']=opcode_LoadRegister; //Macro for CA/AD
        opcodes['jnz']=opcode_JumpNonZero;//Alias for JC
        opcodes['add']=opcode_Add;//Alias for AD
        opcodes['sub']=opcode_Sub;//Alias for SU
        opcodes['nop']=opcode_NoOp;//Alias for NO
        opcodes['hlt']=opcode_Halt;//Alias for HT
    }
}

function Line(l,loc) {
    this.Line=l;
    this.Location=loc;
}

function Label(sourceLine,location) {
    this.Line=sourceLine;
    this.Location=location;
    this.toString=new Function("return this.Location");
}

function AddLabel(name,location) {
    name=name.toLowerCase();
    if (labels[name]!=null) {
        WScript.Echo("Duplicate label '"+name+"' defined at line "+sLine);
        return;
    }
    labels[name]=new Label(sLine,location);
    if (LabelDefineMessage)
        WScript.Echo("Defined label '"+name+"' at memory location "+location+" (source line "+sLine+")");
}

function ParseLine(line) {
    line=line.replace(/^\s+/,""); // Ignore leading whitespace
    // Check for comments
    if (/^\;/.test(line))
        return;
    if (EnableExtensions && /^js\s+(.*)$/.test(line)) {
        // Javascript statement, execute it...
        /^js\s+(.*)$/.exec(line);
        eval(RegExp.$1);
        return;
    }
    //Check for labels...
    var labelTest=/^(\w+)\:(.*)$/;
    if (labelTest.test(line)) {
        labelTest.exec(line);
        var label=RegExp.$1;
        AddLabel(label,pc);
        line=RegExp.$2;
    }
    // Parse instructions
    ax=/^\s*(.*)\s*(\;.*)?$/;
    ax.exec(line);
    line=RegExp.$1;
    if (line=="")
        return;
    line=line.replace(/\s\s+/g," ");
    var l=line.toLowerCase().split(" ");
    var inst=l.shift();
    if (/^\d/.test(inst)) {
        // numeric
        bytecode[pc]=parseInt(inst);
        pc++;
        return;
    }
    if (!opcodes[inst]) {
        WScript.Echo("Invalid opcode '"+inst+"' defined at line "+sLine);
        return;
    }
    else{
        opcodes[inst](l);
    }
}

// Opcode handlers

function opcode_ClearAccumulator(args) {
    if (args.length>0)
        WScript.Echo("Arguments ignored for CA");
    bytecode[pc]=new Opcode(0x01,0);
    pc++;
}

function opcode_ShiftLeft(args) {
    if (args.length>0)
        WScript.Echo("Arguments ignored for SL");
    bytecode[pc]=new Opcode(0x08,0);
    pc++;
}

function opcode_NoOp(args) {
    if (args.length>0)
        WScript.Echo("Arguments ignored for NO");
    bytecode[pc]=new Opcode(0x04,0);
    pc++;
}

function opcode_ShiftRight(args) {
    if (args.length>0)
        WScript.Echo("Arguments ignored for SR");
    bytecode[pc]=new Opcode(0x02,0);
    pc++;
}

function opcode_Halt(args) {
    if (args.length>0)
        WScript.Echo("Arguments ignored for HT");
    bytecode[pc]=new Opcode(0x00,0);
    pc++;
}

function opcode_Jump(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for JU");
    bytecode[pc]=new Opcode(0x80,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_JumpNonZero(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for JC");
    bytecode[pc]=new Opcode(0x20,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_Add(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for AD");
    bytecode[pc]=new Opcode(0xE0,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_And(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for AN");
    bytecode[pc]=new Opcode(0xC0,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_Sub(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for SU");
    bytecode[pc]=new Opcode(0x60,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_StoreAccumulator(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for SA");
    bytecode[pc]=new Opcode(0xA0,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_StoreExt(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for SE");
    bytecode[pc]=new Opcode(0x10,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function opcode_StartSubroutine(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for SS");
    bytecode[pc]=new Opcode(0x40,1);
    pc++;
    with(labels)
        bytecode[pc]=new Arg(args[0],0);
    pc++;
}

function Arg(exp,arg) {
    this.Exp=exp;
    this.Line=sLine;
    this.Argument=arg;
    this.toString=ArgCalc;
}

function Opcode(oc,args) {
    this.Opcode=oc;
    this.Line=sLine;
    this.Arguments=args;
    this.toString=new Function("return this.Opcode;");
}

function ArgCalc() {
    var x=0;
    try {
        with(labels){x=eval(this.Exp)};
    }
    catch(e) {
        WScript.Echo("Invalid location: source line "+this.Line+", argument "+(this.Argument+1));
    }
    return (x%256);
}

// -----------------------------
/*

Unofficial ECAL instructions and supporting structures - these will not work on the official compiler.  Most of
these are simple macros for common tasks.  However, some may use new opcodes that will not execute on the EL24 hardware.

These are:

ldint <integer> - load constant integer into accumulator
ldr <location> - copy contents of memory location into accumulator
jnz <location> - alias for JC (easier to remember for me, as it matches x86 mnemonics)
addint <int>
subint <int>
*/

function opcode_LoadRegister(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for LDR");
    opcode_ClearAccumulator([]);
    opcode_Add(args);
}

//WriteRegister(reg,value)

function opcode_WriteRegister(args) {
    if (args.length>2)
        WScript.Echo("Arguments ignored for WriteRegister");
    opcode_StoreAccumulator([254]);//Save accumulator
    opcode_LoadInteger(args[1]);//Load new value
    opcode_StoreAccumulator(args[0]);//Save to memory
    opcode_LoadRegister([254]);//Restore state
}

//CloneRegister(reg,oldreg)

function opcode_CloneRegister(args) {
    if (args.length>2)
        WScript.Echo("Arguments ignored for CloneRegister");
    opcode_StoreAccumulator([254]);//Save accumulator
    opcode_LoadRegister(args[1]);//Load value
    opcode_StoreAccumulator(args[0]);//Save to new location
    opcode_LoadRegister([254]);//Restore accumulator
}

function opcode_LoadInteger(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for LDINT");
    opcode_ClearAccumulator([]);
    opcode_AddInteger(args);
}

/*
Add/SubInteger

These low-level functions use the new AddConstant routines to allow integers to be added to memory locations.  This
is actually a trick - the integers are stored in memory, and the ConstRef objects point at that location.  Because of
this, we cannot easily pass the locations to opcode_Add.
*/

function opcode_AddInteger(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for ADDINT");
    bytecode[pc]=new Opcode(0xE0,1);
    pc++;
    with(labels)
        bytecode[pc]=new ConstRef(args[0],0);
    pc++;
}

function opcode_SubInteger(args) {
    if (args.length>1)
        WScript.Echo("Arguments ignored for SUBINT");
    bytecode[pc]=new Opcode(0x60,1);
    pc++;
    with(labels)
        bytecode[pc]=new ConstRef(args[0],0);
    pc++;
}

/*
AddConstant function: this will allow the use of things like "ldint 0x00", which compiles to:

ca
ad const0
...
const0: 0x00

(In fact, the label does not physically exist - const0 cannot be accessed in any form.  However, the assembler will
reuse constants with the same value.  Note that constants remove space from the program.)

This is an unofficial extension to the ECAL language - none of the official opcodes use this.
*/

function AddConstant(value) {
    value=parseInt(value);
    if (consts[value]!=null) {
        return consts[value];
    }
    var x=consts.length;
    var y=(lastVar-1)-consts.length;
    consts[value]=y;
    consts.length++;
    bytecode[y]=value;
    if (LabelDefineMessage)
        WScript.Echo("Stored constant "+value+" at location "+y);
    return y;
}

function ConstRef(cnst,arg) {
    this.Const=new Arg(cnst,arg);
    this.Line=sLine;
    this.Argument=arg;
    this.toString=ConstCalc;
}

function ConstCalc() {
    return AddConstant(this.Const);
}

// --- Disassembler functionality ---

function disassemble(bytecode,pc) {
    while(pc<bytecode.length) {
        if (SparseDisassemble && bytecode[pc].unused) {
            pc++;
            continue;
        }
        var opcode=parseInt(bytecode[pc]);
        //WScript.Echo("pc="+pc);
        switch(opcode) {
            case 0x01:pc=print_CA(bytecode,pc);break;
            case 0x08:pc=print_SL(bytecode,pc);break;
            case 0x04:pc=print_NO(bytecode,pc);break;
            case 0x02:pc=print_SR(bytecode,pc);break;
            case 0x10:pc=print_SE(bytecode,pc);break;
            case 0x20:pc=print_JC(bytecode,pc);break;
            case 0x40:pc=print_SS(bytecode,pc);break;
            case 0x60:pc=print_SU(bytecode,pc);break;
            case 0x80:pc=print_JU(bytecode,pc);break;
            case 0xa0:pc=print_SA(bytecode,pc);break;
            case 0xc0:pc=print_AN(bytecode,pc);break;
            case 0xe0:pc=print_AD(bytecode,pc);break;
            case 0x00:pc=print_HT(bytecode,pc);break;
            default:print_byte(bytecode,pc);pc++;
        }
    }
}

function print_inst(pc,a) {
    var args=new Array();
    for (var i=0;i<arguments.length;i++) {args[i]=arguments[i];}
    var pc=args.shift();
    var str=args.shift();
    var bytes=args.pop();
    var bstr="";
    for (var i=0;i<bytes.length;i++) {
        bstr+=ByteString(bytes[i])+" ";
    }
    if (args.length>0) {
        str+=" "+(args.join(" "));
    }
    fileDis.WriteLine("0x"+ByteString(pc)+":\t"+bstr+"\t# "+str);
}

function print_HT(bytecode,pc) {
    print_inst(pc,"HT",[bytecode[pc]]);
    pc++;
    return pc;
}

function print_CA(bytecode,pc) {
    print_inst(pc,"CA",[bytecode[pc]]);
    pc++;
    return pc;
}

function print_SL(bytecode,pc) {
    print_inst(pc,"SL",[bytecode[pc]]);
    pc++;
    return pc;
}

function print_NO(bytecode,pc) {
    print_inst(pc,"NO",[bytecode[pc]]);
    pc++;
    return pc;
}

function print_SR(bytecode,pc) {
    print_inst(pc,"SR",[bytecode[pc]]);
    pc++;
    return pc;
}

function print_SE(bytecode,pc) {
    print_inst(pc,"SE",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_JC(bytecode,pc) {
    print_inst(pc,"JC",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_SS(bytecode,pc) {
    print_inst(pc,"SS",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_SU(bytecode,pc) {
    print_inst(pc,"SU",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_JU(bytecode,pc) {
    print_inst(pc,"JU",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_SA(bytecode,pc) {
    print_inst(pc,"SA",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_AN(bytecode,pc) {
    print_inst(pc,"AN",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_AD(bytecode,pc) {
    print_inst(pc,"AD",ByteString(bytecode[pc+1]),[bytecode[pc],bytecode[pc+1]]);
    pc+=2;
    return pc;
}

function print_byte(bytecode,pc) {
    print_inst(pc,"0x"+ByteString(bytecode[pc]),[bytecode[pc]]);
}

function ByteString(b) {
    b=parseInt(b);
    return (((b<16)?"0":"")+b.toString(16)).toUpperCase();
}
