Make the Ragel config parser tokenize one line at a time.  This approach allows a far simpler state machine and better error diagnostics.

/* config.rl - configure file parser for ncron
 *
 * (c) 2003-2014 Nicholas J. Kain <njkain at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <limits.h>
#include "nk/log.h"
#include "nk/malloc.h"
#include "nk/privilege.h"

#include "defines.h"
#include "config.h"
#include "sched.h"

/* BSD uses OFILE rather than NOFILE... */
#ifndef RLIMIT_NOFILE
#  define RLIMIT_NOFILE RLIMIT_OFILE
#endif

static int cfg_reload;    /* 0 on first call, 1 on subsequent calls */

struct ParseCfgState
{
    char v_str[1024];

    cronentry_t *stack;
    cronentry_t *deadstack;
    cronentry_t *ce;

    char *execfile;

    char *jobid_st;
    char *time_st;
    char *intv_st;
    char *intv2_st;
    char *strv_st;

    size_t v_strlen;
    size_t linenum;

    unsigned int v_jobid;
    unsigned int v_time;

    int v_int;
    int v_int2;

    int cs;
    int noextime;
    int cmdret;

    bool intv2_exist;
};

static void nullify_item(cronentry_t *item)
{
    if (!item)
        return;
    item->id = 0;
    item->command = NULL;
    item->args = NULL;
    item->chroot = NULL;
    item->numruns = 0;
    item->maxruns = 0;
    item->journal = 0;
    item->user = 0;
    item->group = 0;
    item->month = NULL;
    item->day = NULL;
    item->weekday = NULL;
    item->hour = NULL;
    item->minute = NULL;
    item->interval = 0;
    item->exectime = 0;
    item->lasttime = 0;
    item->limits = NULL;
    item->next = NULL;
}

static void nullify_limits(limit_t *l)
{
    if (!l)
        return;
    l->cpu = NULL;
    l->fsize = NULL;
    l->data = NULL;
    l->stack = NULL;
    l->core = NULL;
    l->rss = NULL;
    l->nproc = NULL;
    l->nofile = NULL;
    l->memlock = NULL;
    l->as = NULL;
}

struct hstm {
    char *st;
    int cs;
    int id;
    time_t exectime;
    time_t lasttime;
    unsigned int numruns;
    bool got_exectime:1;
    bool got_lasttime:1;
    bool got_numruns:1;
};

%%{
    machine history_m;
    access hstm->;

    action St { hstm->st = p; }
    action LastTimeEn {
        hstm->lasttime = atoi(p);
        hstm->got_lasttime = 1;
    }
    action NumRunsEn {
        hstm->numruns = atoi(p);
        hstm->got_numruns = 1;
    }
    action ExecTimeEn {
        hstm->exectime = atoi(p);
        hstm->got_exectime = 1;
    }
    action IdEn { hstm->id = atoi(p); }

    lasttime = '|' digit+ > St % LastTimeEn;
    numruns = ':' digit+ > St % NumRunsEn;
    exectime = '=' digit+ > St % ExecTimeEn;
    id = digit+ > St % IdEn;
    main := id (numruns | exectime | lasttime)+;
}%%

%% write data;

static int do_get_history(struct hstm *hstm, char *buf, size_t blen)
{
    char *p = buf;
    const char *pe = buf + blen;
    const char *eof = pe;

    %% write init;
    %% write exec;

    if (hstm->cs >= history_m_first_final)
        return 1;
    if (hstm->cs == history_m_error)
        return -1;
    return -2;
}

static void get_history(cronentry_t *item, char *path, int noextime)
{
    struct hstm hstm = {0};

    assert(item);

    if (!noextime && !hstm.got_exectime)
        item->exectime = 0;

    FILE *f = fopen(path, "r");
    if (!f) {
        log_line("failed to open history file \"%s\" for read", path);
        if (!noextime)
            item->exectime = (time_t)0; /* gracefully fail */
        return;
    }

    char buf[MAXLINE];
    while (fgets(buf, sizeof buf, f)) {
        int r = do_get_history(&hstm, buf, strlen(buf));
        if (r < 0)
            suicide("%s: do_get_history(%s) failed", __func__, path);
        if (r == 1) {
            if (hstm.id == item->id) {
                if (hstm.got_lasttime)
                    item->lasttime = hstm.lasttime;
                if (hstm.got_numruns)
                    item->numruns = hstm.numruns;
                if (hstm.got_exectime && !noextime)
                    item->exectime = hstm.exectime;
                break;
            }
        }
    }
    if (fclose(f))
        suicide("%s: fclose(%s) failed: %s", __func__, path, strerror(errno));
}

static void setlim(struct ParseCfgState *ncs, int type)
{
    struct rlimit **p = NULL;
    if (!ncs->ce->limits) {
        ncs->ce->limits = xmalloc(sizeof(limit_t));
        nullify_limits(ncs->ce->limits);
    }
    switch (type) {
    case RLIMIT_CPU: *p = ncs->ce->limits->cpu; break;
    case RLIMIT_FSIZE: *p = ncs->ce->limits->fsize; break;
    case RLIMIT_DATA: *p = ncs->ce->limits->data; break;
    case RLIMIT_STACK: *p = ncs->ce->limits->stack; break;
    case RLIMIT_CORE: *p = ncs->ce->limits->core; break;
    case RLIMIT_RSS: *p = ncs->ce->limits->rss; break;
    case RLIMIT_NPROC: *p = ncs->ce->limits->nproc; break;
    case RLIMIT_NOFILE: *p = ncs->ce->limits->nofile; break;
    case RLIMIT_MEMLOCK: *p = ncs->ce->limits->memlock; break;
#ifndef BSD
    case RLIMIT_AS: *p = ncs->ce->limits->as; break;
#endif /* BSD */
    default: return;
    }
    if (!p)
        suicide("%s: unexpected NULL, corruption?", __func__);
    if (!*p)
        *p = xmalloc(sizeof(struct rlimit));
    if (ncs->v_int == 0) (*p)->rlim_cur = RLIM_INFINITY;
    else (*p)->rlim_cur = ncs->v_int;

    if (ncs->v_int2 == 0) (*p)->rlim_max = RLIM_INFINITY;
    else (*p)->rlim_cur = ncs->v_int2;
}

static void addipairlist(struct ParseCfgState *ncs,
                         ipair_node_t **list, int wildcard, int min, int max)
{
    ipair_node_t *l;
    int low, high = wildcard;

    low = ncs->v_int;
    if (ncs->intv2_exist)
        high = ncs->v_int2;

    if (high == wildcard)
        high = low;
    if (low > max || low < min)
        low = wildcard;
    if (high > max || high < min)
        high = wildcard;

    /* we don't allow meaningless 'rules' */
    if (low == wildcard && high == wildcard)
        return;

    if (*list == NULL) {
        *list = xmalloc(sizeof(ipair_node_t));
        l = *list;
    } else {
        l = *list;
        while (l->next)
            l = l->next;

        l->next = xmalloc(sizeof(ipair_node_t));
        l = l->next;
    }

    /* discontinuous range, split into two continuous rules... */
    if (low > high) {
        l->node.l = low;
        l->node.h = max;
        l->next = xmalloc(sizeof(ipair_node_t));
        l = l->next;
        l->node.l = min;
        l->node.h = high;
        l->next = NULL;
    } else {
        /* handle continuous ranges normally */
        l->node.l = low;
        l->node.h = high;
        l->next = NULL;
    }
}

static void setgroupv(struct ParseCfgState *ncs)
{
    if (nk_gidbyname(ncs->v_str, &ncs->ce->group))
        suicide("%s: nonexistent group specified at line %zu", ncs->linenum);
}

static void setuserv(struct ParseCfgState *ncs)
{
    if (nk_uidgidbyname(ncs->v_str, &ncs->ce->user, &ncs->ce->group))
        suicide("%s: nonexistent user specified at line %zu", ncs->linenum);
}

static void parse_assign_str(char **dest, char *src, size_t srclen,
                             size_t linenum)
{
    if (srclen > INT_MAX)
        suicide("%s: srclen would overflow int at line %zu",
                __func__, linenum);
    free(*dest);
    *dest = xmalloc(srclen+1);
    ssize_t l = snprintf(*dest, srclen+1, "%.*s", (int)srclen, src);
    if (l < 0 || (size_t)l >= srclen+1)
        suicide("%s: snprintf failed l=%u srclen+1=%u at line %zu",
                __func__, l, srclen+1, linenum);
}

struct pckm {
    char *st;
    int cs;
};

%%{
    machine parse_cmd_key_m;
    access pckm.;

    action St { pckm.st = p; }
    action CmdEn {
        size_t cmdlen = p - pckm.st;
        parse_assign_str(&ncs->ce->command, pckm.st, cmdlen, ncs->linenum);
        // Unescape "\\" and "\ ".
        int prevsl = 0;
        size_t i = 0;
        while (ncs->ce->command[i]) {
            if (prevsl && (ncs->ce->command[i] == '\\'
                           || ncs->ce->command[i] == ' ')) {
                memmove(ncs->ce->command + i - 1,
                        ncs->ce->command + i, cmdlen--);
                continue;
            }
            if (ncs->ce->command[i] == '\\')
                prevsl = 1;
            ++i;
        }
    }
    action ArgEn {
        parse_assign_str(&ncs->ce->args, pckm.st, p - pckm.st, ncs->linenum);
    }

    sptab = [ \t];
    cmd = sptab+ ([^\0 \\] | '\\\\' | '\\ ')+ > St % CmdEn;
    args = sptab+ ([^\0])* > St % ArgEn;
    main := cmd args;
}%%

%% write data;

// cmdret = 0: Not parsed a command key yet.
// cmdret = 1: Success.  Got a command key.
// cmdret = -1: Error: malformed command key.
// cmdret = -2: Error: incomplete command key.
// cmdret = -3: Error: duplicate command key.
static void parse_command_key(struct ParseCfgState *ncs)
{
    char *p = ncs->v_str;
    const char *pe = ncs->v_str + ncs->v_strlen;
    const char *eof = pe;

    struct pckm pckm = {0};

    if (ncs->cmdret != 0) {
        ncs->cmdret = -3;
        suicide("Duplicate 'command' value at line %zu", ncs->linenum);
    }

    %% write init;
    %% write exec;

    if (pckm.cs == parse_cmd_key_m_error) {
        ncs->cmdret = -1;
        suicide("Malformed 'command' value at line %zu", ncs->linenum);
    } else if (pckm.cs >= parse_cmd_key_m_first_final)
        ncs->cmdret = 1;
    else {
        ncs->cmdret = -2;
        suicide("Incomplete 'command' value at line %zu", ncs->linenum);
    }
}

static void create_ce(struct ParseCfgState *ncs)
{
    assert(!ncs->ce);
    ncs->ce = xmalloc(sizeof(cronentry_t));
    nullify_item(ncs->ce);
    ncs->cmdret = 0;
    ncs->noextime = 0;
}

static void finish_ce(struct ParseCfgState *ncs)
{
    assert(ncs->ce);
    if (ncs->ce->id <= 0
        || (ncs->ce->interval <= 0 && ncs->ce->exectime <= 0)
        || !ncs->ce->command || ncs->cmdret < 1) {
        free_cronentry(&ncs->ce);
        return;
    }

    /* we have a job to insert */
    if (ncs->ce->exectime != 0) { /* runat task */
        get_history(ncs->ce, ncs->execfile, 1);

        /* insert iif we haven't exceeded maxruns */
        if (ncs->ce->maxruns == 0 || ncs->ce->numruns < ncs->ce->maxruns)
            stack_insert(ncs->ce, &ncs->stack);
        else
            stack_insert(ncs->ce, &ncs->deadstack);
    } else { /* interval task */
        get_history(ncs->ce, ncs->execfile, ncs->noextime && !cfg_reload);

        /* compensate for user edits to job constraints */
        time_t ttm = get_first_time(ncs->ce);
        if (ttm - ncs->ce->lasttime >= ncs->ce->interval)
            ncs->ce->exectime = ttm;
        else
            force_to_constraint(ncs->ce);

        /* insert iif numruns < maxruns and no constr error */
        if ((ncs->ce->maxruns == 0 || ncs->ce->numruns < ncs->ce->maxruns)
            && ncs->ce->exectime != 0)
            stack_insert(ncs->ce, &ncs->stack);
        else
            stack_insert(ncs->ce, &ncs->deadstack);
    }
    ncs->ce = NULL;
}

