#define REPT_IRPC__C

/*
        .REPT
        .IRPC streams
*/

#include <stdlib.h>
#include <string.h>

#include "rept_irpc.h"                 /* my own definitions */

#include "util.h"
#include "assemble_aux.h"
#include "parse.h"
#include "listing.h"
#include "macros.h"
#include "assemble_globals.h"


/* *** implement REPT_STREAM */

typedef struct rept_stream {
    BUFFER_STREAM   bstr;
    int             count;      /* The current repeat countdown */
    int             savecond;   /* conditional stack level at time of
                                   expansion */
} REPT_STREAM;

/* rept_stream_getline gets a line from a repeat stream.  At the end of
   each count, the coutdown is decreated and the stream is reset to
   its beginning. */

char           *rept_stream_getline(
    STREAM *str)
{
    REPT_STREAM    *rstr = (REPT_STREAM *) str;
    char           *cp;

    for (;;) {
        if (rstr->count <= 0)
            return NULL;

        if ((cp = buffer_stream_getline(str)) != NULL)
            return cp;

        buffer_stream_rewind(str);
        rstr->count--;
    }
}

/* rept_stream_delete unwinds nested conditionals like .MEXIT does. */

void rept_stream_delete(
    STREAM *str)
{
    REPT_STREAM    *rstr = (REPT_STREAM *) str;

    pop_cond(rstr->savecond);          /* complete unterminated
                                          conditionals */
    buffer_stream_delete(&rstr->bstr.stream);
}

/* The VTBL */

STREAM_VTBL     rept_stream_vtbl = {
    rept_stream_delete, rept_stream_getline, buffer_stream_rewind
};

/* expand_rept is called when a .REPT is encountered in the input. */

STREAM         *expand_rept(
    STACK *stack,
    char *cp)
{
    EX_TREE        *value;
    BUFFER         *gb;
    REPT_STREAM    *rstr;
    int             levelmod;

    value = parse_expr(cp, 0);
    if (value->type != EX_LIT) {
        report(stack->top, ".REPT value must be constant\n");
        free_tree(value);
        return NULL;
    }
    /*
     * Reading the next lines-to-be-repeated overwrites the line buffer
     * that the caller is using. So for junk-at-end-of-line checking we
     * need to do it here.
     */
    check_eol(stack, value->cp);

    list_value(stack->top, value->data.lit);

    gb = new_buffer();

    levelmod = 0;
    if (!list_md) {
        list_level--;
        levelmod = 1;
    }

    read_body(stack, gb, NULL, FALSE);

    list_level += levelmod;

    rstr = memcheck(malloc(sizeof(REPT_STREAM))); {
        char           *name = memcheck(malloc(strlen(stack->top->name) + 32));

        sprintf(name, "%s:%d->.REPT", stack->top->name, stack->top->line);
        buffer_stream_construct(&rstr->bstr, gb, name);
        free(name);
    }

    rstr->count = value->data.lit;
    rstr->bstr.stream.vtbl = &rept_stream_vtbl;
    rstr->savecond = last_cond;

    buffer_free(gb);
    free_tree(value);

    return &rstr->bstr.stream;
}

/* *** implement IRP_STREAM */

typedef struct irp_stream {
    BUFFER_STREAM   bstr;
    char           *label;      /* The substitution label */
    char           *items;      /* The substitution items (in source code
                                   format) */
    int             offset;     /* Current offset into "items" */
    BUFFER         *body;       /* Original body */
    int             savecond;   /* Saved conditional level */
} IRP_STREAM;

/* irp_stream_getline expands the IRP as the stream is read. */
/* Each time an iteration is exhausted, the next iteration is
   generated. */

char           *irp_stream_getline(
    STREAM *str)
{
    IRP_STREAM     *istr = (IRP_STREAM *) str;
    char           *cp;
    BUFFER         *buf;
    ARG            *arg;

    for (;;) {
        if ((cp = buffer_stream_getline(str)) != NULL)
            return cp;

        cp = istr->items + istr->offset;

        if (!*cp)
            return NULL;               /* No more items.  EOF. */

        arg = new_arg();
        arg->next = NULL;
        arg->locsym = 0;
        arg->label = istr->label;
        arg->value = getstring_macarg(str, cp, &cp);
        cp = skipdelim(cp);
        istr->offset = (int) (cp - istr->items);

        buf = subst_args(istr->body, arg);

        free(arg->value);
        free(arg);
        buffer_stream_set_buffer(&istr->bstr, buf);
        buffer_free(buf);
    }
}

/* irp_stream_delete - also pops the conditional stack */

void irp_stream_delete(
    STREAM *str)
{
    IRP_STREAM     *istr = (IRP_STREAM *) str;

    pop_cond(istr->savecond);          /* complete unterminated
                                          conditionals */

    buffer_free(istr->body);
    free(istr->items);
    free(istr->label);
    buffer_stream_delete(str);
}

STREAM_VTBL     irp_stream_vtbl = {
    irp_stream_delete, irp_stream_getline, buffer_stream_rewind
};

/* We occasionally see .IRP with the formal name in angle brackets.  I
 * have no idea why, but it appears to be legal.  So allow that.  Not
 * sure if it should be allowed elsewhere, e.g., in .MACRO.  For now,
 * don't.  */
