//-----------------------------------------------------------------------------
// Copyright 2007 Jonathan Westhues
//
// This file is part of LDmicro.
// 
// LDmicro is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// LDmicro 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 General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with LDmicro.  If not, see <http://www.gnu.org/licenses/>.
//------
//
// Generate intermediate code for the ladder logic. Basically generate code
// for a `virtual machine' with operations chosen to be easy to compile to
// AVR or PIC16 code.
// Jonathan Westhues, Nov 2004
//-----------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>

#include "ldmicro.h"
#include "intcode.h"

IntOp IntCode[MAX_INT_OPS];
int IntCodeLen;

static DWORD GenSymCountParThis;
static DWORD GenSymCountParOut;
static DWORD GenSymCountOneShot;
static DWORD GenSymCountFormattedString;

static WORD EepromAddrFree;

//-----------------------------------------------------------------------------
// Pretty-print the intermediate code to a file, for debugging purposes.
//-----------------------------------------------------------------------------
void IntDumpListing(char *outFile)
{
    FILE *f = fopen(outFile, "w");
    if(!f) {
        Error("Couldn't dump intermediate code to '%s'.", outFile);
    }

    int i;
    int indent = 0;
    for(i = 0; i < IntCodeLen; i++) {

        if(IntCode[i].op == INT_END_IF) indent--;
        if(IntCode[i].op == INT_ELSE) indent--;
    
        fprintf(f, "%3d:", i);
        int j;
        for(j = 0; j < indent; j++) fprintf(f, "    ");

        switch(IntCode[i].op) {
            case INT_SET_BIT:
                fprintf(f, "set bit '%s'", IntCode[i].name1);
                break;

            case INT_CLEAR_BIT:
                fprintf(f, "clear bit '%s'", IntCode[i].name1);
                break;

            case INT_COPY_BIT_TO_BIT:
                fprintf(f, "let bit '%s' := '%s'", IntCode[i].name1,
                    IntCode[i].name2);
                break;

            case INT_SET_VARIABLE_TO_LITERAL:
                fprintf(f, "let var '%s' := %d", IntCode[i].name1,
                    IntCode[i].literal);
                break;

            case INT_SET_VARIABLE_TO_VARIABLE:
                fprintf(f, "let var '%s' := '%s'", IntCode[i].name1,
                    IntCode[i].name2);
                break;

            case INT_SET_VARIABLE_ADD:
                fprintf(f, "let var '%s' := '%s' + '%s'", IntCode[i].name1,
                    IntCode[i].name2, IntCode[i].name3);
                break;

            case INT_SET_VARIABLE_SUBTRACT:
                fprintf(f, "let var '%s' := '%s' - '%s'", IntCode[i].name1,
                    IntCode[i].name2, IntCode[i].name3);
                break;

            case INT_SET_VARIABLE_MULTIPLY:
                fprintf(f, "let var '%s' := '%s' * '%s'", IntCode[i].name1,
                    IntCode[i].name2, IntCode[i].name3);
                break;

            case INT_SET_VARIABLE_DIVIDE:
                fprintf(f, "let var '%s' := '%s' / '%s'", IntCode[i].name1,
                    IntCode[i].name2, IntCode[i].name3);
                break;

            case INT_INCREMENT_VARIABLE:
                fprintf(f, "increment '%s'", IntCode[i].name1);
                break;

            case INT_READ_ADC:
                fprintf(f, "read adc '%s'", IntCode[i].name1);
                break;

            case INT_SET_PWM:
                fprintf(f, "set pwm '%s' %s Hz", IntCode[i].name1,
                    IntCode[i].name2);
                break;

            case INT_EEPROM_BUSY_CHECK:
                fprintf(f, "set bit '%s' if EEPROM busy", IntCode[i].name1);
                break;
            
            case INT_EEPROM_READ:
                fprintf(f, "read EEPROM[%d,%d+1] into '%s'",
                    IntCode[i].literal, IntCode[i].literal, IntCode[i].name1);
                break;

            case INT_EEPROM_WRITE:
                fprintf(f, "write '%s' into EEPROM[%d,%d+1]",
                    IntCode[i].name1, IntCode[i].literal, IntCode[i].literal);
                break;

            case INT_UART_SEND:
                fprintf(f, "uart send from '%s', done? into '%s'",
                    IntCode[i].name1, IntCode[i].name2);
                break;

            case INT_UART_RECV:
                fprintf(f, "uart recv int '%s', have? into '%s'", 
                    IntCode[i].name1, IntCode[i].name2);
                break;

            case INT_IF_BIT_SET:
                fprintf(f, "if '%s' {", IntCode[i].name1); indent++;
                break;

            case INT_IF_BIT_CLEAR:
                fprintf(f, "if not '%s' {", IntCode[i].name1); indent++;
                break;

            case INT_IF_VARIABLE_LES_LITERAL:
                fprintf(f, "if '%s' < %d {", IntCode[i].name1,
                    IntCode[i].literal); indent++;
                break;

            case INT_IF_VARIABLE_EQUALS_VARIABLE:
                fprintf(f, "if '%s' == '%s' {", IntCode[i].name1,
                    IntCode[i].name2); indent++;
                break;

            case INT_IF_VARIABLE_GRT_VARIABLE:
                fprintf(f, "if '%s' > '%s' {", IntCode[i].name1,
                    IntCode[i].name2); indent++;
                break;

            case INT_END_IF:
                fprintf(f, "}");
                break;

            case INT_ELSE:
                fprintf(f, "} else {"); indent++;
                break;

            case INT_SIMULATE_NODE_STATE:
                // simulation-only; the real code generators don't care
                break;

            case INT_COMMENT:
                fprintf(f, "# %s", IntCode[i].name1);
                break;

            default:
                oops();
        }
        fprintf(f, "\n");
        fflush(f);
    }
    fclose(f);
}