%%{
    machine ncrontab;
    access ncs->;

    spc = [ \t];
    eqsep = spc+ '=' spc+;
    cmdterm = [\0\n];

    action TUnitSt { ncs->time_st = p; ncs->v_time = 0; }
    action TSecEn  { ncs->v_time +=          atoi(ncs->time_st); }
    action TMinEn  { ncs->v_time += 60     * atoi(ncs->time_st); }
    action THrEn   { ncs->v_time += 3600   * atoi(ncs->time_st); }
    action TDayEn  { ncs->v_time += 86400  * atoi(ncs->time_st); }
    action TWeekEn { ncs->v_time += 604800 * atoi(ncs->time_st); }

    action IntValSt {
        ncs->intv_st = p;
        ncs->v_int = ncs->v_int2 = 0;
        ncs->intv2_exist = false;
    }
    action IntValEn { ncs->v_int = atoi(ncs->intv_st); }
    action IntVal2St { ncs->intv2_st = p; }
    action IntVal2En {
        ncs->v_int2 = atoi(ncs->intv2_st);
        ncs->intv2_exist = true;
    }

    action StrValSt { ncs->strv_st = p; ncs->v_strlen = 0; }
    action StrValEn {
        ncs->v_strlen = p - ncs->strv_st;
        if (ncs->v_strlen <= INT_MAX) {
            ssize_t snl = snprintf(ncs->v_str, sizeof ncs->v_str,
                                   "%.*s", (int)ncs->v_strlen, ncs->strv_st);
            if (snl < 0 || (size_t)snl >= sizeof ncs->v_str)
                suicide("error parsing line %u in crontab; too long?",
                        ncs->linenum);
        }
    }

    t_sec  = (digit+ > TUnitSt) 's' % TSecEn;
    t_min  = (digit+ > TUnitSt) 'm' % TMinEn;
    t_hr   = (digit+ > TUnitSt) 'h' % THrEn;
    t_day  = (digit+ > TUnitSt) 'd' % TDayEn;
    t_week = (digit+ > TUnitSt) 'w' % TWeekEn;
    t_any = (t_sec | t_min | t_hr | t_day | t_week);

    intval = (digit+ > IntValSt % IntValEn) cmdterm;
    timeval = t_any (spc* t_any)* cmdterm;
    intrangeval = (digit+ > IntValSt % IntValEn)
                  (',' (digit+ > IntVal2St % IntVal2En))? cmdterm;
    stringval = ([^\0\n]+ > StrValSt % StrValEn) cmdterm;

    action JournalEn { ncs->ce->journal = 1; }
    action NoExecTimeEn { ncs->noextime = 1; }

    journal = 'journal'i cmdterm % JournalEn;
    noexectime = 'noexectime'i cmdterm % NoExecTimeEn;

    action RunAtEn {
        ncs->ce->exectime = ncs->v_int;
        ncs->ce->maxruns = 1;
        ncs->ce->journal = 1;
    }
    action MaxRunsEn {
        if (ncs->ce->exectime == 0)
            ncs->ce->maxruns = ncs->v_int;
    }

    runat = 'runat'i eqsep intval % RunAtEn;
    maxruns = 'maxruns'i eqsep intval % MaxRunsEn;

    action LimAsEn { setlim(ncs, RLIMIT_AS); }
    action LimMemlockEn { setlim(ncs, RLIMIT_MEMLOCK); }
    action LimNofileEn { setlim(ncs, RLIMIT_NOFILE); }
    action LimNprocEn { setlim(ncs, RLIMIT_NPROC); }
    action LimRssEn { setlim(ncs, RLIMIT_RSS); }
    action LimCoreEn { setlim(ncs, RLIMIT_CORE); }
    action LimStackEn { setlim(ncs, RLIMIT_STACK); }
    action LimDataEn { setlim(ncs, RLIMIT_DATA); }
    action LimFsizeEn { setlim(ncs, RLIMIT_FSIZE); }
    action LimCpuEn { setlim(ncs, RLIMIT_CPU); }

    lim_as = 'l_as'i eqsep intrangeval % LimAsEn;
    lim_memlock = 'l_memlock'i eqsep intrangeval % LimMemlockEn;
    lim_nofile = 'l_nofile'i eqsep intrangeval % LimNofileEn;
    lim_nproc = 'l_nproc'i eqsep intrangeval % LimNprocEn;
    lim_rss = 'l_rss'i eqsep intrangeval % LimRssEn;
    lim_core = 'l_core'i eqsep intrangeval % LimCoreEn;
    lim_stack = 'l_stack'i eqsep intrangeval % LimStackEn;
    lim_data = 'l_data'i eqsep intrangeval % LimDataEn;
    lim_fsize = 'l_fsize'i eqsep intrangeval % LimFsizeEn;
    lim_cpu = 'l_cpu'i eqsep intrangeval % LimCpuEn;

    action IntervalEn { ncs->ce->interval = ncs->v_time; }

    interval = 'interval'i eqsep timeval % IntervalEn;

    action MonthEn { addipairlist(ncs, &ncs->ce->month, 0, 1, 12); }
    action DayEn { addipairlist(ncs, &ncs->ce->day, 0, 1, 31); }
    action WeekdayEn { addipairlist(ncs, &ncs->ce->weekday, 0, 1, 7); }
    action HourEn { addipairlist(ncs, &ncs->ce->hour, 24, 0, 23); }
    action MinuteEn { addipairlist(ncs, &ncs->ce->minute, 60, 0, 59); }

    month = 'month'i eqsep intrangeval % MonthEn;
    day = 'day'i eqsep intrangeval % DayEn;
    weekday = 'weekday'i eqsep intrangeval % WeekdayEn;
    hour = 'hour'i eqsep intrangeval % HourEn;
    minute = 'minute'i eqsep intrangeval % MinuteEn;

    action GroupEn { setgroupv(ncs); }
    action UserEn { setuserv(ncs); }
    action ChrootEn {
        parse_assign_str(&ncs->ce->chroot, ncs->v_str, ncs->v_strlen,
                         ncs->linenum);
    }
    action CommandEn { parse_command_key(ncs); }

    group = 'group'i eqsep stringval % GroupEn;
    user = 'user'i eqsep stringval % UserEn;
    chroot = 'chroot'i eqsep stringval % ChrootEn;
    command = 'command'i eqsep stringval % CommandEn;

    cmds = command | chroot | user | group | minute | hour | weekday | day |
           month | interval | lim_cpu | lim_fsize | lim_data | lim_stack |
           lim_core | lim_rss | lim_nproc | lim_nofile | lim_memlock | lim_as |
           maxruns | runat | noexectime | journal;

    action JobIdSt { ncs->jobid_st = p; }
    action JobIdEn { ncs->v_jobid = atoi(ncs->jobid_st); }
    action CreateCe { finish_ce(ncs); create_ce(ncs); }

    jobid = ('!' > CreateCe) (digit+ > JobIdSt) cmdterm % JobIdEn;

    main := jobid | cmds;
}%%

%% write data;

static int do_parse_config(struct ParseCfgState *ncs, char *data, size_t len)
{
    char *p = data;
    const char *pe = data + len;
    const char *eof = pe;

    %% write init;
    %% write exec;

    if (ncs->cs == ncrontab_error)
        return -1;
    if (ncs->cs >= ncrontab_first_final)
        return 1;
    return 0;
}

void parse_config(char *path, char *execfile, cronentry_t **stk,
                  cronentry_t **deadstk)
{
    char buf[MAXLINE];
    struct ParseCfgState ncs;
    memset(&ncs, 0, sizeof ncs);
    ncs.execfile = execfile;
    ncs.stack = *stk;
    ncs.deadstack = *deadstk;

    FILE *f = fopen(path, "r");
    if (!f)
        suicide("%s: fopen(%s) failed: %s", __func__, path, strerror(errno));

    while (++ncs.linenum, fgets(buf, sizeof buf, f)) {
        int r = do_parse_config(&ncs, buf, strlen(buf));
        if (r < 0)
            suicide("%s: do_parse_config(%s) failed", __func__, path);
        if (r == 1)
            break;
    }
    if (fclose(f))
        suicide("%s: fclose(%s) failed: %s", __func__, path, strerror(errno));

    *stk = ncs.stack;
    *deadstk = ncs.deadstack;
    if (ncs.ce)
        free_cronentry(&ncs.ce); // Free partially built unused item.
    cfg_reload = 1;
}

/* config.rl - configure file parser for ncron
 *
 * (c) 2003-2014 Nicholas J. Kain <njkain at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <limits.h>
#include "nk/log.h"
#include "nk/malloc.h"
#include "nk/privilege.h"

#include "defines.h"
#include "config.h"
#include "sched.h"

/* BSD uses OFILE rather than NOFILE... */
#ifndef RLIMIT_NOFILE
#  define RLIMIT_NOFILE RLIMIT_OFILE
#endif

static int cfg_reload;    /* 0 on first call, 1 on subsequent calls */

struct ParseCfgState
{
    char v_str[1024];

    cronentry_t *stack;
    cronentry_t *deadstack;
    cronentry_t *ce;

    char *execfile;

    char *jobid_st;
    char *time_st;
    char *intv_st;
    char *intv2_st;
    char *strv_st;

    size_t v_strlen;

    unsigned int v_jobid;
    unsigned int v_time;

    int v_int;
    int v_int2;

    int cs;
    int noextime;
    int cmdret;

    bool intv2_exist;
};

static void nullify_item(cronentry_t *item)
{
    if (!item)
        return;
    item->id = 0;
    item->command = NULL;
    item->args = NULL;
    item->chroot = NULL;
    item->numruns = 0;
    item->maxruns = 0;
    item->journal = 0;
    item->user = 0;
    item->group = 0;
    item->month = NULL;
    item->day = NULL;
    item->weekday = NULL;
    item->hour = NULL;
    item->minute = NULL;
    item->interval = 0;
    item->exectime = 0;
    item->lasttime = 0;
    item->limits = NULL;
    item->next = NULL;
}

static void nullify_limits(limit_t *l)
{
    if (!l)
        return;
    l->cpu = NULL;
    l->fsize = NULL;
    l->data = NULL;
    l->stack = NULL;
    l->core = NULL;
    l->rss = NULL;
    l->nproc = NULL;
    l->nofile = NULL;
    l->memlock = NULL;
    l->as = NULL;
}

struct hstm {
    char *st;
    int cs;
    int id;
    time_t exectime;
    time_t lasttime;
    unsigned int numruns;
    bool got_exectime:1;
    bool got_lasttime:1;
    bool got_numruns:1;
};

%%{
    machine history_m;
    access hstm->;

    action St { hstm->st = p; }
    action LastTimeEn {
        hstm->lasttime = atoi(p);
        hstm->got_lasttime = 1;
    }
    action NumRunsEn {
        hstm->numruns = atoi(p);
        hstm->got_numruns = 1;
    }
    action ExecTimeEn {
        hstm->exectime = atoi(p);
        hstm->got_exectime = 1;
    }
    action IdEn { hstm->id = atoi(p); }

    lasttime = '|' digit+ > St % LastTimeEn;
    numruns = ':' digit+ > St % NumRunsEn;
    exectime = '=' digit+ > St % ExecTimeEn;
    id = digit+ > St % IdEn;
    main := id (numruns | exectime | lasttime)+;
}%%

%% write data;

static int do_get_history(struct hstm *hstm, char *buf, size_t blen)
{
    char *p = buf;
    const char *pe = buf + blen;
    const char *eof = pe;

    %% write exec;

    if (hstm->cs >= history_m_first_final)
        return 1;
    if (hstm->cs == history_m_error)
        return -1;
    return -2;
}

static void get_history(cronentry_t *item, char *path, int noextime)
{
    struct hstm hstm = {0};

    assert(item);

    if (!noextime && !hstm.got_exectime)
        item->exectime = 0;

    FILE *f = fopen(path, "r");

    if (!f) {
        log_line("failed to open history file \"%s\" for read", path);
        if (!noextime)
            item->exectime = (time_t)0; /* gracefully fail */
        return;
    }

    char buf[MAXLINE];
    memset(buf, 0, sizeof buf);

    while (!feof(f)) {
        char *s = fgets(buf, (int)(sizeof buf), f);
        if (!s)
            break;
        int r = do_get_history(&hstm, buf, strlen(buf));
        if (r < 0)
            suicide("%s: do_get_history(%s) failed", __func__, path);
        if (r == 1) {
            if (hstm.id == item->id) {
                if (hstm.got_lasttime)
                    item->lasttime = hstm.lasttime;
                if (hstm.got_numruns)
                    item->numruns = hstm.numruns;
                if (hstm.got_exectime && !noextime)
                    item->exectime = hstm.exectime;
                break;
            }
        }
    }
    if (fclose(f))
        suicide("%s: fclose(%s) failed: %s", __func__, path, strerror(errno));
}

