@@[target("rust")]

// Dogfooded body closer — Rust language brace matcher.
// Extends C with nested block comments (/* /* */ */) and raw strings (r#"..."#).
//
// State machine flow:
//   $Init.scan() → $Scanning.$>() ↔ $InString/$InCharLiteral/$InLineComment/$InBlockComment/$InRawString

@@system RustBodyCloserFsm {
    interface:
        scan()

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

        $Scanning {
            $>() {
                let n = self.bytes.len();
                while self.pos < n {
                    let b = self.bytes[self.pos];
                    if b == b'\n' {
                        self.pos += 1;
                    } else if b == b'/' && self.pos + 1 < n && self.bytes[self.pos + 1] == b'/' {
                        self.pos += 2;
                        -> $InLineComment
                    } else if b == b'/' && self.pos + 1 < n && self.bytes[self.pos + 1] == b'*' {
                        self.block_comment_nest = 1;
                        self.pos += 2;
                        -> $InBlockComment
                    } else if b == b'\'' {
                        // Rust uses `'` for BOTH char literals
                        // (`'a'`, `'\n'`) AND labels / lifetimes
                        // (`'block:`, `'static`, `&'a T`). The
                        // disambiguator: after the apostrophe,
                        // walk any identifier characters; if we
                        // land on a closing `'`, it's a char
                        // literal — enter $InCharLiteral. Otherwise
                        // it's a label/lifetime — just advance the
                        // apostrophe and keep scanning. An escape
                        // start (`\`) is always a char literal.
                        let next = if self.pos + 1 < n { self.bytes[self.pos + 1] } else { 0 };
                        let is_escape = next == b'\\';
                        let is_ident_start = next.is_ascii_alphabetic() || next == b'_';
                        if is_escape {
                            self.pos += 1;
                            -> $InCharLiteral
                        } else if is_ident_start {
                            let mut k = self.pos + 2;
                            while k < n {
                                let c = self.bytes[k];
                                let is_word = c.is_ascii_alphanumeric() || c == b'_';
                                if !is_word {
                                    break;
                                }
                                k += 1;
                            }
                            if k < n && self.bytes[k] == b'\'' {
                                self.pos += 1;
                                -> $InCharLiteral
                            } else {
                                self.pos += 1;
                                continue;
                            }
                        } else {
                            self.pos += 1;
                            -> $InCharLiteral
                        }
                    } else if b == b'"' {
                        self.pos += 1;
                        -> $InString
                    } else if b == b'r' {
                        // Rust raw string r#"..."# or just r"..."
                        let mut j = self.pos + 1;
                        let mut hashes: usize = 0;
                        while j < n && self.bytes[j] == b'#' {
                            hashes += 1;
                            j += 1;
                        }
                        if j < n && self.bytes[j] == b'"' {
                            self.raw_hashes = hashes;
                            self.pos = j + 1;
                            -> $InRawString
                        } else {
                            self.pos += 1;
                            continue;
                        }
                    } else if b == b'{' {
                        self.depth += 1;
                        self.pos += 1;
                    } else if b == b'}' {
                        self.depth -= 1;
                        self.pos += 1;
                        if self.depth == 0 {
                            self.result_pos = self.pos - 1;
                            self.error_kind = 0;
                            return
                        }
                    } else {
                        self.pos += 1;
                    }
                }
                self.error_kind = 3;
                self.error_msg = "body not closed".to_string();
            }
        }

        $InString {
            $>() {
                let n = self.bytes.len();
                while self.pos < n {
                    if self.bytes[self.pos] == b'\\' {
                        self.pos += 2;
                        continue;
                    }
                    if self.bytes[self.pos] == b'"' {
                        self.pos += 1;
                        -> $Scanning
                    }
                    self.pos += 1;
                }
                self.error_kind = 1;
                self.error_msg = "unterminated string".to_string();
            }
        }

        $InCharLiteral {
            $>() {
                let n = self.bytes.len();
                while self.pos < n {
                    if self.bytes[self.pos] == b'\\' {
                        self.pos += 2;
                        continue;
                    }
                    if self.bytes[self.pos] == b'\'' {
                        self.pos += 1;
                        -> $Scanning
                    }
                    self.pos += 1;
                }
                self.error_kind = 1;
                self.error_msg = "unterminated char".to_string();
            }
        }

        $InLineComment {
            $>() {
                let n = self.bytes.len();
                while self.pos < n && self.bytes[self.pos] != b'\n' {
                    self.pos += 1;
                }
                -> $Scanning
            }
        }

        $InBlockComment {
            $>() {
                // Rust supports nested block comments: /* /* */ */
                let n = self.bytes.len();
                while self.pos + 1 < n {
                    if self.bytes[self.pos] == b'/' && self.bytes[self.pos + 1] == b'*' {
                        self.block_comment_nest += 1;
                        self.pos += 2;
                        continue;
                    }
                    if self.bytes[self.pos] == b'*' && self.bytes[self.pos + 1] == b'/' {
                        self.block_comment_nest -= 1;
                        self.pos += 2;
                        if self.block_comment_nest == 0 {
                            -> $Scanning
                        }
                        continue;
                    }
                    self.pos += 1;
                }
                self.error_kind = 2;
                self.error_msg = "unterminated comment".to_string();
            }
        }

        $InRawString {
            $>() {
                // Find closing "###
                let n = self.bytes.len();
                loop {
                    if self.pos >= n {
                        self.error_kind = 4;
                        self.error_msg = "unterminated raw".to_string();
                        return
                    }
                    if self.bytes[self.pos] == b'"' {
                        let mut k = self.pos + 1;
                        let mut m: usize = 0;
                        while m < self.raw_hashes && k < n && self.bytes[k] == b'#' {
                            m += 1;
                            k += 1;
                        }
                        if m == self.raw_hashes {
                            self.pos = k;
                            -> $Scanning
                        }
                    }
                    self.pos += 1;
                }
            }
        }

    domain:
        bytes: Vec<u8> = Vec::new()
        pos: usize = 0
        depth: i32 = 1
        result_pos: usize = 0
        error_kind: usize = 0
        error_msg: String = String::new()
        block_comment_nest: i32 = 0
        raw_hashes: usize = 0
}
