@@[target("rust")]

// Output Block Lexer — Frame state machine.
//
// EXHAUSTIVE tokenizer: every byte of input maps to exactly one token.
// No gaps — the parser can reconstruct complete output from the token stream.
//
// Token kinds:
//   1=IF, 2=ELSEIF, 3=ELSE, 4=WHILE, 5=FOR,
//   6=LBRACE, 7=RBRACE, 8=RETURN, 9=END,
//   10=NEWLINE, 11=TEXT, 12=COMMENT, 13=STRING
//
// Invariant: sum of (token_end - token_start) == input length.

@@system OutputBlockLexerFsm {
    interface:
        do_lex()

    machine:
        $Init {
            do_lex() { -> $Scanning }
        }

        $Scanning {
            $>() {
                let bytes = &self.bytes;
                let n = self.end;
                let mut i: usize = 0;
                let mut text_start: i64 = -1;

                while i < n {
                    let b = bytes[i];

                    // Comment detection (configured per-language)
                    let is_comment = if self.comment_double {
                        i + 1 < n && bytes[i] == self.comment_char && bytes[i + 1] == self.comment_char
                    } else {
                        self.comment_char != 0 && bytes[i] == self.comment_char
                    };

                    if is_comment {
                        if text_start >= 0 {
                            self.token_kinds.push(11); self.token_starts.push(text_start as usize); self.token_ends.push(i);
                            text_start = -1;
                        }
                        let start = i;
                        while i < n && bytes[i] != b'\n' { i += 1; }
                        self.token_kinds.push(12); self.token_starts.push(start); self.token_ends.push(i);
                        continue;
                    }

                    // String literals
                    if b == b'"' || b == b'\'' {
                        if text_start >= 0 {
                            self.token_kinds.push(11); self.token_starts.push(text_start as usize); self.token_ends.push(i);
                            text_start = -1;
                        }
                        let q = b;
                        let start = i;
                        i += 1;
                        while i < n {
                            if bytes[i] == b'\\' && i + 1 < n { i += 2; continue; }
                            if bytes[i] == q { i += 1; break; }
                            i += 1;
                        }
                        self.token_kinds.push(13); self.token_starts.push(start); self.token_ends.push(i);
                        continue;
                    }

                    // Newline
                    if b == b'\n' {
                        if text_start >= 0 {
                            self.token_kinds.push(11); self.token_starts.push(text_start as usize); self.token_ends.push(i);
                            text_start = -1;
                        }
                        self.token_kinds.push(10); self.token_starts.push(i); self.token_ends.push(i + 1);
                        i += 1;
                        continue;
                    }

                    // Braces
                    if b == b'{' || b == b'}' {
                        if text_start >= 0 {
                            self.token_kinds.push(11); self.token_starts.push(text_start as usize); self.token_ends.push(i);
                            text_start = -1;
                        }
                        let kind = if b == b'{' { 6 } else { 7 };
                        self.token_kinds.push(kind); self.token_starts.push(i); self.token_ends.push(i + 1);
                        i += 1;
                        continue;
                    }

                    // Keyword detection
                    if b.is_ascii_alphabetic() || b == b'_' {
                        let at_boundary = i == 0 || !(bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_');
                        if at_boundary {
                            let ws = i;
                            let mut j = i;
                            while j < n && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') { j += 1; }
                            let end_boundary = j >= n || !(bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_');
                            if end_boundary {
                                let word = &bytes[i..j];
                                let kind = if word == b"if" { 1 }
                                    else if word == b"elseif" { 2 }
                                    else if word == b"else" { 3 }
                                    else if word == b"while" { 4 }
                                    else if word == b"for" { 5 }
                                    else if word == b"return" { 8 }
                                    else if word == b"end" { 9 }
                                    else { 0 };
                                if kind > 0 {
                                    if text_start >= 0 {
                                        self.token_kinds.push(11); self.token_starts.push(text_start as usize); self.token_ends.push(i);
                                        text_start = -1;
                                    }
                                    self.token_kinds.push(kind); self.token_starts.push(ws); self.token_ends.push(j);
                                    i = j;
                                    continue;
                                }
                            }
                        }
                    }

                    // Accumulate as TEXT
                    if text_start < 0 { text_start = i as i64; }
                    i += 1;
                }

                // Flush remaining text
                if text_start >= 0 {
                    self.token_kinds.push(11); self.token_starts.push(text_start as usize); self.token_ends.push(n);
                }
            }
        }

    domain:
        bytes: Vec<u8> = Vec::new()
        end: usize = 0
        comment_char: u8 = 0
        comment_double: bool = false
        token_kinds: Vec<usize> = Vec::new()
        token_starts: Vec<usize> = Vec::new()
        token_ends: Vec<usize> = Vec::new()
}