static void setlim(struct ParseCfgState *ncs, int type)
{
    struct rlimit **p = NULL;
    if (!ncs->ce->limits) {
        ncs->ce->limits = xmalloc(sizeof(limit_t));
        nullify_limits(ncs->ce->limits);
    }
    switch (type) {
    case RLIMIT_CPU: *p = ncs->ce->limits->cpu; break;
    case RLIMIT_FSIZE: *p = ncs->ce->limits->fsize; break;
    case RLIMIT_DATA: *p = ncs->ce->limits->data; break;
    case RLIMIT_STACK: *p = ncs->ce->limits->stack; break;
    case RLIMIT_CORE: *p = ncs->ce->limits->core; break;
    case RLIMIT_RSS: *p = ncs->ce->limits->rss; break;
    case RLIMIT_NPROC: *p = ncs->ce->limits->nproc; break;
    case RLIMIT_NOFILE: *p = ncs->ce->limits->nofile; break;
    case RLIMIT_MEMLOCK: *p = ncs->ce->limits->memlock; break;
#ifndef BSD
    case RLIMIT_AS: *p = ncs->ce->limits->as; break;
#endif /* BSD */
    default: return;
    }
    if (!p)
        suicide("%s: unexpected NULL, corruption?", __func__);
    if (!*p)
        *p = xmalloc(sizeof(struct rlimit));
    if (ncs->v_int == 0) (*p)->rlim_cur = RLIM_INFINITY;
    else (*p)->rlim_cur = ncs->v_int;

    if (ncs->v_int2 == 0) (*p)->rlim_max = RLIM_INFINITY;
    else (*p)->rlim_cur = ncs->v_int2;
}

static void addipairlist(struct ParseCfgState *ncs,
                         ipair_node_t **list, int wildcard, int min, int max)
{
    ipair_node_t *l;
    int low, high = wildcard;

    low = ncs->v_int;
    if (ncs->intv2_exist)
        high = ncs->v_int2;

    if (high == wildcard)
        high = low;
    if (low > max || low < min)
        low = wildcard;
    if (high > max || high < min)
        high = wildcard;

    /* we don't allow meaningless 'rules' */
    if (low == wildcard && high == wildcard)
        return;

    if (*list == NULL) {
        *list = xmalloc(sizeof(ipair_node_t));
        l = *list;
    } else {
        l = *list;
        while (l->next)
            l = l->next;

        l->next = xmalloc(sizeof(ipair_node_t));
        l = l->next;
    }

    /* discontinuous range, split into two continuous rules... */
    if (low > high) {
        l->node.l = low;
        l->node.h = max;
        l->next = xmalloc(sizeof(ipair_node_t));
        l = l->next;
        l->node.l = min;
        l->node.h = high;
        l->next = NULL;
    } else {
        /* handle continuous ranges normally */
        l->node.l = low;
        l->node.h = high;
        l->next = NULL;
    }
}

static void setgroupv(struct ParseCfgState *ncs)
{
    if (nk_gidbyname(ncs->v_str, &ncs->ce->group))
        suicide("%s: nonexistent group specified at line %zu", ncs->linenum);
}

static void setuserv(struct ParseCfgState *ncs)
{
    if (nk_uidgidbyname(ncs->v_str, &ncs->ce->user, &ncs->ce->group))
        suicide("%s: nonexistent user specified at line %zu", ncs->linenum);
}

static void parse_assign_str(char **dest, char *src, size_t srclen)
{
    if (srclen > INT_MAX)
        suicide("%s: srclen would overflow int", __func__);
    free(*dest);
    *dest = xmalloc(srclen+1);
    ssize_t l = snprintf(*dest, srclen+1, "%.*s", (int)srclen, src);
    if (l < 0 || (size_t)l >= srclen+1)
        suicide("%s: snprintf failed l=%u srclen+1=%u", __func__, l, srclen+1);
}

struct pckm {
    char *st;
    int cs;
};

%%{
    machine parse_cmd_key_m;
    access pckm.;

    action St { pckm.st = p; }
    action CmdEn {
        size_t cmdlen = p - pckm.st;
        parse_assign_str(&ncs->ce->command, pckm.st, cmdlen);
        // Unescape "\\" and "\ ".
        int prevsl = 0;
        size_t i = 0;
        while (ncs->ce->command[i]) {
            if (prevsl && (ncs->ce->command[i] == '\\'
                           || ncs->ce->command[i] == ' ')) {
                memmove(ncs->ce->command + i - 1,
                        ncs->ce->command + i, cmdlen--);
                continue;
            }
            if (ncs->ce->command[i] == '\\')
                prevsl = 1;
            ++i;
        }
    }
    action ArgEn {
        parse_assign_str(&ncs->ce->args, pckm.st, p - pckm.st);
    }

    sptab = [ \t];
    cmd = sptab+ ([^\0 \\] | '\\\\' | '\\ ')+ > St % CmdEn;
    args = sptab+ ([^\0])* > St % ArgEn;
    main := cmd args;
}%%

%% write data;

// cmdret = 0: Not parsed a command key yet.
// cmdret = 1: Success.  Got a command key.
// cmdret = -1: Error: malformed command key.
// cmdret = -2: Error: incomplete command key.
// cmdret = -3: Error: duplicate command key.
static void parse_command_key(struct ParseCfgState *ncs)
{
    char *p = ncs->v_str;
    const char *pe = ncs->v_str + ncs->v_strlen;
    const char *eof = pe;

    struct pckm pckm = {0};

    if (ncs->cmdret != 0) {
        ncs->cmdret = -3;
        return;
    }

    %% write exec;

    if (pckm.cs == parse_cmd_key_m_error)
        ncs->cmdret = -1;
    else if (pckm.cs >= parse_cmd_key_m_first_final)
        ncs->cmdret = 1;
    else
        ncs->cmdret = -2;
}

static void create_ce(struct ParseCfgState *ncs)
{
    assert(!ncs->ce);
    ncs->ce = xmalloc(sizeof(cronentry_t));
    nullify_item(ncs->ce);
    ncs->cmdret = 0;
    ncs->noextime = 0;
}

static void finish_ce(struct ParseCfgState *ncs)
{
    assert(ncs->ce);
    if (ncs->ce->id <= 0
        || (ncs->ce->interval <= 0 && ncs->ce->exectime <= 0)
        || !ncs->ce->command || ncs->cmdret < 1) {
        free_cronentry(&ncs->ce);
        return;
    }

    /* we have a job to insert */
    if (ncs->ce->exectime != 0) { /* runat task */
        get_history(ncs->ce, ncs->execfile, 1);

        /* insert iif we haven't exceeded maxruns */
        if (ncs->ce->maxruns == 0 || ncs->ce->numruns < ncs->ce->maxruns)
            stack_insert(ncs->ce, &ncs->stack);
        else
            stack_insert(ncs->ce, &ncs->deadstack);
    } else { /* interval task */
        get_history(ncs->ce, ncs->execfile, ncs->noextime && !cfg_reload);

        /* compensate for user edits to job constraints */
        time_t ttm = get_first_time(ncs->ce);
        if (ttm - ncs->ce->lasttime >= ncs->ce->interval)
            ncs->ce->exectime = ttm;
        else
            force_to_constraint(ncs->ce);

        /* insert iif numruns < maxruns and no constr error */
        if ((ncs->ce->maxruns == 0 || ncs->ce->numruns < ncs->ce->maxruns)
            && ncs->ce->exectime != 0)
            stack_insert(ncs->ce, &ncs->stack);
        else
            stack_insert(ncs->ce, &ncs->deadstack);
    }
    ncs->ce = NULL;
}

%%{
    machine ncrontab;
    access ncs->;

    eqsep = [ \t]+ '=' [ \t]+;
    cmdterm = '\0';

    action TUnitSt { ncs->time_st = p; ncs->v_time = 0; }
    action TSecEn  { ncs->v_time +=          atoi(ncs->time_st); }
    action TMinEn  { ncs->v_time += 60     * atoi(ncs->time_st); }
    action THrEn   { ncs->v_time += 3600   * atoi(ncs->time_st); }
    action TDayEn  { ncs->v_time += 86400  * atoi(ncs->time_st); }
    action TWeekEn { ncs->v_time += 604800 * atoi(ncs->time_st); }

    action IntValSt {
        ncs->intv_st = p;
        ncs->v_int = ncs->v_int2 = 0;
        ncs->intv2_exist = false;
    }
    action IntValEn { ncs->v_int = atoi(ncs->intv_st); }
    action IntVal2St { ncs->intv2_st = p; }
    action IntVal2En {
        ncs->v_int2 = atoi(ncs->intv2_st);
        ncs->intv2_exist = true;
    }

    action StrValSt { ncs->strv_st = p; ncs->v_strlen = 0; }
    action StrValEn {
        ncs->v_strlen = p - ncs->strv_st;
        if (ncs->v_strlen <= INT_MAX) {
            ssize_t snl = snprintf(ncs->v_str, sizeof ncs->v_str,
                                   "%.*s", (int)ncs->v_strlen, ncs->strv_st);
            if (snl < 0 || (size_t)snl >= sizeof ncs->v_str) {
                // XXX: Error/truncated.
            }
        }
    }

    t_sec  = (digit+ > TUnitSt) 's' % TSecEn;
    t_min  = (digit+ > TUnitSt) 'm' % TMinEn;
    t_hr   = (digit+ > TUnitSt) 'h' % THrEn;
    t_day  = (digit+ > TUnitSt) 'd' % TDayEn;
    t_week = (digit+ > TUnitSt) 'w' % TWeekEn;
    t_any = (t_sec | t_min | t_hr | t_day | t_week);

    intval = (digit+ > IntValSt % IntValEn) cmdterm;
    timeval = t_any ([ \t]* t_any)* cmdterm;
    intrangeval = (digit+ > IntValSt % IntValEn)
                  (',' (digit+ > IntVal2St % IntVal2En))? cmdterm;
    stringval = ([^\0]+ > StrValSt % StrValEn) cmdterm;

    action JournalEn { ncs->ce->journal = 1; }
    action NoExecTimeEn { ncs->noextime = 1; }

    journal = 'journal'i cmdterm % JournalEn;
    noexectime = 'noexectime'i cmdterm % NoExecTimeEn;

    action RunAtEn {
        ncs->ce->exectime = ncs->v_int;
        ncs->ce->maxruns = 1;
        ncs->ce->journal = 1;
    }
    action MaxRunsEn {
        if (ncs->ce->exectime == 0)
            ncs->ce->maxruns = ncs->v_int;
    }

    runat = 'runat'i eqsep intval % RunAtEn;
    maxruns = 'maxruns'i eqsep intval % MaxRunsEn;

    action LimAsEn { setlim(ncs, RLIMIT_AS); }
    action LimMemlockEn { setlim(ncs, RLIMIT_MEMLOCK); }
    action LimNofileEn { setlim(ncs, RLIMIT_NOFILE); }
    action LimNprocEn { setlim(ncs, RLIMIT_NPROC); }
    action LimRssEn { setlim(ncs, RLIMIT_RSS); }
    action LimCoreEn { setlim(ncs, RLIMIT_CORE); }
    action LimStackEn { setlim(ncs, RLIMIT_STACK); }
    action LimDataEn { setlim(ncs, RLIMIT_DATA); }
    action LimFsizeEn { setlim(ncs, RLIMIT_FSIZE); }
    action LimCpuEn { setlim(ncs, RLIMIT_CPU); }

    lim_as = 'l_as'i eqsep intrangeval % LimAsEn;
    lim_memlock = 'l_memlock'i eqsep intrangeval % LimMemlockEn;
    lim_nofile = 'l_nofile'i eqsep intrangeval % LimNofileEn;
    lim_nproc = 'l_nproc'i eqsep intrangeval % LimNprocEn;
    lim_rss = 'l_rss'i eqsep intrangeval % LimRssEn;
    lim_core = 'l_core'i eqsep intrangeval % LimCoreEn;
    lim_stack = 'l_stack'i eqsep intrangeval % LimStackEn;
    lim_data = 'l_data'i eqsep intrangeval % LimDataEn;
    lim_fsize = 'l_fsize'i eqsep intrangeval % LimFsizeEn;
    lim_cpu = 'l_cpu'i eqsep intrangeval % LimCpuEn;

    action IntervalEn { ncs->ce->interval = ncs->v_time; }

    interval = 'interval'i eqsep timeval % IntervalEn;

    action MonthEn { addipairlist(ncs, &ncs->ce->month, 0, 1, 12); }
    action DayEn { addipairlist(ncs, &ncs->ce->day, 0, 1, 31); }
    action WeekdayEn { addipairlist(ncs, &ncs->ce->weekday, 0, 1, 7); }
    action HourEn { addipairlist(ncs, &ncs->ce->hour, 24, 0, 23); }
    action MinuteEn { addipairlist(ncs, &ncs->ce->minute, 60, 0, 59); }

    month = 'month'i eqsep intrangeval % MonthEn;
    day = 'day'i eqsep intrangeval % DayEn;
    weekday = 'weekday'i eqsep intrangeval % WeekdayEn;
    hour = 'hour'i eqsep intrangeval % HourEn;
    minute = 'minute'i eqsep intrangeval % MinuteEn;

    action GroupEn { setgroupv(ncs); }
    action UserEn { setuserv(ncs); }
    action ChrootEn { parse_assign_str(&ncs->ce->chroot,
                                       ncs->v_str, ncs->v_strlen); }
    action CommandEn { parse_command_key(ncs); }

    group = 'group'i eqsep stringval % GroupEn;
    user = 'user'i eqsep stringval % UserEn;
    chroot = 'chroot'i eqsep stringval % ChrootEn;
    command = 'command'i eqsep stringval % CommandEn;

    cmds = command | chroot | user | group | minute | hour | weekday | day |
           month | interval | lim_cpu | lim_fsize | lim_data | lim_stack |
           lim_core | lim_rss | lim_nproc | lim_nofile | lim_memlock | lim_as |
           maxruns | runat | noexectime | journal;

    action JobIdSt { ncs->jobid_st = p; }
    action JobIdEn { ncs->v_jobid = atoi(ncs->jobid_st); }
    action CreateCe { create_ce(ncs); }

    jobid = ('!' > CreateCe) (digit+ > JobIdSt) cmdterm % JobIdEn;

    action FinishCe { finish_ce(ncs); }

    job = jobid cmds+ % FinishCe;
    main := job+;
}%%