//-----------------------------------------------------------------------------
// Convert a hex digit (0-9a-fA-F) to its hex value, or return -1 if the
// character is not a hex digit.
//-----------------------------------------------------------------------------
int HexDigit(int c)
{
    if((c >= '0') && (c <= '9')) {
        return c - '0';
    } else if((c >= 'a') && (c <= 'f')) {
        return 10 + (c - 'a');
    } else if((c >= 'A') && (c <= 'F')) {
        return 10 + (c - 'A');
    }
    return -1;
}

//-----------------------------------------------------------------------------
// Generate a unique symbol (unique with each call) having the given prefix
// guaranteed not to conflict with any user symbols.
//-----------------------------------------------------------------------------
static void GenSymParThis(char *dest)
{
    sprintf(dest, "$parThis_%04x", GenSymCountParThis);
    GenSymCountParThis++;
}
static void GenSymParOut(char *dest)
{
    sprintf(dest, "$parOut_%04x", GenSymCountParOut);
    GenSymCountParOut++;
}
static void GenSymOneShot(char *dest)
{
    sprintf(dest, "$oneShot_%04x", GenSymCountOneShot);
    GenSymCountOneShot++;
}
static void GenSymFormattedString(char *dest)
{
    sprintf(dest, "$formattedString_%04x", GenSymCountFormattedString);
    GenSymCountFormattedString++;
}

//-----------------------------------------------------------------------------
// Compile an instruction to the program.
//-----------------------------------------------------------------------------
static void Op(int op, char *name1, char *name2, char *name3, SWORD lit)
{
    IntCode[IntCodeLen].op = op;
    if(name1) strcpy(IntCode[IntCodeLen].name1, name1);
    if(name2) strcpy(IntCode[IntCodeLen].name2, name2);
    if(name3) strcpy(IntCode[IntCodeLen].name3, name3);
    IntCode[IntCodeLen].literal = lit;
    IntCodeLen++;
}
static void Op(int op, char *name1, char *name2, SWORD lit)
{
    Op(op, name1, name2, NULL, lit);
}
static void Op(int op, char *name1, SWORD lit)
{
    Op(op, name1, NULL, NULL, lit);
}
static void Op(int op, char *name1, char *name2)
{
    Op(op, name1, name2, NULL, 0);
}
static void Op(int op, char *name1)
{
    Op(op, name1, NULL, NULL, 0);
}
static void Op(int op)
{
    Op(op, NULL, NULL, NULL, 0);
}

//-----------------------------------------------------------------------------
// Compile the instruction that the simulator uses to keep track of which
// nodes are energized (so that it can display which branches of the circuit
// are energized onscreen). The MCU code generators ignore this, of course.
//-----------------------------------------------------------------------------
static void SimState(BOOL *b, char *name)
{
    IntCode[IntCodeLen].op = INT_SIMULATE_NODE_STATE;
    strcpy(IntCode[IntCodeLen].name1, name);
    IntCode[IntCodeLen].poweredAfter = b;
    IntCodeLen++;
}

//-----------------------------------------------------------------------------
// printf-like comment function
//-----------------------------------------------------------------------------
void Comment(char *str, ...)
{
    va_list f;
    char buf[MAX_NAME_LEN];
    va_start(f, str);
    vsprintf(buf, str, f);
    Op(INT_COMMENT, buf);
}

//-----------------------------------------------------------------------------
// Calculate the period in scan units from the period in microseconds, and
// raise an error if the given period is unachievable.
//-----------------------------------------------------------------------------
static int TimerPeriod(ElemLeaf *l)
{
    int period = (l->d.timer.delay / Prog.cycleTime) - 1;

    if(period < 1)  {
        Error(_("Timer period too short (needs faster cycle time)."));
        CompileError();
    }
    if(period >= (1 << 15)) {
        Error(_("Timer period too long (max 32767 times cycle time); use a "
            "slower cycle time."));
        CompileError();
    }

    return period;
}

//-----------------------------------------------------------------------------
// Is an expression that could be either a variable name or a number a number?
//-----------------------------------------------------------------------------
static BOOL IsNumber(char *str)
{
    if(*str == '-' || isdigit(*str)) {
        return TRUE;
    } else if(*str == '\'') {
        // special case--literal single character
        return TRUE;
    } else {
        return FALSE;
    }
}

