//go:generate re2go $INPUT -o $OUTPUT --tags
package main

import (
	"os"
	"reflect"
	"strings"
)

const BUFSIZE int = 4095

type Input struct {
	file *os.File
	buf  []byte
	cur  int
	mar  int
	tok  int
	lim  int
	// 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 = "\t@@ int\n"; */
	eof  bool
}

type SemVer struct { major, minor, patch int }

func s2n(s []byte) int { // convert pre-parsed string to a number
	n := 0
	for _, c := range s { n = n*10 + int(c-'0') }
	return n
}

func fill(in *Input) int {
	if in.eof { return -1 } // unexpected EOF

	// Error: lexeme too long. In real life can reallocate a larger buffer.
	if in.tok < 1 { return -2 }

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

	// Fill free space at the end of buffer with new data from file.
	n, _ := in.file.Read(in.buf[in.lim:BUFSIZE])
	in.lim += n
	in.buf[in.lim] = 0

	// If read less than expected, this is the end of input.
	in.eof = in.lim < BUFSIZE

	return 0
}

func parse(in *Input) []SemVer {
	// 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.
	var t1, t2, t3, t4 int
	vers := make([]SemVer, 0)
	for {
		in.tok = in.cur
	/*!re2c
		re2c:eof = 0;
		re2c:define:YYCTYPE     = byte;
		re2c:define:YYPEEK      = "in.buf[in.cur]";
		re2c:define:YYSKIP      = "in.cur += 1";
		re2c:define:YYBACKUP    = "in.mar = in.cur";
		re2c:define:YYRESTORE   = "in.cur = in.mar";
		re2c:define:YYLESSTHAN  = "in.lim <= in.cur";
		re2c:define:YYFILL      = "fill(in) == 0";
		re2c:define:YYSTAGP     = "@@{tag} = in.cur";
		re2c:define:YYSTAGN     = "@@{tag} = -1";
		re2c:define:YYSHIFTSTAG = "@@{tag} += @@{shift}";
		re2c:tags:expression    = "in.@@";

		num = [0-9]+;

		num @t1 "." @t2 num @t3 ("." @t4 num)? [\n] {
			major := s2n(in.buf[in.tok:t1])
			minor := s2n(in.buf[t2:t3])
			patch := 0
			if t4 != -1 { patch = s2n(in.buf[t4:in.cur-1]) }
			vers = append(vers, SemVer{major, minor, patch})
			continue
		}
		$ { return vers }
		* { return nil }
	*/
	}
}


func main() () {
	fname := "input"
	content := "1.22.333\n";

	expect := make([]SemVer, 0, BUFSIZE)
	for i := 0; i < BUFSIZE; i += 1 { expect = append(expect, SemVer{1, 22, 333}) }

	// Prepare input file (make sure it exceeds buffer size).
	f, _ := os.Create(fname)
	f.WriteString(strings.Repeat(content, BUFSIZE))
	f.Seek(0, 0)

	// Initialize lexer state: all offsets are at the end of buffer.
	in := &Input{
		file: f,
		// Sentinel at `lim` offset is set to zero, which triggers YYFILL.
		buf:  make([]byte, BUFSIZE+1),
		cur:  BUFSIZE,
		mar:  BUFSIZE,
		tok:  BUFSIZE,
		lim:  BUFSIZE,
		eof:  false,
	}

	// Run the lexer and check results.
	if !reflect.DeepEqual(parse(in), expect) { panic("error"); }

	// Cleanup: remove input file.
	f.Close();
	os.Remove(fname);
}