%% write data;

static int do_parse_config(struct ParseCfgState *ncs,
                           char *data, size_t len, bool is_eof)
{
    char *p = data;
    const char *pe = data + len;
    const char *eof = is_eof ? pe : 0;

    %% write exec;

    if (ncs->cs == ncrontab_error)
        return -1;
    if (ncs->cs >= ncrontab_first_final)
        return 1;
    return 0;
}

void parse_config(char *path, char *execfile, cronentry_t **stk,
                  cronentry_t **deadstk)
{
    char buf[MAXLINE];
    struct ParseCfgState ncs;
    memset(buf, 0, sizeof buf);
    memset(&ncs, 0, sizeof ncs);
    ncs.execfile = execfile;
    ncs.stack = *stk;
    ncs.deadstack = *deadstk;

    FILE *f = fopen(path, "r");
    if (!f)
        suicide("%s: fopen(%s) failed: %s", __func__, path, strerror(errno));

    while (!feof(f)) {
        char *s = fgets(buf, (int)(sizeof buf), f);
        int r = do_parse_config(&ncs, buf, strlen(buf), s == NULL);
        if (r < 0)
            suicide("%s: do_parse_config(%s) failed", __func__, path);
        if (r == 1)
            break;
    }
    if (fclose(f))
        suicide("%s: fclose(%s) failed: %s", __func__, path, strerror(errno));

    *stk = ncs.stack;
    *deadstk = ncs.deadstack;
    if (ncs.ce)
        free_cronentry(&ncs.ce); // Free partially built unused item.
    cfg_reload = 1;
}


Added send token.  Got rid of util.h include

#include <stdio.h>
#include <stdlib.h>

#include "kegogi_parser.h"

#include <bstring.h>
#include "kegogi.h"
#include "httpclient.h"

%%{
    machine kegogi;
    action command {
        if(idx < max_commands && uri != NULL && method != NULL) {
            host = host ? host : bstrcpy(default_host);
	    int numPort = port ? atoi(bdata(port)) : default_port;
	    if(port) bdestroy(port);
	    Request *req = Request_create(host, numPort, method, uri);
	    commands[idx++].request = req;
        }
	else
	    printf("error\n");
    }

    action clear {
        method = NULL;
 	port = NULL;
	host = NULL;
	uri = NULL;
    }

    action mark {
        mark = fpc;
    }

    action method {
        method = blk2bstr(mark, fpc - mark);
    }

    action host {
        host = blk2bstr(mark, fpc - mark);
    }

    action port {
        port = blk2bstr(mark, fpc - mark);
    }

    action uri {
        uri = blk2bstr(mark, fpc - mark);
    }

    action status_code {
        status_code = atoin(mark, fpc - mark);
    }

    action param_name {
        param_name = blk2bstr(mark, fpc - mark);
    }

    action dictionary {

    }

    action pattern {
        pattern = blk2bstr(mark, fpc - mark);
    }

    ws = (space - '\n')+;

    method = (upper | digit){1,20} >mark %method;

    protocol = alpha+ '://';
    host = ([^:/]+) >mark %host;
    port = ':' (digit+) >mark %port;
    uri = ('/' (^space)*) >mark %uri;

    url = ((protocol? host port?)? uri);

    comment = '#' [^\n]*;

    command = (ws? "send" ws+ method ws+ url ws? comment? '\n') >clear @command;
    empty_line = ws? comment? '\n';

    main := (command | empty_line)*;
}%%

%% write data;

int parse_kegogi_file(const char *path, Command commands[], int max_commands) {
    FILE *f = fopen(path, "r");
    fseek(f, 0L, SEEK_END);
    int size = ftell(f);
    fseek(f, 0L, SEEK_SET);
    char *buffer = malloc(size);
    fread(buffer, 1, size, f);

    int idx = 0;

    bstring default_host = bfromcstr("localhost");
    int default_port = 80;

    bstring host, port, uri, method;
    char *mark, *p = buffer, *pe = buffer + size, *eof = pe;
    int cs;

    %% write init;
    %% write exec;

    return idx;
}

#include <stdio.h>
#include <stdlib.h>

#include "kegogi_parser.h"

#include <bstring.h>
#include "kegogi.h"
#include "httpclient.h"
#include "util.h"

%%{
    machine kegogi;
    action command {
        if(idx < max_commands && uri != NULL && method != NULL) {
            host = host ? host : bstrcpy(default_host);
	    int numPort = port ? atoi(bdata(port)) : default_port;
	    if(port) bdestroy(port);
	    Request *req = Request_create(host, numPort, method, uri);
	    commands[idx++].request = req;
        }
	else
	    printf("error\n");
    }

    action clear {
        method = NULL;
 	port = NULL;
	host = NULL;
	uri = NULL;
    }

    action mark {
        mark = fpc;
    }

    action method {
        method = blk2bstr(mark, fpc - mark);
    }

    action host {
        host = blk2bstr(mark, fpc - mark);
    }

    action port {
        port = blk2bstr(mark, fpc - mark);
    }

    action uri {
        uri = blk2bstr(mark, fpc - mark);
    }

    action status_code {
        status_code = atoin(mark, fpc - mark);
    }

    action param_name {
        param_name = blk2bstr(mark, fpc - mark);
    }

    action dictionary {

    }

    action pattern {
        pattern = blk2bstr(mark, fpc - mark);
    }

    ws = (space - '\n')+;

    method = (upper | digit){1,20} >mark %method;

    protocol = alpha+ '://';
    host = ([^:/]+) >mark %host;
    port = ':' (digit+) >mark %port;
    uri = ('/' (^space)*) >mark %uri;

    url = ((protocol? host port?)? uri);

    comment = '#' [^\n]*;

    command = (ws? method ws url ws? comment? '\n') >clear @command;
    empty_line = ws? comment? '\n';

    main := (command | empty_line)*;
}%%

%% write data;

int parse_kegogi_file(const char *path, Command commands[], int max_commands) {
    FILE *f = fopen(path, "r");
    fseek(f, 0L, SEEK_END);
    int size = ftell(f);
    fseek(f, 0L, SEEK_SET);
    char *buffer = malloc(size);
    fread(buffer, 1, size, f);

    int idx = 0;

    bstring default_host = bfromcstr("localhost");
    int default_port = 80;

    bstring host, port, uri, method;
    char *mark, *p = buffer, *pe = buffer + size, *eof = pe;
    int cs;

    %% write init;
    %% write exec;

    return idx;
}


Fix uninitialized color value.

Fix uninitialized color value.

#include "parser/TextFile.h"
#include "Color.h"
#include <string.h>
#include <stdlib.h>
#include <cstddef>
#include <functional>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
namespace text_file_parser
{
struct FSM
{
	public:
		int cs;
		char separator;
		int act;
		int top;
		char *ts;
		char *te;
		int stack[256];
		char buffer[8 * 1024];
		int line;
		int column;
		int line_start;
		int buffer_offset;
		int64_t number_i64;
		vector<int64_t> numbers_i64;
		char *number_double_start;
		vector<double> numbers_double;
		function<void(const Color&)> addColor;
		void handleNewline()
		{
			line++;
			column = 0;
			line_start = te - buffer;
		}
		int hexToInt(char hex)
		{
			if (hex >= '0' && hex <= '9') return hex - '0';
			if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10;
			if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10;
			return 0;
		}
		int hexPairToInt(const char *hex_pair)
		{
			return hexToInt(hex_pair[0]) << 4 | hexToInt(hex_pair[1]);
		}
		void colorHexFull(bool with_hash_symbol)
		{
			Color color;
			int start_index = with_hash_symbol ? 1 : 0;
			color.rgb.red = hexPairToInt(ts + start_index) / 255.0;
			color.rgb.green = hexPairToInt(ts + start_index + 2) / 255.0;
			color.rgb.blue = hexPairToInt(ts + start_index + 4) / 255.0;
			color.ma[3] = 0;
			addColor(color);
		}
		void colorHexShort(bool with_hash_symbol)
		{
			Color color;
			int start_index = with_hash_symbol ? 1 : 0;
			color.rgb.red = hexToInt(ts[start_index + 0]) / 15.0;
			color.rgb.green = hexToInt(ts[start_index + 1]) / 15.0;
			color.rgb.blue = hexToInt(ts[start_index + 2]) / 15.0;
			color.ma[3] = 0;
			addColor(color);
		}
		void colorRgb()
		{
			Color color;
			color.rgb.red = numbers_i64[0] / 255.0;
			color.rgb.green = numbers_i64[1] / 255.0;
			color.rgb.blue = numbers_i64[2] / 255.0;
			color.ma[3] = 0;
			numbers_i64.clear();
			addColor(color);
		}
		void colorRgba()
		{
			Color color;
			color.rgb.red = numbers_i64[0] / 255.0;
			color.rgb.green = numbers_i64[1] / 255.0;
			color.rgb.blue = numbers_i64[2] / 255.0;
			color.ma[3] = numbers_double[0];
			numbers_i64.clear();
			numbers_double.clear();
			addColor(color);
		}
		void colorValues()
		{
			Color color;
			color.rgb.red = numbers_double[0];
			color.rgb.green = numbers_double[1];
			color.rgb.blue = numbers_double[2];
			color.ma[3] = 0;
			numbers_double.clear();
			addColor(color);
		}
		void colorValueIntegers()
		{
			Color color;
			color.rgb.red = numbers_i64[0] / 255.0;
			color.rgb.green = numbers_i64[1] / 255.0;
			color.rgb.blue = numbers_i64[2] / 255.0;
			color.ma[3] = 0;
			numbers_i64.clear();
			addColor(color);
		}
		double parseDouble(const char *start, const char *end)
		{
			string v(start, end);
			return stod(v.c_str());
		}
		void clearNumberStacks()
		{
			numbers_i64.clear();
			numbers_double.clear();
		}
};

%%{
	machine text_file;
	access fsm->;
	number_i64 = digit+ >{ fsm->number_i64 = 0; } ${ fsm->number_i64 = fsm->number_i64 * 10 + (*p - '0'); };
	sign = '-' | '+';
	number_double = sign? (([0-9]+ '.' [0-9]+) | ('.' [0-9]+) | ([0-9]+)) ('e'i sign? digit+)?;
	number = number_i64 %{ fsm->numbers_i64.push_back(fsm->number_i64); };
	real_number = number_double >{ fsm->number_double_start = p; } %{ fsm->numbers_double.push_back(fsm->parseDouble(fsm->number_double_start, p)); };

	newline = ('\n' | '\r\n') @{ fsm->handleNewline(); };
	anything = any | newline;
	multi_line_comment := anything* :>> '*/' @{ fgoto main; };
	single_line_comment := (any - newline)* :>> ('\n' | '\r\n') @{ fgoto main; };

	main := |*
		( '#'[0-9a-fA-F]{6} ) { if (configuration.full_hex) fsm->colorHexFull(true); };
		( '#'[0-9a-fA-F]{3} ) { if (configuration.short_hex) fsm->colorHexShort(true); };
		( [0-9a-fA-F]{6} ) { if (configuration.full_hex) fsm->colorHexFull(false); };
		( [0-9a-fA-F]{3} ) { if (configuration.short_hex) fsm->colorHexShort(false); };
		( 'rgb'i '(' space* number space* ',' space* number space* ',' space* number space* ')' ) { if (configuration.css_rgb) fsm->colorRgb(); else fsm->clearNumberStacks(); };
		( 'rgba'i '(' space* number space* ',' space* number space* ',' space* number space* ',' space* real_number space* ')' ) { if (configuration.css_rgba) fsm->colorRgba(); else fsm->clearNumberStacks(); };
		( number space* ',' space* number space* ',' space* number ) { if (configuration.int_values) fsm->colorValueIntegers(); else fsm->clearNumberStacks(); };
		( number space+ number space+ number ) { if (configuration.int_values) fsm->colorValueIntegers(); else fsm->clearNumberStacks(); };
		( real_number space* ',' space* real_number space* ',' space* real_number ) { if (configuration.float_values) fsm->colorValues(); else fsm->clearNumberStacks(); };
		( '//' ) { if (configuration.single_line_c_comments) fgoto single_line_comment; };
		( '/*' ) {  if (configuration.multi_line_c_comments) fgoto multi_line_comment; };
		( '#' ) { if (configuration.single_line_hash_comments) fgoto single_line_comment; };
		( space+ ) { };
		( punct+ ) { };
		( (any - (newline | space | punct | '//' | '/*'))+ ) { };
		( newline ) { };
		*|;
}%%

%% write data;

bool scanner(TextFile &text_file, const Configuration &configuration)
{
	FSM fsm_struct;
	FSM *fsm = &fsm_struct;
	fsm->ts = 0;
	fsm->te = 0;
	fsm->line = 0;
	fsm->line_start = 0;
	fsm->column = 0;
	fsm->buffer_offset = 0;
	bool parse_error = false;
	fsm->addColor = [&text_file](const Color &color){
		Color c = color;
		color_rgb_normalize(&c);
		text_file.addColor(c);
	};
	%% write init;
	int have = 0;
	while (1){
		char *p = fsm->buffer + have;
		int space = sizeof(fsm->buffer) - have;
		if (space == 0){
			text_file.outOfMemory();
			break;
		}
		char *eof = 0;
		auto read_size = text_file.read(fsm->buffer + have, space);
		char *pe = p + read_size;
		if (read_size > 0){
			if (read_size < sizeof(fsm->buffer)) eof = pe;
			%% write exec;
			if (fsm->cs == text_file_error) {
				parse_error = true;
				text_file.syntaxError(fsm->line, fsm->ts - fsm->buffer - fsm->line_start, fsm->line, fsm->te - fsm->buffer - fsm->line_start);
				break;
			}
			if (fsm->ts == 0){
				have = 0;
				fsm->line_start -= sizeof(fsm->buffer);
			}else{
				have = pe - fsm->ts;
				memmove(fsm->buffer, fsm->ts, have);
				int buffer_movement = fsm->ts - fsm->buffer;
				fsm->te -= buffer_movement;
				fsm->line_start -= buffer_movement;
				fsm->ts = fsm->buffer;
				fsm->buffer_offset += fsm->ts - fsm->buffer;
			}
		}else{
			break;
		}
	}
	return parse_error == false;
}

}