//-----------------------------------------------------------------------------
// Report an error if a constant doesn't fit in 16 bits.
//-----------------------------------------------------------------------------
void CheckConstantInRange(int v)
{
    if(v < -32768 || v > 32767) {
        Error(_("Constant %d out of range: -32768 to 32767 inclusive."), v);
        CompileError();
    }
}

//-----------------------------------------------------------------------------
// Try to turn a string into a 16-bit constant, and raise an error if
// something bad happens when we do so (e.g. out of range).
//-----------------------------------------------------------------------------
SWORD CheckMakeNumber(char *str)
{
    int val;

    if(*str == '\'') {
        val = str[1];
    } else {
        val = atoi(str);
    }

    CheckConstantInRange(val);

    return (SWORD)val;
}

//-----------------------------------------------------------------------------
// Return an integer power of ten.
//-----------------------------------------------------------------------------
static int TenToThe(int x)
{
    int i;
    int r = 1;
    for(i = 0; i < x; i++) {
        r *= 10;
    }
    return r;
}

//-----------------------------------------------------------------------------
// Compile code to evaluate the given bit of ladder logic. The rung input
// state is in stateInOut before calling and will be in stateInOut after
// calling.
//-----------------------------------------------------------------------------
static char *VarFromExpr(char *expr, char *tempName)
{
    if(IsNumber(expr)) {
        Op(INT_SET_VARIABLE_TO_LITERAL, tempName, CheckMakeNumber(expr));
        return tempName;
    } else {
        return expr;
    }
}
static void IntCodeFromCircuit(int which, void *any, char *stateInOut)
{
    ElemLeaf *l = (ElemLeaf *)any;

    switch(which) {
        case ELEM_SERIES_SUBCKT: {
            int i;
            ElemSubcktSeries *s = (ElemSubcktSeries *)any;
            
            Comment("start series [");
            for(i = 0; i < s->count; i++) {
                IntCodeFromCircuit(s->contents[i].which, s->contents[i].d.any,
                    stateInOut);
            }
            Comment("] finish series");
            break;
        }
        case ELEM_PARALLEL_SUBCKT: {
            char parThis[MAX_NAME_LEN];
            GenSymParThis(parThis);

            char parOut[MAX_NAME_LEN];
            GenSymParOut(parOut);

            Comment("start parallel [");

            Op(INT_CLEAR_BIT, parOut);

            ElemSubcktParallel *p = (ElemSubcktParallel *)any;
            int i;
            for(i = 0; i < p->count; i++) {
                Op(INT_COPY_BIT_TO_BIT, parThis, stateInOut);

                IntCodeFromCircuit(p->contents[i].which, p->contents[i].d.any,
                    parThis);

                Op(INT_IF_BIT_SET, parThis);
                Op(INT_SET_BIT, parOut);
                Op(INT_END_IF);
            }
            Op(INT_COPY_BIT_TO_BIT, stateInOut, parOut);
            Comment("] finish parallel");
            
            break;
        }
        case ELEM_CONTACTS: {
            if(l->d.contacts.negated) {
                Op(INT_IF_BIT_SET, l->d.contacts.name);
            } else {
                Op(INT_IF_BIT_CLEAR, l->d.contacts.name);
            }
            Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_END_IF);
            break;
        }
        case ELEM_COIL: {
            if(l->d.coil.negated) {
                Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_CLEAR_BIT, l->d.contacts.name);
                Op(INT_ELSE);
                Op(INT_SET_BIT, l->d.contacts.name);
                Op(INT_END_IF);
            } else if(l->d.coil.setOnly) {
                Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_SET_BIT, l->d.contacts.name);
                Op(INT_END_IF);
            } else if(l->d.coil.resetOnly) {
                Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_CLEAR_BIT, l->d.contacts.name);
                Op(INT_END_IF);
            } else {
                Op(INT_COPY_BIT_TO_BIT, l->d.contacts.name, stateInOut);
            }
            break;
        }
        case ELEM_RTO: {
            int period = TimerPeriod(l);

            Op(INT_IF_VARIABLE_LES_LITERAL, l->d.timer.name, period);

            Op(INT_IF_BIT_SET, stateInOut);
            Op(INT_INCREMENT_VARIABLE, l->d.timer.name);
            Op(INT_END_IF);
            Op(INT_CLEAR_BIT, stateInOut);

            Op(INT_ELSE);

            Op(INT_SET_BIT, stateInOut);

            Op(INT_END_IF);

            break;
        }
        case ELEM_RES:
            Op(INT_IF_BIT_SET, stateInOut);
            Op(INT_SET_VARIABLE_TO_LITERAL, l->d.reset.name);
            Op(INT_END_IF);
            break;

        case ELEM_TON: {
            int period = TimerPeriod(l);

            Op(INT_IF_BIT_SET, stateInOut);

            Op(INT_IF_VARIABLE_LES_LITERAL, l->d.timer.name, period);

            Op(INT_INCREMENT_VARIABLE, l->d.timer.name);
            Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_END_IF);

            Op(INT_ELSE);

            Op(INT_SET_VARIABLE_TO_LITERAL, l->d.timer.name);

            Op(INT_END_IF);

            break;
        }
        case ELEM_TOF: {
            int period = TimerPeriod(l);

            // All variables start at zero by default, so by default the
            // TOF timer would start out with its output forced HIGH, until
            // it finishes counting up. This does not seem to be what
            // people expect, so add a special case to fix that up.
            char antiGlitchName[MAX_NAME_LEN];
            sprintf(antiGlitchName, "$%s_antiglitch", l->d.timer.name);
            Op(INT_IF_BIT_CLEAR, antiGlitchName);
                Op(INT_SET_VARIABLE_TO_LITERAL, l->d.timer.name, period);
            Op(INT_END_IF);
            Op(INT_SET_BIT, antiGlitchName);
            
            Op(INT_IF_BIT_CLEAR, stateInOut);

            Op(INT_IF_VARIABLE_LES_LITERAL, l->d.timer.name, period);

            Op(INT_INCREMENT_VARIABLE, l->d.timer.name);
            Op(INT_SET_BIT, stateInOut);
            Op(INT_END_IF);

            Op(INT_ELSE);

            Op(INT_SET_VARIABLE_TO_LITERAL, l->d.timer.name);

            Op(INT_END_IF);
            break;
        }
        case ELEM_CTU: {
            CheckConstantInRange(l->d.counter.max);
            char storeName[MAX_NAME_LEN];
            GenSymOneShot(storeName);

            Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_IF_BIT_CLEAR, storeName);
                    Op(INT_INCREMENT_VARIABLE, l->d.counter.name);
                Op(INT_END_IF);
            Op(INT_END_IF);
            Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut);

            Op(INT_IF_VARIABLE_LES_LITERAL, l->d.counter.name,
                l->d.counter.max);
                Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_ELSE);
                Op(INT_SET_BIT, stateInOut);
            Op(INT_END_IF);
            break;
        }
        case ELEM_CTD: {
            CheckConstantInRange(l->d.counter.max);
            char storeName[MAX_NAME_LEN];
            GenSymOneShot(storeName);

            Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_IF_BIT_CLEAR, storeName);
                    Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", 1);
                    Op(INT_SET_VARIABLE_SUBTRACT, l->d.counter.name,
                        l->d.counter.name, "$scratch", 0);
                Op(INT_END_IF);
            Op(INT_END_IF);
            Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut);

            Op(INT_IF_VARIABLE_LES_LITERAL, l->d.counter.name,
                l->d.counter.max);
                Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_ELSE);
                Op(INT_SET_BIT, stateInOut);
            Op(INT_END_IF);
            break;
        }
        case ELEM_CTC: {
            char storeName[MAX_NAME_LEN];
            GenSymOneShot(storeName);

            Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_IF_BIT_CLEAR, storeName);
                    Op(INT_INCREMENT_VARIABLE, l->d.counter.name);
                    Op(INT_IF_VARIABLE_LES_LITERAL, l->d.counter.name,
                        l->d.counter.max+1);
                    Op(INT_ELSE);
                        Op(INT_SET_VARIABLE_TO_LITERAL, l->d.counter.name,
                            (SWORD)0);
                    Op(INT_END_IF);
                Op(INT_END_IF);
            Op(INT_END_IF);
            Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut);
            break;
        }
        case ELEM_GRT:
        case ELEM_GEQ:
        case ELEM_LES:
        case ELEM_LEQ:
        case ELEM_NEQ:
        case ELEM_EQU: {
            char *op1 = VarFromExpr(l->d.cmp.op1, "$scratch");
            char *op2 = VarFromExpr(l->d.cmp.op2, "$scratch2");
            
            if(which == ELEM_GRT) {
                Op(INT_IF_VARIABLE_GRT_VARIABLE, op1, op2);
                Op(INT_ELSE);
            } else if(which == ELEM_GEQ) {
                Op(INT_IF_VARIABLE_GRT_VARIABLE, op2, op1);
            } else if(which == ELEM_LES) {
                Op(INT_IF_VARIABLE_GRT_VARIABLE, op2, op1);
                Op(INT_ELSE);
            } else if(which == ELEM_LEQ) {
                Op(INT_IF_VARIABLE_GRT_VARIABLE, op1, op2);
            } else if(which == ELEM_EQU) {
                Op(INT_IF_VARIABLE_EQUALS_VARIABLE, op1, op2);
                Op(INT_ELSE);
            } else if(which == ELEM_NEQ) {
                Op(INT_IF_VARIABLE_EQUALS_VARIABLE, op1, op2);
            } else oops();

            Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_END_IF);
            break;
        }
        case ELEM_ONE_SHOT_RISING: {
            char storeName[MAX_NAME_LEN];
            GenSymOneShot(storeName);

            Op(INT_COPY_BIT_TO_BIT, "$scratch", stateInOut);
            Op(INT_IF_BIT_SET, storeName);
            Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_END_IF);
            Op(INT_COPY_BIT_TO_BIT, storeName, "$scratch");
            break;
        }
        case ELEM_ONE_SHOT_FALLING: {
            char storeName[MAX_NAME_LEN];
            GenSymOneShot(storeName);
        
            Op(INT_COPY_BIT_TO_BIT, "$scratch", stateInOut);

            Op(INT_IF_BIT_CLEAR, stateInOut);
            Op(INT_IF_BIT_SET, storeName);
            Op(INT_SET_BIT, stateInOut);
            Op(INT_END_IF);
            Op(INT_ELSE);
            Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_END_IF);

            Op(INT_COPY_BIT_TO_BIT, storeName, "$scratch");
            break;
        }
        case ELEM_MOVE: {
            if(IsNumber(l->d.move.dest)) {
                Error(_("Move instruction: '%s' not a valid destination."),
                    l->d.move.dest);
                CompileError();
            }
            Op(INT_IF_BIT_SET, stateInOut);
            if(IsNumber(l->d.move.src)) {
                Op(INT_SET_VARIABLE_TO_LITERAL, l->d.move.dest,
                    CheckMakeNumber(l->d.move.src));
            } else {
                Op(INT_SET_VARIABLE_TO_VARIABLE, l->d.move.dest, l->d.move.src,
                    0);
            }
            Op(INT_END_IF);
            break;
        }

        // These four are highly processor-dependent; the int code op does
        // most of the highly specific work
        {
            case ELEM_READ_ADC:
                Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_READ_ADC, l->d.readAdc.name);
                Op(INT_END_IF);
                break;

            case ELEM_SET_PWM: {
                Op(INT_IF_BIT_SET, stateInOut);
                char line[80];
                // ugh; need a >16 bit literal though, could be >64 kHz
                sprintf(line, "%d", l->d.setPwm.targetFreq);
                Op(INT_SET_PWM, l->d.readAdc.name, line);
                Op(INT_END_IF);
                break;
            }
            case ELEM_PERSIST: {
                Op(INT_IF_BIT_SET, stateInOut);

                // At startup, get the persistent variable from flash.
                char isInit[MAX_NAME_LEN];
                GenSymOneShot(isInit);
                Op(INT_IF_BIT_CLEAR, isInit);
                    Op(INT_CLEAR_BIT, "$scratch");
                    Op(INT_EEPROM_BUSY_CHECK, "$scratch");
                    Op(INT_IF_BIT_CLEAR, "$scratch");
                        Op(INT_SET_BIT, isInit);
                        Op(INT_EEPROM_READ, l->d.persist.var, EepromAddrFree);
                    Op(INT_END_IF);
                Op(INT_END_IF);

                // While running, continuously compare the EEPROM copy of
                // the variable against the RAM one; if they are different,
                // write the RAM one to EEPROM. 
                Op(INT_CLEAR_BIT, "$scratch");
                Op(INT_EEPROM_BUSY_CHECK, "$scratch");
                Op(INT_IF_BIT_CLEAR, "$scratch");
                    Op(INT_EEPROM_READ, "$scratch", EepromAddrFree);
                    Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch",
                        l->d.persist.var);
                    Op(INT_ELSE);
                        Op(INT_EEPROM_WRITE, l->d.persist.var, EepromAddrFree);
                    Op(INT_END_IF);
                Op(INT_END_IF);

                Op(INT_END_IF);

                EepromAddrFree += 2;
                break;
            }
            case ELEM_UART_SEND:
                Op(INT_UART_SEND, l->d.uart.name, stateInOut);
                break;

            case ELEM_UART_RECV:
                Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_UART_RECV, l->d.uart.name, stateInOut);
                Op(INT_END_IF);
                break;
        }

        case ELEM_ADD:
        case ELEM_SUB:
        case ELEM_MUL:
        case ELEM_DIV: {
            if(IsNumber(l->d.math.dest)) {
                Error(_("Math instruction: '%s' not a valid destination."),
                    l->d.math.dest);
                CompileError();
            }
            Op(INT_IF_BIT_SET, stateInOut);

            char *op1 = VarFromExpr(l->d.math.op1, "$scratch");
            char *op2 = VarFromExpr(l->d.math.op2, "$scratch2");

            int intOp;
            if(which == ELEM_ADD) {
                intOp = INT_SET_VARIABLE_ADD;
            } else if(which == ELEM_SUB) {
                intOp = INT_SET_VARIABLE_SUBTRACT;
            } else if(which == ELEM_MUL) {
                intOp = INT_SET_VARIABLE_MULTIPLY;
            } else if(which == ELEM_DIV) {
                intOp = INT_SET_VARIABLE_DIVIDE;
            } else oops();

            Op(intOp, l->d.math.dest, op1, op2, 0);

            Op(INT_END_IF);
            break;
        }
        case ELEM_MASTER_RELAY:
            // Tricky: must set the master control relay if we reach this
            // instruction while the master control relay is cleared, because
            // otherwise there is no good way for it to ever become set
            // again.
            Op(INT_IF_BIT_CLEAR, "$mcr");
            Op(INT_SET_BIT, "$mcr");
            Op(INT_ELSE);
            Op(INT_COPY_BIT_TO_BIT, "$mcr", stateInOut);
            Op(INT_END_IF);
            break;

        case ELEM_SHIFT_REGISTER: {
            char storeName[MAX_NAME_LEN];
            GenSymOneShot(storeName);
            Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_IF_BIT_CLEAR, storeName);
                    int i;
                    for(i = (l->d.shiftRegister.stages-2); i >= 0; i--) {
                        char dest[MAX_NAME_LEN+10], src[MAX_NAME_LEN+10];
                        sprintf(src, "%s%d", l->d.shiftRegister.name, i);
                        sprintf(dest, "%s%d", l->d.shiftRegister.name, i+1);
                        Op(INT_SET_VARIABLE_TO_VARIABLE, dest, src);
                    }
                Op(INT_END_IF);
            Op(INT_END_IF);
            Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut);
            break;
        }
        case ELEM_LOOK_UP_TABLE: {
            // God this is stupid; but it will have to do, at least until I
            // add new int code instructions for this.
            int i;
            Op(INT_IF_BIT_SET, stateInOut);
            ElemLookUpTable *t = &(l->d.lookUpTable);
            for(i = 0; i < t->count; i++) {
                Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i);
                Op(INT_IF_VARIABLE_EQUALS_VARIABLE, t->index, "$scratch");
                    Op(INT_SET_VARIABLE_TO_LITERAL, t->dest, t->vals[i]);
                Op(INT_END_IF);
            }
            Op(INT_END_IF);
            break;
        }
        case ELEM_PIECEWISE_LINEAR: {
            // This one is not so obvious; we have to decide how best to
            // perform the linear interpolation, using our 16-bit fixed
            // point math.
            ElemPiecewiseLinear *t = &(l->d.piecewiseLinear);
            if(t->count == 0) {
                Error(_("Piecewise linear lookup table with zero elements!"));
                CompileError();
            }
            int i;
            int xThis = t->vals[0];
            for(i = 1; i < t->count; i++) {
                if(t->vals[i*2] <= xThis) {
                    Error(_("x values in piecewise linear table must be "
                        "strictly increasing."));
                    CompileError();
                }
                xThis = t->vals[i*2];
            }
            Op(INT_IF_BIT_SET, stateInOut);
            for(i = t->count - 1; i >= 1; i--) {
                int thisDx = t->vals[i*2] - t->vals[(i-1)*2];
                int thisDy = t->vals[i*2 + 1] - t->vals[(i-1)*2 + 1];
                // The output point is given by
                //    yout = y[i-1] + (xin - x[i-1])*dy/dx
                // and this is the best form in which to keep it, numerically
                // speaking, because you can always fix numerical problems
                // by moving the PWL points closer together.
               
                // Check for numerical problems, and fail if we have them.
                if((thisDx*thisDy) >= 32767 || (thisDx*thisDy) <= -32768) {
                    Error(_("Numerical problem with piecewise linear lookup "
                        "table. Either make the table entries smaller, "
                        "or space the points together more closely.\r\n\r\n"
                        "See the help file for details."));
                    CompileError();
                }

                // Hack to avoid AVR brge issue again, since long jumps break
                Op(INT_CLEAR_BIT, "$scratch");
                Op(INT_IF_VARIABLE_LES_LITERAL, t->index, t->vals[i*2]+1);
                    Op(INT_SET_BIT, "$scratch");
                Op(INT_END_IF);
                
                Op(INT_IF_BIT_SET, "$scratch");
                Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", t->vals[(i-1)*2]);
                Op(INT_SET_VARIABLE_SUBTRACT, "$scratch", t->index,
                    "$scratch", 0);
                Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", thisDx);
                Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch3", thisDy);
                Op(INT_SET_VARIABLE_MULTIPLY, t->dest, "$scratch", "$scratch3",
                    0);
                Op(INT_SET_VARIABLE_DIVIDE, t->dest, t->dest, "$scratch2", 0);

                Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch",
                    t->vals[(i-1)*2 + 1]);
                Op(INT_SET_VARIABLE_ADD, t->dest, t->dest, "$scratch", 0);
                Op(INT_END_IF);
            }
            Op(INT_END_IF);
            break;
        }
        case ELEM_FORMATTED_STRING: {
            // Okay, this one is terrible and ineffcient, and it's a huge pain
            // to implement, but people want it a lot. The hard part is that
            // we have to let the PLC keep cycling, of course, and also that
            // we must do the integer to ASCII conversion sensisbly, with
            // only one divide per PLC cycle.

            // This variable is basically our sequencer: it is a counter that
            // increments every time we send a character.
            char seq[MAX_NAME_LEN];
            GenSymFormattedString(seq);

            // The variable whose value we might interpolate.
            char *var = l->d.fmtdStr.var;

            // This is the state variable for our integer-to-string conversion.
            // It contains the absolute value of var, possibly with some
            // of the higher powers of ten missing.
            char convertState[MAX_NAME_LEN];
            GenSymFormattedString(convertState);

            // We might need to suppress some leading zeros.
            char isLeadingZero[MAX_NAME_LEN];
            GenSymFormattedString(isLeadingZero);

            // This is a table of characters to transmit, as a function of the
            // sequencer position (though we might have a hole in the middle
            // for the variable output)
            char outputChars[MAX_LOOK_UP_TABLE_LEN];

            BOOL mustDoMinus = FALSE;

            // The total number of characters that we transmit, including
            // those from the interpolated variable.
            int steps;

            // The total number of digits to convert.
            int digits = -1;

            // So count that now, and build up our table of fixed things to
            // send.
            steps = 0;
            char *p = l->d.fmtdStr.string;
            while(*p) {
                if(*p == '\\' && (isdigit(p[1]) || p[1] == '-')) {
                    if(digits >= 0) {
                        Error(_("Multiple escapes (\\0-9) present in format "
                            "string, not allowed."));
                        CompileError();
                    }
                    p++;
                    if(*p == '-') {
                        mustDoMinus = TRUE;
                        outputChars[steps++] = 1;
                        p++;
                    }
                    if(!isdigit(*p) || (*p - '0') > 5 || *p == '0') {
                        Error(_("Bad escape sequence following \\; for a "
                            "literal backslash, use \\\\"));
                        CompileError();
                    }
                    digits = (*p - '0');
                    int i;
                    for(i = 0; i < digits; i++) {
                        outputChars[steps++] = 0;
                    }
                } else if(*p == '\\') {
                    p++;
                    switch(*p) {
                        case 'r': outputChars[steps++] = '\r'; break;
                        case 'n': outputChars[steps++] = '\n'; break;
                        case 'b': outputChars[steps++] = '\b'; break;
                        case 'f': outputChars[steps++] = '\f'; break;
                        case '\\': outputChars[steps++] = '\\'; break;
                        case 'x': {
                            int h, l;
                            p++;
                            h = HexDigit(*p);
                            if(h >= 0) {
                                p++;
                                l = HexDigit(*p);
                                if(l >= 0) {
                                    outputChars[steps++] = (h << 4) | l;
                                    break;
                                }
                            }
                            Error(_("Bad escape: correct form is \\xAB."));
                            CompileError();
                            break;
                        }
                        default:
                            Error(_("Bad escape '\\%c'"), *p);
                            CompileError();
                            break;
                    }
                } else {
                    outputChars[steps++] = *p;
                }
                if(*p) p++;
            }

            if(digits >= 0 && (strlen(var) == 0)) {
                Error(_("Variable is interpolated into formatted string, but "
                    "none is specified."));
                CompileError();
            } else if(digits < 0 && (strlen(var) > 0)) {
                Error(_("No variable is interpolated into formatted string, "
                    "but a variable name is specified. Include a string like "
                    "'\\-3', or leave variable name blank."));
                CompileError();
            }

            // We want to respond to rising edges, so yes we need a one shot.
            char oneShot[MAX_NAME_LEN];
            GenSymOneShot(oneShot);

            Op(INT_IF_BIT_SET, stateInOut);
                Op(INT_IF_BIT_CLEAR, oneShot);
                    Op(INT_SET_VARIABLE_TO_LITERAL, seq, (SWORD)0);
                Op(INT_END_IF);
            Op(INT_END_IF);
            Op(INT_COPY_BIT_TO_BIT, oneShot, stateInOut);

            // Everything that involves seqScratch is a terrible hack to
            // avoid an if statement with a big body, which is the risk
            // factor for blowing up on PIC16 page boundaries.

            char *seqScratch = "$scratch3";

            Op(INT_SET_VARIABLE_TO_VARIABLE, seqScratch, seq);

            // No point doing any math unless we'll get to transmit this
            // cycle, so check that first.

            Op(INT_IF_VARIABLE_LES_LITERAL, seq, steps);
            Op(INT_ELSE);
                Op(INT_SET_VARIABLE_TO_LITERAL, seqScratch, -1);
            Op(INT_END_IF);

            Op(INT_CLEAR_BIT, "$scratch");
            Op(INT_UART_SEND, "$scratch", "$scratch");
            Op(INT_IF_BIT_SET, "$scratch");
                Op(INT_SET_VARIABLE_TO_LITERAL, seqScratch, -1);
            Op(INT_END_IF);

            // So we transmit this cycle, so check out which character.
            int i;
            int digit = 0;
            for(i = 0; i < steps; i++) {
                if(outputChars[i] == 0) {
                    // Note gross hack to work around limit of range for
                    // AVR brne op, which is +/- 64 instructions.
                    Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i);
                    Op(INT_CLEAR_BIT, "$scratch");
                    Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", seqScratch);
                        Op(INT_SET_BIT, "$scratch");
                    Op(INT_END_IF);

                    Op(INT_IF_BIT_SET, "$scratch");

                    // Start the integer-to-string

                    // If there's no minus, then we have to load up
                    // convertState ourselves the first time.
                    if(digit == 0 && !mustDoMinus) {
                        Op(INT_SET_VARIABLE_TO_VARIABLE, convertState, var);
                    }
                    if(digit == 0) {
                        Op(INT_SET_BIT, isLeadingZero);
                    }
                    
                    Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch",
                        TenToThe((digits-digit)-1));
                    Op(INT_SET_VARIABLE_DIVIDE, "$scratch2", convertState,
                        "$scratch", 0);
                    Op(INT_SET_VARIABLE_MULTIPLY, "$scratch", "$scratch",
                        "$scratch2", 0);
                    Op(INT_SET_VARIABLE_SUBTRACT, convertState,
                        convertState, "$scratch", 0);
                    Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", '0');
                    Op(INT_SET_VARIABLE_ADD, "$scratch2", "$scratch2",
                        "$scratch", 0);

                    // Suppress all but the last leading zero.
                    if(digit != (digits - 1)) {
                        Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch",
                            "$scratch2");
                            Op(INT_IF_BIT_SET, isLeadingZero);
                                Op(INT_SET_VARIABLE_TO_LITERAL,
                                    "$scratch2", ' ');
                            Op(INT_END_IF);
                        Op(INT_ELSE);
                            Op(INT_CLEAR_BIT, isLeadingZero);
                        Op(INT_END_IF);
                    }

                    Op(INT_END_IF);

                    digit++;
                } else if(outputChars[i] == 1) {
                    // do the minus; ugliness to get around the BRNE jump
                    // size limit, though
                    Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i);
                    Op(INT_CLEAR_BIT, "$scratch");
                    Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", seqScratch);
                        Op(INT_SET_BIT, "$scratch");
                    Op(INT_END_IF);
                    Op(INT_IF_BIT_SET, "$scratch");
                       
                        // Also do the `absolute value' calculation while
                        // we're at it.
                        Op(INT_SET_VARIABLE_TO_VARIABLE, convertState, var);
                        Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", ' ');
                        Op(INT_IF_VARIABLE_LES_LITERAL, var, (SWORD)0);
                            Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", '-');
                            Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch",
                                (SWORD)0);
                            Op(INT_SET_VARIABLE_SUBTRACT, convertState,
                                "$scratch", var, 0);
                        Op(INT_END_IF);

                    Op(INT_END_IF);
                } else {
                    // just another character
                    Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i);
                    Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", seqScratch);
                        Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", 
                            outputChars[i]);
                    Op(INT_END_IF);
                }
            }

            Op(INT_IF_VARIABLE_LES_LITERAL, seqScratch, (SWORD)0);
            Op(INT_ELSE);
                Op(INT_SET_BIT, "$scratch");
                Op(INT_UART_SEND, "$scratch2", "$scratch");
                Op(INT_INCREMENT_VARIABLE, seq);
            Op(INT_END_IF);
    
            // Rung-out state: true if we're still running, else false
            Op(INT_CLEAR_BIT, stateInOut);
            Op(INT_IF_VARIABLE_LES_LITERAL, seq, steps);
                Op(INT_SET_BIT, stateInOut);
            Op(INT_END_IF);
            break;
        }
        case ELEM_OPEN:
            Op(INT_CLEAR_BIT, stateInOut);
            break;

        case ELEM_SHORT:
            // goes straight through
            break;

        case ELEM_PLACEHOLDER:
            Error(
              _("Empty row; delete it or add instructions before compiling."));
            CompileError();
            break;

        default:
            oops();
            break;
    }

    if(which != ELEM_SERIES_SUBCKT && which != ELEM_PARALLEL_SUBCKT) {
        // then it is a leaf; let the simulator know which leaf it
        // should be updating for display purposes
        SimState(&(l->poweredAfter), stateInOut);
    }
}

