// re2js $INPUT -o $OUTPUT

const assert = require('assert');
const fs = require('fs')

const BUFSIZE = 4096
const OK = 0
const EOF = 1
const LONG_LEXEME = 2

function fill(st) {
    if (st.eof) return EOF

    // Error: lexeme too long. In real life could reallocate a larger buffer.
    if (st.token < 1) return LONG_LEXEME

    // Shift buffer contents (discard everything up to the current token).
    st.yyinput.copy(st.yyinput, 0, st.token, st.yylimit)
    st.yycursor -= st.token;
    st.yymarker -= st.token;
    st.yylimit -= st.token;
    /*!stags:re2c format = "if (st.@@ != -1) st.@@ -= st.token\n"; */
    st.token = 0;

    // Read a new chunk of data from file and append it to `yyinput`.
    let want = BUFSIZE - st.yylimit - 1 // -1 for sentinel
    let nread = fs.readSync(st.file, st.yyinput, st.yylimit, want)
    st.eof = nread < want // end of file?
    st.yylimit += nread
    st.yyinput.writeUInt8(0, st.yylimit) // sentinel

    return OK
}

function lex(st) {
    let vers = []
    loop: while (true) {
        st.token = st.yycursor

        // Final tag variables available in semantic action.
        /*!svars:re2c format = "let @@\n"; */

        /*!re2c
            re2c:api = record;
            re2c:yyrecord = st;
            re2c:YYPEEK = "readUInt8";
            re2c:YYFILL = "fill(st) == OK";
            re2c:eof = 0;
            re2c:tags = 1;

            num = [0-9]+;

            num @t1 "." @t2 num @t3 ("." @t4 num)? [\n] {
                vers.push({
                    major: Number(st.yyinput.subarray(st.token, t1)),
                    minor: Number(st.yyinput.subarray(t2, t3)),
                    patch: t4 == -1 ? 0 : Number(st.yyinput.subarray(t4, st.yycursor - 1))
                })
                continue loop
            }
            $ { return vers }
            * { return null }
        */
    }
}

function main() {
    let fname = "input"

    // Create input file.
    let content = "1.22.333\n".repeat(BUFSIZE)
    fs.writeFileSync(fname, content, function(err) { if (err) throw err; })

    // Init lexer state.
    let limit = BUFSIZE - 1 // exclude terminating null
    let st = {
        file: fs.openSync(fname, 'r'),
        yyinput: Buffer.alloc(BUFSIZE),
        yylimit: limit,
        yycursor: limit,
        yymarker: limit,
        token: limit,
        // Intermediate tag variables used by the lexer (must be autogenerated).
        /*!stags:re2c format = "@@: -1,\n"; */
        eof: false
    }

    // Run lexer on the prepared file.
    assert.deepEqual(lex(st), Array(BUFSIZE).fill({major: 1, minor: 22, patch: 333}))

    // Cleanup.
    fs.unlink(fname, function(err){ if (err) throw err; })
}

main()