#include "parser/TextFile.h"
#include "Color.h"
#include <string.h>
#include <stdlib.h>
#include <cstddef>
#include <functional>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
namespace text_file_parser
{
struct FSM
{
	public:
		int cs;
		char separator;
		int act;
		int top;
		char *ts;
		char *te;
		int stack[256];
		char buffer[8 * 1024];
		int line;
		int column;
		int line_start;
		int buffer_offset;
		int64_t number_i64;
		vector<int64_t> numbers_i64;
		char *number_double_start;
		vector<double> numbers_double;
		function<void(const Color&)> addColor;
		void handleNewline()
		{
			line++;
			column = 0;
			line_start = te - buffer;
		}
		int hexToInt(char hex)
		{
			if (hex >= '0' && hex <= '9') return hex - '0';
			if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10;
			if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10;
			return 0;
		}
		int hexPairToInt(const char *hex_pair)
		{
			return hexToInt(hex_pair[0]) << 4 | hexToInt(hex_pair[1]);
		}
		void colorHexFull(bool with_hash_symbol)
		{
			Color color;
			int start_index = with_hash_symbol ? 1 : 0;
			color.rgb.red = hexPairToInt(ts + start_index) / 255.0;
			color.rgb.green = hexPairToInt(ts + start_index + 2) / 255.0;
			color.rgb.blue = hexPairToInt(ts + start_index + 4) / 255.0;
			addColor(color);
		}
		void colorHexShort(bool with_hash_symbol)
		{
			Color color;
			int start_index = with_hash_symbol ? 1 : 0;
			color.rgb.red = hexToInt(ts[start_index + 0]) / 15.0;
			color.rgb.green = hexToInt(ts[start_index + 1]) / 15.0;
			color.rgb.blue = hexToInt(ts[start_index + 2]) / 15.0;
			color.ma[3] = 0;
			addColor(color);
		}
		void colorRgb()
		{
			Color color;
			color.rgb.red = numbers_i64[0] / 255.0;
			color.rgb.green = numbers_i64[1] / 255.0;
			color.rgb.blue = numbers_i64[2] / 255.0;
			color.ma[3] = 0;
			numbers_i64.clear();
			addColor(color);
		}
		void colorRgba()
		{
			Color color;
			color.rgb.red = numbers_i64[0] / 255.0;
			color.rgb.green = numbers_i64[1] / 255.0;
			color.rgb.blue = numbers_i64[2] / 255.0;
			color.ma[3] = numbers_double[0];
			numbers_i64.clear();
			numbers_double.clear();
			addColor(color);
		}
		void colorValues()
		{
			Color color;
			color.rgb.red = numbers_double[0];
			color.rgb.green = numbers_double[1];
			color.rgb.blue = numbers_double[2];
			color.ma[3] = 0;
			numbers_double.clear();
			addColor(color);
		}
		void colorValueIntegers()
		{
			Color color;
			color.rgb.red = numbers_i64[0] / 255.0;
			color.rgb.green = numbers_i64[1] / 255.0;
			color.rgb.blue = numbers_i64[2] / 255.0;
			color.ma[3] = 0;
			numbers_i64.clear();
			addColor(color);
		}
		double parseDouble(const char *start, const char *end)
		{
			string v(start, end);
			return stod(v.c_str());
		}
		void clearNumberStacks()
		{
			numbers_i64.clear();
			numbers_double.clear();
		}
};

%%{
	machine text_file;
	access fsm->;
	number_i64 = digit+ >{ fsm->number_i64 = 0; } ${ fsm->number_i64 = fsm->number_i64 * 10 + (*p - '0'); };
	sign = '-' | '+';
	number_double = sign? (([0-9]+ '.' [0-9]+) | ('.' [0-9]+) | ([0-9]+)) ('e'i sign? digit+)?;
	number = number_i64 %{ fsm->numbers_i64.push_back(fsm->number_i64); };
	real_number = number_double >{ fsm->number_double_start = p; } %{ fsm->numbers_double.push_back(fsm->parseDouble(fsm->number_double_start, p)); };

	newline = ('\n' | '\r\n') @{ fsm->handleNewline(); };
	anything = any | newline;
	multi_line_comment := anything* :>> '*/' @{ fgoto main; };
	single_line_comment := (any - newline)* :>> ('\n' | '\r\n') @{ fgoto main; };

	main := |*
		( '#'[0-9a-fA-F]{6} ) { if (configuration.full_hex) fsm->colorHexFull(true); };
		( '#'[0-9a-fA-F]{3} ) { if (configuration.short_hex) fsm->colorHexShort(true); };
		( [0-9a-fA-F]{6} ) { if (configuration.full_hex) fsm->colorHexFull(false); };
		( [0-9a-fA-F]{3} ) { if (configuration.short_hex) fsm->colorHexShort(false); };
		( 'rgb'i '(' space* number space* ',' space* number space* ',' space* number space* ')' ) { if (configuration.css_rgb) fsm->colorRgb(); else fsm->clearNumberStacks(); };
		( 'rgba'i '(' space* number space* ',' space* number space* ',' space* number space* ',' space* real_number space* ')' ) { if (configuration.css_rgba) fsm->colorRgba(); else fsm->clearNumberStacks(); };
		( number space* ',' space* number space* ',' space* number ) { if (configuration.int_values) fsm->colorValueIntegers(); else fsm->clearNumberStacks(); };
		( number space+ number space+ number ) { if (configuration.int_values) fsm->colorValueIntegers(); else fsm->clearNumberStacks(); };
		( real_number space* ',' space* real_number space* ',' space* real_number ) { if (configuration.float_values) fsm->colorValues(); else fsm->clearNumberStacks(); };
		( '//' ) { if (configuration.single_line_c_comments) fgoto single_line_comment; };
		( '/*' ) {  if (configuration.multi_line_c_comments) fgoto multi_line_comment; };
		( '#' ) { if (configuration.single_line_hash_comments) fgoto single_line_comment; };
		( space+ ) { };
		( punct+ ) { };
		( (any - (newline | space | punct | '//' | '/*'))+ ) { };
		( newline ) { };
		*|;
}%%

%% write data;

bool scanner(TextFile &text_file, const Configuration &configuration)
{
	FSM fsm_struct;
	FSM *fsm = &fsm_struct;
	fsm->ts = 0;
	fsm->te = 0;
	fsm->line = 0;
	fsm->line_start = 0;
	fsm->column = 0;
	fsm->buffer_offset = 0;
	bool parse_error = false;
	fsm->addColor = [&text_file](const Color &color){
		Color c = color;
		color_rgb_normalize(&c);
		text_file.addColor(c);
	};
	%% write init;
	int have = 0;
	while (1){
		char *p = fsm->buffer + have;
		int space = sizeof(fsm->buffer) - have;
		if (space == 0){
			text_file.outOfMemory();
			break;
		}
		char *eof = 0;
		auto read_size = text_file.read(fsm->buffer + have, space);
		char *pe = p + read_size;
		if (read_size > 0){
			if (read_size < sizeof(fsm->buffer)) eof = pe;
			%% write exec;
			if (fsm->cs == text_file_error) {
				parse_error = true;
				text_file.syntaxError(fsm->line, fsm->ts - fsm->buffer - fsm->line_start, fsm->line, fsm->te - fsm->buffer - fsm->line_start);
				break;
			}
			if (fsm->ts == 0){
				have = 0;
				fsm->line_start -= sizeof(fsm->buffer);
			}else{
				have = pe - fsm->ts;
				memmove(fsm->buffer, fsm->ts, have);
				int buffer_movement = fsm->ts - fsm->buffer;
				fsm->te -= buffer_movement;
				fsm->line_start -= buffer_movement;
				fsm->ts = fsm->buffer;
				fsm->buffer_offset += fsm->ts - fsm->buffer;
			}
		}else{
			break;
		}
	}
	return parse_error == false;
}

}


Docs for the C lexer.

Docs for the C lexer.

#include "lexer.h"

VALUE oga_cLexer;

%%machine lexer;

/**
 * Calls a method defined in the Ruby side of the lexer. The String value is
 * created based on the values of `ts` and `te` and uses the encoding specified
 * in `encoding`.
 *
 * @example
 *  rb_encoding *encoding = rb_enc_get(...);
 *  oga_xml_lexer_callback(self, "on_string", encoding, ts, te);
 */
void oga_xml_lexer_callback(
  VALUE self,
  const char *name,
  rb_encoding *encoding,
  const char *ts,
  const char *te
)
{
  VALUE value  = rb_enc_str_new(ts, te - ts, encoding);
  VALUE method = rb_intern(name);

  rb_funcall(self, method, 1, value);
}

/**
 * Calls a method defined in the Ruby side of the lexer without passing it any
 * arguments.
 *
 * @example
 *  oga_xml_lexer_callback_simple(self, "on_cdata_start");
 */
void oga_xml_lexer_callback_simple(VALUE self, const char *name)
{
  VALUE method = rb_intern(name);

  rb_funcall(self, method, 0);
}

%% write data;

/**
 * Lexes the input String specified in the instance variable `@data`. Lexed
 * values have the same encoding as the input value. This instance variable
 * is set in the Ruby layer of the lexer.
 *
 * The Ragel loop dispatches method calls back to Ruby land to make it easier
 * to implement complex actions without having to fiddle around with C. This
 * introduces a small performance overhead compared to a pure C implementation.
 * However, this is worth the overhead due to it being much easier to maintain.
 */
VALUE oga_xml_lexer_advance(VALUE self)
{
  /* Pull the data in from Ruby land. */
  VALUE data_ivar = rb_ivar_get(self, rb_intern("@data"));

  /* Make sure that all data passed back to Ruby has the proper encoding. */
  rb_encoding *encoding = rb_enc_get(data_ivar);

  char *data_str_val = StringValuePtr(data_ivar);

  const char *p   = data_str_val;
  const char *pe  = data_str_val + strlen(data_str_val);
  const char *eof = pe;
  const char *ts, *te;

  int act = 0;
  int cs  = 0;
  int top = 0;

  /*
  Fixed stack size is enough since the lexer doesn't use that many nested
  fcalls.
  */
  int stack[8];

  %% write init;
  %% write exec;

  return Qnil;
}