//-----------------------------------------------------------------------------
// Generate intermediate code for the entire program. Return TRUE if it worked,
// else FALSE.
//-----------------------------------------------------------------------------
BOOL GenerateIntermediateCode(void)
{
    GenSymCountParThis = 0;
    GenSymCountParOut = 0;
    GenSymCountOneShot = 0;
    GenSymCountFormattedString = 0;

    // The EEPROM addresses for the `Make Persistent' op are assigned at
    // int code generation time.
    EepromAddrFree = 0;
    
    IntCodeLen = 0;
    memset(IntCode, 0, sizeof(IntCode));

    if(setjmp(CompileErrorBuf) != 0) {
        return FALSE;
    }

    Op(INT_SET_BIT, "$mcr");

    int i;
    for(i = 0; i < Prog.numRungs; i++) {
        if(Prog.rungs[i]->count == 1 && 
            Prog.rungs[i]->contents[0].which == ELEM_COMMENT)
        {
            // nothing to do for this one
            continue;
        }
        Comment("");
        Comment("start rung %d", i+1);
        Op(INT_COPY_BIT_TO_BIT, "$rung_top", "$mcr");
        SimState(&(Prog.rungPowered[i]), "$rung_top");
        IntCodeFromCircuit(ELEM_SERIES_SUBCKT, Prog.rungs[i], "$rung_top");
    }

    return TRUE;
}