static char *get_irp_sym (char *cp, char **endcp, int *islocal)
{
    char *ret = NULL;
    
    cp = skipwhite(cp);
    if (*cp == '<') {
        ret = get_symbol (cp + 1, &cp, islocal);
        if (*cp++ != '>') {
            *endcp = cp;
            return NULL;
        }
    }
    else {
        ret = get_symbol (cp, &cp, islocal);
    }
    *endcp = cp;
    return ret;
}

/* expand_irp is called when a .IRP is encountered in the input. */

STREAM         *expand_irp(
    STACK *stack,
    char *cp)
{
    char           *label,
                   *items;
    BUFFER         *gb;
    int             levelmod = 0;
    IRP_STREAM     *str;

    label = get_irp_sym(cp, &cp, NULL);
    if (!label) {
        report(stack->top, "Illegal .IRP syntax\n");
        return NULL;
    }

    cp = skipdelim(cp);

    items = getstring(cp, &cp);
    if (!items) {
        report(stack->top, "Illegal .IRP syntax\n");
        free(label);
        return NULL;
    }

    check_eol(stack, cp);

    gb = new_buffer();

    levelmod = 0;
    if (!list_md) {
        list_level--;
        levelmod++;
    }

    read_body(stack, gb, NULL, FALSE);

    list_level += levelmod;

    str = memcheck(malloc(sizeof(IRP_STREAM))); {
        char           *name = memcheck(malloc(strlen(stack->top->name) + 32));

        sprintf(name, "%s:%d->.IRP", stack->top->name, stack->top->line);
        buffer_stream_construct(&str->bstr, NULL, name);
        free(name);
    }

    str->bstr.stream.vtbl = &irp_stream_vtbl;

    str->body = gb;
    str->items = items;
    str->offset = 0;
    str->label = label;
    str->savecond = last_cond;

    return &str->bstr.stream;
}


/* *** implement IRPC_STREAM */

typedef struct irpc_stream {
    BUFFER_STREAM   bstr;
    char           *label;      /* The substitution label */
    char           *items;      /* The substitution items (in source code
                                   format) */
    int             offset;     /* Current offset in "items" */
    BUFFER         *body;       /* Original body */
    int             savecond;   /* conditional stack at invocation */
} IRPC_STREAM;

/* irpc_stream_getline - same comments apply as with irp_stream_getline, but
   the substitution is character-by-character */

char           *irpc_stream_getline(
    STREAM *str)
{
    IRPC_STREAM    *istr = (IRPC_STREAM *) str;
    char           *cp;
    BUFFER         *buf;
    ARG            *arg;

    for (;;) {
        if ((cp = buffer_stream_getline(str)) != NULL)
            return cp;

        cp = istr->items + istr->offset;

        if (!*cp)
            return NULL;               /* No more items.  EOF. */

        arg = new_arg();
        arg->next = NULL;
        arg->locsym = 0;
        arg->label = istr->label;
        arg->value = memcheck(malloc(2));
        arg->value[0] = *cp++;
        arg->value[1] = 0;
        istr->offset = (int) (cp - istr->items);

        buf = subst_args(istr->body, arg);

        free(arg->value);
        free(arg);
        buffer_stream_set_buffer(&istr->bstr, buf);
        buffer_free(buf);
    }
}

/* irpc_stream_delete - also pops contidionals */

void irpc_stream_delete(
    STREAM *str)
{
    IRPC_STREAM    *istr = (IRPC_STREAM *) str;

    pop_cond(istr->savecond);          /* complete unterminated
                                          conditionals */
    buffer_free(istr->body);
    free(istr->items);
    free(istr->label);
    buffer_stream_delete(str);
}

STREAM_VTBL     irpc_stream_vtbl = {
    irpc_stream_delete, irpc_stream_getline, buffer_stream_rewind
};

/* expand_irpc - called when .IRPC is encountered in the input */

STREAM         *expand_irpc(
    STACK *stack,
    char *cp)
{
    char           *label,
                   *items;
    BUFFER         *gb;
    int             levelmod = 0;
    IRPC_STREAM    *str;

    label = get_irp_sym(cp, &cp, NULL);
    if (!label) {
        report(stack->top, "Illegal .IRPC syntax\n");
        return NULL;
    }

    cp = skipdelim(cp);

    items = getstring(cp, &cp);
    if (!items) {
        report(stack->top, "Illegal .IRPC syntax\n");
        free(label);
        return NULL;
    }

    check_eol(stack, cp);

    gb = new_buffer();

    levelmod = 0;
    if (!list_md) {
        list_level--;
        levelmod++;
    }

    read_body(stack, gb, NULL, FALSE);

    list_level += levelmod;

    str = memcheck(malloc(sizeof(IRPC_STREAM))); {
        char           *name = memcheck(malloc(strlen(stack->top->name) + 32));

        sprintf(name, "%s:%d->.IRPC", stack->top->name, stack->top->line);
        buffer_stream_construct(&str->bstr, NULL, name);
        free(name);
    }

    str->bstr.stream.vtbl = &irpc_stream_vtbl;
    str->body = gb;
    str->items = items;
    str->offset = 0;
    str->label = label;
    str->savecond = last_cond;

    return &str->bstr.stream;
}