%%{
    newline    = '\n' | '\r\n';
    whitespace = [ \t];
    identifier = [a-zA-Z0-9\-_:]+;

    # Strings
    #
    # Strings in HTML can either be single or double quoted. If a string
    # starts with one of these quotes it must be closed with the same type
    # of quote.
    dquote = '"';
    squote = "'";

    # Machine for processing double quoted strings.
    string_dquote := |*
      ^dquote+ => {
        oga_xml_lexer_callback(self, "on_string", encoding, ts, te);
      };

      dquote => { fret; };
    *|;

    # Machine for processing single quoted strings.
    string_squote := |*
      ^squote+ => {
        oga_xml_lexer_callback(self, "on_string", encoding, ts, te);
      };

      squote => { fret; };
    *|;

    # DOCTYPES
    #
    # http://www.w3.org/TR/html-markup/syntax.html#doctype-syntax
    #
    # These rules support the 3 flavours of doctypes:
    #
    # 1. Normal doctypes, as introduced in the HTML5 specification.
    # 2. Deprecated doctypes, the more verbose ones used prior to HTML5.
    # 3. Legacy doctypes
    #
    doctype_start = '<!DOCTYPE'i whitespace+;

    action start_doctype {
      oga_xml_lexer_callback_simple(self, "on_start_doctype");
      fcall doctype;
    }

    # Machine for processing doctypes. Doctype values such as the public
    # and system IDs are treated as T_STRING tokens.
    doctype := |*
      'PUBLIC' | 'SYSTEM' => {
        oga_xml_lexer_callback(self, "on_doctype_type", encoding, ts, te);
      };

      # Lex the public/system IDs as regular strings.
      dquote => { fcall string_dquote; };
      squote => { fcall string_squote; };

      # Whitespace inside doctypes is ignored since there's no point in
      # including it.
      whitespace;

      identifier => {
        oga_xml_lexer_callback(self, "on_doctype_name", encoding, ts, te);
      };

      '>' => {
        oga_xml_lexer_callback_simple(self, "on_doctype_end");
        fret;
      };
    *|;

    # CDATA
    #
    # http://www.w3.org/TR/html-markup/syntax.html#cdata-sections
    #
    # CDATA tags are broken up into 3 parts: the start, the content and the
    # end tag.
    #
    # In HTML CDATA tags have no meaning/are not supported. Oga does
    # support them but treats their contents as plain text.
    #
    cdata_start = '<![CDATA[';
    cdata_end   = ']]>';

    action start_cdata {
      oga_xml_lexer_callback_simple(self, "on_cdata_start");
      fcall cdata;
    }

    # Machine that for processing the contents of CDATA tags. Everything
    # inside a CDATA tag is treated as plain text.
    cdata := |*
      any* cdata_end => {
        oga_xml_lexer_callback(self, "on_text", encoding, ts, te - 3);
        oga_xml_lexer_callback_simple(self, "on_cdata_end");
        fret;
      };
    *|;

    # Comments
    #
    # http://www.w3.org/TR/html-markup/syntax.html#comments
    #
    # Comments are lexed into 3 parts: the start tag, the content and the
    # end tag.
    #
    # Unlike the W3 specification these rules *do* allow character
    # sequences such as `--` and `->`. Putting extra checks in for these
    # sequences would actually make the rules/actions more complex.
    #
    comment_start = '<!--';
    comment_end   = '-->';

    action start_comment {
      oga_xml_lexer_callback_simple(self, "on_comment_start");
      fcall comment;
    }

    # Machine used for processing the contents of a comment. Everything
    # inside a comment is treated as plain text (similar to CDATA tags).
    comment := |*
      any* comment_end => {
        oga_xml_lexer_callback(self, "on_text", encoding, ts, te - 3);
        oga_xml_lexer_callback_simple(self, "on_comment_end");
        fret;
      };
    *|;

    # XML declaration tags
    #
    # http://www.w3.org/TR/REC-xml/#sec-prolog-dtd
    #
    xml_decl_start = '<?xml';
    xml_decl_end   = '?>';

    action start_xml_decl {
      oga_xml_lexer_callback_simple(self, "on_xml_decl_start");
      fcall xml_decl;
    }

    # Machine that processes the contents of an XML declaration tag.
    xml_decl := |*
      xml_decl_end => {
        oga_xml_lexer_callback_simple(self, "on_xml_decl_end");
        fret;
      };

      # Attributes and their values (e.g. version="1.0").
      identifier => {
        oga_xml_lexer_callback(self, "on_attribute", encoding, ts, te);
      };

      dquote => { fcall string_dquote; };
      squote => { fcall string_squote; };

      any;
    *|;

    # Elements
    #
    # http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
    #

    # Action that creates the tokens for the opening tag, name and
    # namespace (if any). Remaining work is delegated to a dedicated
    # machine.
    action start_element {
      oga_xml_lexer_callback(self, "on_element_start", encoding, ts + 1, te);

      fcall element_head;
    }

    element_start = '<' identifier;

    # Machine used for processing the characters inside a element head. An
    # element head is everything between `<NAME` (where NAME is the element
    # name) and `>`.
    #
    # For example, in `<p foo="bar">` the element head is ` foo="bar"`.
    #
    element_head := |*
      whitespace | '=';

      newline => {
        oga_xml_lexer_callback_simple(self, "on_newline");
      };

      # Attribute names.
      identifier => {
        oga_xml_lexer_callback(self, "on_attribute", encoding, ts, te);
      };

      # Attribute values.
      dquote => { fcall string_dquote; };
      squote => { fcall string_squote; };

      # The closing character of the open tag.
      ('>' | '/') => {
        fhold;
        fret;
      };
    *|;

    main := |*
      element_start  => start_element;
      doctype_start  => start_doctype;
      cdata_start    => start_cdata;
      comment_start  => start_comment;
      xml_decl_start => start_xml_decl;

      # Enter the body of the tag. If HTML mode is enabled and the current
      # element is a void element we'll close it and bail out.
      '>' => {
        oga_xml_lexer_callback_simple(self, "on_element_open_end");
      };

      # Regular closing tags.
      '</' identifier '>' => {
        oga_xml_lexer_callback_simple(self, "on_element_end");
      };

      # Self closing elements that are not handled by the HTML mode.
      '/>' => {
        oga_xml_lexer_callback_simple(self, "on_element_end");
      };

      # Note that this rule should be declared at the very bottom as it
      # will otherwise take precedence over the other rules.
      ^('<' | '>')+ => {
        oga_xml_lexer_callback(self, "on_text", encoding, ts, te);
      };
    *|;
}%%

void Init_liboga_xml_lexer()
{
  oga_cLexer = rb_define_class_under(oga_mXML, "Lexer", rb_cObject);

  rb_define_method(oga_cLexer, "advance_native", oga_xml_lexer_advance, 0);
}

#include "lexer.h"

VALUE oga_cLexer;

%%machine lexer;

void oga_xml_lexer_callback(
  VALUE self,
  const char *name,
  rb_encoding *encoding,
  const char *ts,
  const char *te
)
{
  int length   = te - ts;
  VALUE value  = rb_enc_str_new(ts, length, encoding);
  VALUE method = rb_intern(name);

  rb_funcall(self, method, 1, value);
}

void oga_xml_lexer_callback_simple(VALUE self, const char *name)
{
  VALUE method = rb_intern(name);

  rb_funcall(self, method, 0);
}

%% write data;

VALUE oga_xml_lexer_advance(VALUE self)
{
  /* Pull the data in from Ruby land. */
  VALUE data_ivar = rb_ivar_get(self, rb_intern("@data"));

  /* Make sure that all data passed back to Ruby has the proper encoding. */
  rb_encoding *encoding = rb_enc_get(data_ivar);

  char *data_str_val = StringValuePtr(data_ivar);

  const char *p   = data_str_val;
  const char *pe  = data_str_val + strlen(data_str_val);
  const char *eof = pe;
  const char *ts, *te;

  int act = 0;
  int cs  = 0;
  int top = 0;
  int stack[8];

  %% write init;
  %% write exec;

  return Qnil;
}

%%{
    newline    = '\n' | '\r\n';
    whitespace = [ \t];
    identifier = [a-zA-Z0-9\-_:]+;

    # Strings
    #
    # Strings in HTML can either be single or double quoted. If a string
    # starts with one of these quotes it must be closed with the same type
    # of quote.
    dquote = '"';
    squote = "'";

    # Machine for processing double quoted strings.
    string_dquote := |*
      ^dquote+ => {
        oga_xml_lexer_callback(self, "on_string", encoding, ts, te);
      };

      dquote => { fret; };
    *|;

    # Machine for processing single quoted strings.
    string_squote := |*
      ^squote+ => {
        oga_xml_lexer_callback(self, "on_string", encoding, ts, te);
      };

      squote => { fret; };
    *|;

    # DOCTYPES
    #
    # http://www.w3.org/TR/html-markup/syntax.html#doctype-syntax
    #
    # These rules support the 3 flavours of doctypes:
    #
    # 1. Normal doctypes, as introduced in the HTML5 specification.
    # 2. Deprecated doctypes, the more verbose ones used prior to HTML5.
    # 3. Legacy doctypes
    #
    doctype_start = '<!DOCTYPE'i whitespace+;

    action start_doctype {
      oga_xml_lexer_callback_simple(self, "on_start_doctype");
      fcall doctype;
    }

    # Machine for processing doctypes. Doctype values such as the public
    # and system IDs are treated as T_STRING tokens.
    doctype := |*
      'PUBLIC' | 'SYSTEM' => {
        oga_xml_lexer_callback(self, "on_doctype_type", encoding, ts, te);
      };

      # Lex the public/system IDs as regular strings.
      dquote => { fcall string_dquote; };
      squote => { fcall string_squote; };

      # Whitespace inside doctypes is ignored since there's no point in
      # including it.
      whitespace;

      identifier => {
        oga_xml_lexer_callback(self, "on_doctype_name", encoding, ts, te);
      };

      '>' => {
        oga_xml_lexer_callback_simple(self, "on_doctype_end");
        fret;
      };
    *|;

    # CDATA
    #
    # http://www.w3.org/TR/html-markup/syntax.html#cdata-sections
    #
    # CDATA tags are broken up into 3 parts: the start, the content and the
    # end tag.
    #
    # In HTML CDATA tags have no meaning/are not supported. Oga does
    # support them but treats their contents as plain text.
    #
    cdata_start = '<![CDATA[';
    cdata_end   = ']]>';

    action start_cdata {
      oga_xml_lexer_callback_simple(self, "on_cdata_start");
      fcall cdata;
    }

    # Machine that for processing the contents of CDATA tags. Everything
    # inside a CDATA tag is treated as plain text.
    cdata := |*
      any* cdata_end => {
        oga_xml_lexer_callback(self, "on_text", encoding, ts, te - 3);
        oga_xml_lexer_callback_simple(self, "on_cdata_end");
        fret;
      };
    *|;

    # Comments
    #
    # http://www.w3.org/TR/html-markup/syntax.html#comments
    #
    # Comments are lexed into 3 parts: the start tag, the content and the
    # end tag.
    #
    # Unlike the W3 specification these rules *do* allow character
    # sequences such as `--` and `->`. Putting extra checks in for these
    # sequences would actually make the rules/actions more complex.
    #
    comment_start = '<!--';
    comment_end   = '-->';

    action start_comment {
      oga_xml_lexer_callback_simple(self, "on_comment_start");
      fcall comment;
    }

    # Machine used for processing the contents of a comment. Everything
    # inside a comment is treated as plain text (similar to CDATA tags).
    comment := |*
      any* comment_end => {
        oga_xml_lexer_callback(self, "on_text", encoding, ts, te - 3);
        oga_xml_lexer_callback_simple(self, "on_comment_end");
        fret;
      };
    *|;

    # XML declaration tags
    #
    # http://www.w3.org/TR/REC-xml/#sec-prolog-dtd
    #
    xml_decl_start = '<?xml';
    xml_decl_end   = '?>';

    action start_xml_decl {
      oga_xml_lexer_callback_simple(self, "on_xml_decl_start");
      fcall xml_decl;
    }

    # Machine that processes the contents of an XML declaration tag.
    xml_decl := |*
      xml_decl_end => {
        oga_xml_lexer_callback_simple(self, "on_xml_decl_end");
        fret;
      };

      # Attributes and their values (e.g. version="1.0").
      identifier => {
        oga_xml_lexer_callback(self, "on_attribute", encoding, ts, te);
      };

      dquote => { fcall string_dquote; };
      squote => { fcall string_squote; };

      any;
    *|;

    # Elements
    #
    # http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
    #

    # Action that creates the tokens for the opening tag, name and
    # namespace (if any). Remaining work is delegated to a dedicated
    # machine.
    action start_element {
      oga_xml_lexer_callback(self, "on_element_start", encoding, ts + 1, te);

      fcall element_head;
    }

    element_start = '<' identifier;

    # Machine used for processing the characters inside a element head. An
    # element head is everything between `<NAME` (where NAME is the element
    # name) and `>`.
    #
    # For example, in `<p foo="bar">` the element head is ` foo="bar"`.
    #
    element_head := |*
      whitespace | '=';

      newline => {
        oga_xml_lexer_callback_simple(self, "on_newline");
      };

      # Attribute names.
      identifier => {
        oga_xml_lexer_callback(self, "on_attribute", encoding, ts, te);
      };

      # Attribute values.
      dquote => { fcall string_dquote; };
      squote => { fcall string_squote; };

      # The closing character of the open tag.
      ('>' | '/') => {
        fhold;
        fret;
      };
    *|;

    main := |*
      element_start  => start_element;
      doctype_start  => start_doctype;
      cdata_start    => start_cdata;
      comment_start  => start_comment;
      xml_decl_start => start_xml_decl;

      # Enter the body of the tag. If HTML mode is enabled and the current
      # element is a void element we'll close it and bail out.
      '>' => {
        oga_xml_lexer_callback_simple(self, "on_element_open_end");
      };

      # Regular closing tags.
      '</' identifier '>' => {
        oga_xml_lexer_callback_simple(self, "on_element_end");
      };

      # Self closing elements that are not handled by the HTML mode.
      '/>' => {
        oga_xml_lexer_callback_simple(self, "on_element_end");
      };

      # Note that this rule should be declared at the very bottom as it
      # will otherwise take precedence over the other rules.
      ^('<' | '>')+ => {
        oga_xml_lexer_callback(self, "on_text", encoding, ts, te);
      };
    *|;
}%%

