// re2c $INPUT -o $OUTPUT --tags
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <vector>

#define BUFSIZE 4095

struct Input {
    FILE *file;
    char buf[BUFSIZE + 1], *lim, *cur, *mar, *tok;
    // Tag variables must be part of the lexer state passed to YYFILL.
    // They don't correspond to tags and should be autogenerated by re2c.
    /*!stags:re2c format = 'const char *@@;'; */
    bool eof;
};

struct SemVer { int major, minor, patch; };

static bool operator==(const SemVer &x, const SemVer &y) {
    return x.major == y.major && x.minor == y.minor && x.patch == y.patch;
}

static int s2n(const char *s, const char *e) { // pre-parsed string to number
    int n = 0;
    for (; s < e; ++s) n = n * 10 + (*s - '0');
    return n;
}

static int fill(Input &in) {
    if (in.eof) return 1;

    const size_t shift = in.tok - in.buf;
    const size_t used = in.lim - in.tok;

    // Error: lexeme too long. In real life could reallocate a larger buffer.
    if (shift < 1) return 2;

    // Shift buffer contents (discard everything up to the current token).
    memmove(in.buf, in.tok, used);
    in.lim -= shift;
    in.cur -= shift;
    in.mar -= shift;
    in.tok -= shift;
    // Tag variables need to be shifted like other input positions. The check
    // for non-NULL is only needed if some tags are nested inside of alternative
    // or repetition, so that they can have NULL value.
    /*!stags:re2c format = "if (in.@@) in.@@ -= shift;\n"; */

    // Fill free space at the end of buffer with new data from file.
    in.lim += fread(in.lim, 1, BUFSIZE - used, in.file);
    in.lim[0] = 0;
    in.eof = in.lim < in.buf + BUFSIZE;
    return 0;
}

static bool lex(Input &in, std::vector<SemVer> &vers) {
    // User-defined local variables that store final tag values.
    // They are different from tag variables autogenerated with `stags:re2c`,
    // as they are set at the end of match and used only in semantic actions.
    const char *t1, *t2, *t3, *t4;
    for (;;) {
        in.tok = in.cur;
    /*!re2c
        re2c:eof = 0;
        re2c:api:style = free-form;
        re2c:define:YYCTYPE  = char;
        re2c:define:YYCURSOR = in.cur;
        re2c:define:YYMARKER = in.mar;
        re2c:define:YYLIMIT  = in.lim;
        re2c:define:YYFILL   = "fill(in) == 0";
        re2c:tags:expression = "in.@@";

        num = [0-9]+;

        num @t1 "." @t2 num @t3 ("." @t4 num)? [\n] {
            int major = s2n(in.tok, t1);
            int minor = s2n(t2, t3);
            int patch = t4 != NULL ? s2n(t4, in.cur - 1) : 0;
            SemVer ver = {major, minor, patch};
            vers.push_back(ver);
            continue;
        }
        $ { return true; }
        * { return false; }
    */}
}

int main() {
    const char *fname = "input";
    const SemVer semver = {1, 22, 333};
    std::vector<SemVer> expect(BUFSIZE, semver), actual;

    // Prepare input file (make sure it exceeds buffer size).
    FILE *f = fopen(fname, "w");
    for (int i = 0; i < BUFSIZE; ++i) fprintf(f, "1.22.333\n");
    fclose(f);

    // Reopen input file for reading.
    f = fopen(fname, "r");

    // Initialize lexer state: all pointers are at the end of buffer.
    Input in;
    in.file = f;
    in.cur = in.mar = in.tok = in.lim = in.buf + BUFSIZE;
    /*!stags:re2c format = "in.@@ = in.lim;\n"; */
    in.eof = false;
    // Sentinel (at YYLIMIT pointer) is set to zero, which triggers YYFILL.
    *in.lim = 0;
  
    // Run the lexer and check results.
    assert(lex(in, actual) && expect == actual);

    // Cleanup: remove input file.
    fclose(f);
    remove(fname);
    return 0;
}