void Init_liboga_xml_lexer()
{
  oga_cLexer = rb_define_class_under(oga_mXML, "Lexer", rb_cObject);

  rb_define_method(oga_cLexer, "advance_native", oga_xml_lexer_advance, 0);
}


complete set machines

complete set machines

# begin: ragel
=begin
%%{
  machine bel;

  include 'common.rl';
  include 'identifier.rl';
  include 'string.rl';
  include 'list.rl';

  action add_key {
    $stderr.puts "SET add_key"
    key = @buffers.delete(:ident)
    @buffers[:set_name] = key
  }

  action add_ident_value {
    $stderr.puts "SET add_ident_value"
    ident = @buffers.delete(:ident)
    @buffers[:set_value] = ident
  }

  action add_string_value {
    $stderr.puts "SET add_string_value"
    string = @buffers.delete(:string)
    @buffers[:set_value] = string
  }

  action add_list_value {
    $stderr.puts "SET add_list_value"
    list = @buffers.delete(:list)
    @buffers[:set_value] = list
  }

  action set_end {
    $stderr.puts "SET set_end"
    name = @buffers.delete(:set_name)
    value = @buffers.delete(:set_value)
    set_node = set(name, value, complete: name.complete && value.complete)
    @buffers[:set] = set_node
  }

  action yield_set {
    $stderr.puts "SET yield_set"
    yield @buffers[:set]
  }

  action set_node_eof {
    $stderr.puts "SET set_node_eof"
    name = @buffers.delete(:set_name)
    set_node = set(name)
    completed = name.complete
    if @buffers.key?(:string)
      value = @buffers.delete(:string)
      set_node <<= value
      completed = completed && value.complete
    elsif @buffers.key?(:ident)
      value = @buffers.delete(:ident)
      set_node <<= value
      completed = completed && value.complete
    elsif @buffers.key?(:list)
      value = @buffers.delete(:list)
      set_node <<= value
      completed = completed && value.complete
    end
    set_node.complete = completed
    yield set_node
  }

  SET_KW =
    [sS]
    [eE]
    [tT]
    ;

  key =
    an_ident
    %add_key
    ;

  ident_value =
    an_ident
    %add_ident_value
    ;

  string_value =
    a_string
    %add_string_value
    ;

  list_value =
    a_list
    %add_list_value
    ;

  value =
    ident_value |
    string_value |
    list_value
    ;

  set_node :=
    SET_KW
    SP+
    an_ident
    %add_key
    @eof(set_node_eof)
    SP+
    EQL
    SP+
    value
    @eof(set_node_eof)
    %set_end
    %yield_set
    ;
}%%
=end
# end: ragel

require_relative '../ast/node'
require_relative '../mixin/buffer'
require_relative '../nonblocking_io_wrapper'

module BELParser
  module Parsers
    module BELScript
      module Set

        class << self

          MAX_LENGTH = 1024 * 128 # 128K

          def parse(content)
            return nil unless content

            Parser.new(content).each do |obj|
              yield obj
            end
          end
        end

        private

        class Parser
          include Enumerable
          include BELParser::Parsers::Buffer
          include BELParser::Parsers::AST::Sexp

          def initialize(content)
            @content = content
            $stderr.puts "\n---\ncontent: '" + @content + "'"
      # begin: ragel
            %% write data;
      # end: ragel
          end

          def each
            @buffers    = {}
            @incomplete = {}
            @accum_set  = false
            @have_set   = false
            @have_name  = false
            @have_value = false
            data        = @content.unpack('C*')
            p           = 0
            pe          = data.length
            eof         = data.length

      # begin: ragel
            %% write init;
            %% write exec;
      # end: ragel
          end
        end
      end
    end
  end
end

if __FILE__ == $0
  $stdin.each_line do |line|
    BELParser::Parsers::BELScript::Set.parse(line) { |obj|
      puts obj.inspect
    }
  end
end

# vim: ft=ruby ts=2 sw=2:
# encoding: utf-8

# begin: ragel
=begin
%%{
  machine bel;

  include 'common.rl';
  include 'identifier.rl';
  include 'string.rl';
  include 'list.rl';

  action add_key {
    $stderr.puts "SET add_key"
    key = @buffers.delete(:ident)
    @buffers[:set_name] = key
  }

  action add_ident_value {
    $stderr.puts "SET add_ident_value"
    ident = @buffers.delete(:ident)
    @buffers[:set_value] = ident
  }

  action add_string_value {
    $stderr.puts "SET add_string_value"
    string = @buffers.delete(:string)
    @buffers[:set_value] = string
  }

  action add_list_value {
    $stderr.puts "SET add_list_value"
    list = @buffers.delete(:list)
    @buffers[:set_value] = list
  }

  action set_end {
    $stderr.puts "SET set_end"
    $stderr.puts @buffers.inspect
    name = @buffers.delete(:set_name)
    value = @buffers.delete(:set_value)
    set = set(name, value, complete: name.complete && value.complete)
    @buffers[:set] = set
  }

  action yield_set {
    $stderr.puts "SET yield_set"
    yield @buffers[:set]
  }

  SET_KW =
    [sS]
    [eE]
    [tT]
    ;

  key =
    an_ident
    %add_key
    ;

  ident_value =
    an_ident
    %add_ident_value
    ;

  string_value =
    a_string
    %add_string_value
    ;

  list_value =
    a_list
    %add_list_value
    ;

  value =
    ident_value |
    string_value |
    list_value
    ;

  set_node :=
    SET_KW
    %{ $stderr.puts '% SET_KW' }
    SP+
    an_ident
    %add_key
    SP+
    EQL
    %{ $stderr.puts '% EQL' }
    SP+
    value
    %set_end
    %yield_set
    ;
}%%
=end
# end: ragel

require_relative '../ast/node'
require_relative '../mixin/buffer'
require_relative '../nonblocking_io_wrapper'

module BELParser
  module Parsers
    module BELScript
      module Set

        class << self

          MAX_LENGTH = 1024 * 128 # 128K

          def parse(content)
            return nil unless content

            Parser.new(content).each do |obj|
              yield obj
            end
          end
        end

        private

        class Parser
          include Enumerable
          include BELParser::Parsers::Buffer
          include BELParser::Parsers::AST::Sexp

          def initialize(content)
            @content = content
            $stderr.puts "\n---\ncontent: '" + @content + "'"
      # begin: ragel
            %% write data;
      # end: ragel
          end

          def each
            @buffers    = {}
            @incomplete = {}
            @accum_set  = false
            @have_set   = false
            @have_name  = false
            @have_value = false
            data        = @content.unpack('C*')
            p           = 0
            pe          = data.length
            eof         = data.length

      # begin: ragel
            %% write init;
            %% write exec;
      # end: ragel
          end
        end
      end
    end
  end
end

if __FILE__ == $0
  $stdin.each_line do |line|
    BELParser::Parsers::BELScript::Set.parse(line) { |obj|
      puts obj.inspect
    }
  end
end

# vim: ft=ruby ts=2 sw=2:
# encoding: utf-8


Ragel

Ragel

# Parser for VCF-header info and format fields

=begin
%%{

  machine simple_lexer;

  action mark { ts=p }
  integer     = ('+'|'-')?[0-9]+             >mark %{ emit("integer",data,ts,p)  };
  float       = ('+'|'-')?[0-9]+'.'[0-9]+    >mark %{ emit("float",data,ts,p) };
  assignment  = '=';
  identifier  = [a-zA-Z][a-zA-Z_]+           >mark %{ emit("identifier",data,ts,p) };
  string      = ["][^"]*["]                  >mark %{ emit("string",data,ts,p) };
  boolean     = '.';
  key_word    = ( ('ID'|'Number'|'Type'|'Description') %{ print "*k:" } );
  value       = ( (integer|float|boolean|identifier|string) %{ print "*v:" } );

  key_value = ( key_word '=' value ) ;

  main := ( ('<'|',') key_value )*;
}%%
=end

%% write data;
# %% this just fixes our syntax highlighting...

def emit type, data, ts, p
  # Print the type and text of the last read token
  puts "#{type}: #{data[ts...p].pack('c*')}"
end

def xemit(token_name, data, target_array, ts, te)
  target_array << {:name => token_name.to_sym, :value => data.pack("c*")[ts...te] }
end

def run_lexer(data)
  p data
  data = data.unpack("c*") if(data.is_a?(String))
  eof = data.length
  token_array = []
  stack = []

  %% write init;
  %% write exec;

  p token_array
  token_array.each do | h |
    p h
    # p h[:value] if h[:name]==:identifier or h[:name]==:value or h[:name]==:string
  end
end

# run_lexer("value = -2.00")

lines = <<LINES
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Total read depth">
##FORMAT=<ID=DP4,Number=4,Type=Integer,Description="# high-quality ref-forward bases, ref-reverse, alt-forward and alt-reverse bases">
##INFO=<ID=PM,Number=0,Type=Flag,Description="Variant is Precious(Clinical,Pubmed Cited)">
##INFO=<ID=VP,Number=1,Type=String,Description="Variation Property.  Documentation is at ftp://ftp.ncbi.nlm.nih.gov/snp/specs/dbSNP_BitField_latest.pdf">
##INFO=<ID=GENEINFO,Number=1,Type=String,Description="Pairs each of gene symbol:gene id.  The gene symbol and id are delimited by a colon (:), and each pair is delimited by a vertical bar (|)">
##INFO=<ID=CLNHGVS,Number=.,Type=String,Description="Variant names from HGVS. The order of these variants corresponds to the order of the info in the other clinical  INFO tags.">
LINES

lines.strip.split("\n").each { |s| run_lexer(s.sub(/^##(FORMAT|INFO)=/,'')) }

cmd=ARGV.shift
run_lexer(cmd)

# Parser for VCF-header info and format fields

=begin
%%{

  machine simple_lexer;

  integer     = ('+'|'-')?[0-9]+;
  float       = ('+'|'-')?[0-9]+'.'[0-9]+;
  assignment  = '=';
  identifier  = [a-zA-Z][a-zA-Z_]+;
  string      = ["][^"]*["];
  boolean     = '.';
  key_word    = ( ('ID'|'Number'|'Type'|'Description') %{ puts "**keyword" } );
  value       = ( (integer|float|boolean|identifier|string) %{ puts "***value" } );

  key_value = ( key_word '=' value ) ;

  main := ( ('<'|',') key_value )*;
}%%
=end

%% write data;
# %% this just fixes our syntax highlighting...

def emit(token_name, data, target_array, ts, te)
  target_array << {:name => token_name.to_sym, :value => data.pack("c*")[ts...te] }
end

def run_lexer(data)
  p data
  data = data.unpack("c*") if(data.is_a?(String))
  eof = data.length
  token_array = []
  stack = []

  %% write init;
  %% write exec;

  p token_array
  token_array.each do | h |
    p h
    # p h[:value] if h[:name]==:identifier or h[:name]==:value or h[:name]==:string
  end
end

# run_lexer("value = -2.00")

lines = <<LINES
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Total read depth">
##FORMAT=<ID=DP4,Number=4,Type=Integer,Description="# high-quality ref-forward bases, ref-reverse, alt-forward and alt-reverse bases">
##INFO=<ID=PM,Number=0,Type=Flag,Description="Variant is Precious(Clinical,Pubmed Cited)">
##INFO=<ID=VP,Number=1,Type=String,Description="Variation Property.  Documentation is at ftp://ftp.ncbi.nlm.nih.gov/snp/specs/dbSNP_BitField_latest.pdf">
##INFO=<ID=GENEINFO,Number=1,Type=String,Description="Pairs each of gene symbol:gene id.  The gene symbol and id are delimited by a colon (:), and each pair is delimited by a vertical bar (|)">
##INFO=<ID=CLNHGVS,Number=.,Type=String,Description="Variant names from HGVS. The order of these variants corresponds to the order of the info in the other clinical  INFO tags.">
LINES

lines.strip.split("\n").each { |s| run_lexer(s.sub(/^##(FORMAT|INFO)=/,'')) }

cmd=ARGV.shift
run_lexer(cmd)

  # key := (

    # integer => {
    #   emit(:integer_literal, data, token_array, ts, te)
    # };

    # float => {
    #   emit(:float_literal, data, token_array, ts, te)
    # };

    # assignment => {
    #   emit(:assignment_operator, data, token_array, ts, te)
    # };

    # boolean => {
    #   emit(:boolean, data, token_array, ts, te)
    # };

    # identifier => {
    #   emit(:identifier, data, token_array, ts, te)
    # };

    # string => {
    #   emit(:string, data, token_array, ts, te)
    # };

    # space|',';

  # );


Add binary body parsing to CParser

Add binary body parsing to CParser

#include <ruby.h>

#if DEBUG_H
#  define DEBUG(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); } while(0)
#else
#  define DEBUG(...)
#endif

#define UNUSED(x) (void)(x)
#define MARK_LEN (p - mark)
#define MARK_STR_NEW() rb_external_str_new(mark, MARK_LEN)

#define true 1
#define false 0

typedef struct {
  VALUE error;

  size_t max_message_size;

  const char *chunk;
  const char *p;
  int cs;
  const char *mark;
  VALUE mark_key;
  VALUE mark_message;
  size_t mark_message_size;
  long mark_content_length;
} parser_state_t;

VALUE mStomp = Qnil;
VALUE cMessage = Qnil;
ID g_new;
ID g_write_command;
ID g_write_header;
ID g_write_body;
ID g_content_length;
ID g_build_parse_error;
ID g_max_message_size;

%%{
  machine message;

  action mark {
    mark = p;
  }

  action mark_message {
    mark_message = rb_funcall(cMessage, g_new, 2, Qnil, Qnil);
    mark_message_size = 0;
  }

  action write_command {
    rb_funcall(mark_message, g_write_command, 1, MARK_STR_NEW());
    mark = NULL;
  }

  action mark_key {
    mark_key = MARK_STR_NEW();
    mark = NULL;
  }

  action write_header {
    rb_funcall(mark_message, g_write_header, 2, mark_key, MARK_STR_NEW());
    mark_key = Qnil;
    mark = NULL;
  }

  action finish_headers {
    VALUE length = rb_funcall(mark_message, g_content_length, 0);
    if ( ! NIL_P(length)) {
      mark_content_length = NUM2LONG(length);
    } else {
      mark_content_length = -1;
    }
  }

  action write_body {
    rb_funcall(mark_message, g_write_body, 1, MARK_STR_NEW());
    mark = NULL;
  }

  action consume_null {
    ((mark_content_length != -1) && (MARK_LEN < mark_content_length))
  }

  action consume_octet {
    ((mark_content_length == -1) || (MARK_LEN < mark_content_length))
  }

  action check_message_size {
    mark_message_size += 1;
  }

  action finish_message {
    rb_yield(mark_message);
    mark_message = Qnil;
  }

  include message_common "parser_common.rl";

  write data noprefix;
}%%

static void parser_free(parser_state_t *state) {
  // TODO: free memory inside struct!
  xfree(state);
}

static void parser_mark(parser_state_t *state) {
  rb_gc_mark(state->error);
  rb_gc_mark(state->mark_key);
  rb_gc_mark(state->mark_message);
}

static VALUE parser_alloc(VALUE klass) {
  parser_state_t *state = ALLOC(parser_state_t);
  return Data_Wrap_Struct(klass, parser_mark, parser_free, state);
}

static VALUE parser_initialize(int argc, VALUE *argv, VALUE self) {
  parser_state_t *state;
  Data_Get_Struct(self, parser_state_t, state);

  VALUE max_message_size;
  rb_scan_args(argc, argv, "01", &max_message_size);

  if (max_message_size == Qnil) {
    max_message_size = rb_funcall(mStomp, g_max_message_size, 0);
  }

  state->error = Qnil;
  state->max_message_size = FIX2LONG(max_message_size);
  state->chunk = NULL;
  state->p = NULL;
  state->cs = start;
  state->mark = NULL;
  state->mark_key = Qnil;
  state->mark_message = Qnil;
  state->mark_message_size = 0;
  state->mark_content_length = 0;

  return self;
}

static VALUE parser_parse(VALUE self, VALUE chunk) {
  parser_state_t *state;
  Data_Get_Struct(self, parser_state_t, state);

  if (NIL_P(state->error)) {
    /*
    if state.chunk
      p = state.chunk.bytesize
      chunk = state.chunk << chunk
    else
      p = 0
    end

    pe = chunk.bytesize # special
    */

    // size_t max_message_size = state->max_message_size;
    const char *p = RSTRING_PTR(chunk);
    const char *pe = p + RSTRING_LEN(chunk);

    int cs = state->cs;
    const char *mark = state->mark;
    VALUE mark_key = state->mark_key;
    VALUE mark_message = state->mark_message;
    size_t mark_message_size = state->mark_message_size;
    long mark_content_length = state->mark_content_length;

    %% write exec;

    /*
    if mark
      state.p = chunk.bytesize
      state.chunk = chunk
    else
      state.p = 0
      state.chunk = nil
    end
    */

    state->cs = cs;
    state->mark = mark;
    state->mark_key = mark_key;
    state->mark_message = mark_message;
    state->mark_message_size = mark_message_size;
    state->mark_content_length = mark_content_length;

    if (cs == error) {
      long index = p - RSTRING_PTR(chunk);
      state->error = rb_funcall(mStomp, g_build_parse_error, 2, chunk, LONG2NUM(index));
    }
  }

  if ( ! NIL_P(state->error)) {
    rb_exc_raise(state->error);
  }

  return Qnil;
}

void Init_c_parser(void) {
  VALUE mStompede = rb_const_get(rb_cObject, rb_intern("Stompede"));

  mStomp = rb_const_get(mStompede, rb_intern("Stomp"));
  cMessage = rb_const_get(mStomp, rb_intern("Message"));

  g_new = rb_intern("new");
  g_write_command = rb_intern("write_command");
  g_write_header = rb_intern("write_header");
  g_write_body = rb_intern("write_body");
  g_content_length = rb_intern("content_length");
  g_build_parse_error = rb_intern("build_parse_error");
  g_max_message_size = rb_intern("max_message_size");

  VALUE cParser = rb_define_class_under(mStomp, "CParser", rb_cObject);
  rb_define_alloc_func(cParser, parser_alloc);

  rb_define_method(cParser, "initialize", parser_initialize, -1);
  rb_define_method(cParser, "parse", parser_parse, 1);
}

#include <ruby.h>

#if DEBUG_H
#  define DEBUG(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); } while(0)
#else
#  define DEBUG(...)
#endif

#define UNUSED(x) (void)(x)
#define MARK_LEN (p - mark)
#define MARK_STR_NEW() rb_external_str_new(mark, MARK_LEN)

#define true 1
#define false 0

typedef struct {
  VALUE error;

  size_t max_message_size;

  const char *chunk;
  const char *p;
  int cs;
  const char *mark;
  VALUE mark_key;
  VALUE mark_message;
  size_t mark_message_size;
  size_t mark_message_content_length;
} parser_state_t;

VALUE mStomp = Qnil;
VALUE cMessage = Qnil;
ID g_new;
ID g_write_command;
ID g_write_header;
ID g_write_body;
ID g_build_parse_error;
ID g_max_message_size;

%%{
  machine message;

  action mark {
    mark = p;
  }

  action mark_message {
    mark_message = rb_funcall(cMessage, g_new, 2, Qnil, Qnil);
    mark_message_size = 0;
  }

  action write_command {
    rb_funcall(mark_message, g_write_command, 1, MARK_STR_NEW());
    mark = NULL;
  }

  action mark_key {
    mark_key = MARK_STR_NEW();
    mark = NULL;
  }

  action write_header {
    rb_funcall(mark_message, g_write_header, 2, mark_key, MARK_STR_NEW());
    mark_key = Qnil;
    mark = NULL;
  }

  action finish_headers {
    /*
    VALUE length = rb_funcall(message, rb_intern("content_length"), 0);
    if (FIXNUM_P(length)) {
      content_length = FIX2LONG(length);
    } else {
      content_length = -1;
    }
    */
  }

  action write_body {
    rb_funcall(mark_message, g_write_body, 1, MARK_STR_NEW());
    mark = NULL;
  }

  action consume_null {
    false
    /* ((content_length != -1) && (MARK_LEN < content_length)) */
  }

  action consume_octet {
    true
    /* ((content_length == -1) || (MARK_LEN < content_length)) */
  }

  action check_message_size {
    mark_message_size += 1;
  }

  action finish_message {
    rb_yield(mark_message);
    mark_message = Qnil;
  }

  include message_common "parser_common.rl";

  write data noprefix;
}%%

static void parser_free(parser_state_t *state) {
  // TODO: free memory inside struct!
  xfree(state);
}

static void parser_mark(parser_state_t *state) {
  rb_gc_mark(state->error);
  rb_gc_mark(state->mark_key);
  rb_gc_mark(state->mark_message);
}

static VALUE parser_alloc(VALUE klass) {
  parser_state_t *state = ALLOC(parser_state_t);
  return Data_Wrap_Struct(klass, parser_mark, parser_free, state);
}

static VALUE parser_initialize(int argc, VALUE *argv, VALUE self) {
  parser_state_t *state;
  Data_Get_Struct(self, parser_state_t, state);

  VALUE max_message_size;
  rb_scan_args(argc, argv, "01", &max_message_size);

  if (max_message_size == Qnil) {
    max_message_size = rb_funcall(mStomp, g_max_message_size, 0);
  }

  state->error = Qnil;
  state->max_message_size = FIX2LONG(max_message_size);
  state->chunk = NULL;
  state->p = NULL;
  state->cs = start;
  state->mark = NULL;
  state->mark_key = Qnil;
  state->mark_message = Qnil;
  state->mark_message_size = 0;
  state->mark_message_content_length = 0;

  return self;
}

static VALUE parser_parse(VALUE self, VALUE chunk) {
  parser_state_t *state;
  Data_Get_Struct(self, parser_state_t, state);

  if (NIL_P(state->error)) {
    /*
    if state.chunk
      p = state.chunk.bytesize
      chunk = state.chunk << chunk
    else
      p = 0
    end

    pe = chunk.bytesize # special
    */

    // size_t max_message_size = state->max_message_size;
    const char *p = RSTRING_PTR(chunk);
    const char *pe = p + RSTRING_LEN(chunk);

    int cs = state->cs;
    const char *mark = state->mark;
    VALUE mark_key = state->mark_key;
    VALUE mark_message = state->mark_message;
    size_t mark_message_size = state->mark_message_size;
    // mark_content_length = state->mark_content_length;

    %% write exec;

    /*
    if mark
      state.p = chunk.bytesize
      state.chunk = chunk
    else
      state.p = 0
      state.chunk = nil
    end
    */

    state->cs = cs;
    state->mark = mark;
    state->mark_key = mark_key;
    state->mark_message = mark_message;
    state->mark_message_size = mark_message_size;
    // state->mark_content_length = mark_content_length;

    if (cs == error) {
      long index = p - RSTRING_PTR(chunk);
      state->error = rb_funcall(mStomp, g_build_parse_error, 2, chunk, LONG2NUM(index));
    }
  }

  if ( ! NIL_P(state->error)) {
    rb_exc_raise(state->error);
  }

  return Qnil;
}

void Init_c_parser(void) {
  VALUE mStompede = rb_const_get(rb_cObject, rb_intern("Stompede"));

  mStomp = rb_const_get(mStompede, rb_intern("Stomp"));
  cMessage = rb_const_get(mStomp, rb_intern("Message"));

  g_new = rb_intern("new");
  g_write_command = rb_intern("write_command");
  g_write_header = rb_intern("write_header");
  g_write_body = rb_intern("write_body");
  g_build_parse_error = rb_intern("build_parse_error");
  g_max_message_size = rb_intern("max_message_size");

  VALUE cParser = rb_define_class_under(mStomp, "CParser", rb_cObject);
  rb_define_alloc_func(cParser, parser_alloc);

  rb_define_method(cParser, "initialize", parser_initialize, -1);
  rb_define_method(cParser, "parse", parser_parse, 1);
}