robinpath 0.2.0

RobinPath - A lightweight, fast scripting language interpreter for automation and data processing
Documentation
use crate::error::RobinPathError;
use crate::lexer::{Token, TokenKind};

pub struct TokenStream {
    tokens: Vec<Token>,
    pos: usize,
}

impl TokenStream {
    pub fn new(tokens: Vec<Token>) -> Self {
        Self { tokens, pos: 0 }
    }

    pub fn current(&self) -> Option<&Token> {
        self.tokens.get(self.pos)
    }

    pub fn peek(&self) -> Option<&Token> {
        self.tokens.get(self.pos)
    }

    pub fn peek_offset(&self, offset: usize) -> Option<&Token> {
        self.tokens.get(self.pos + offset)
    }

    pub fn next(&mut self) -> Option<&Token> {
        let tok = self.tokens.get(self.pos);
        if tok.is_some() {
            self.pos += 1;
        }
        // Return previous token (the one we just consumed)
        if self.pos > 0 {
            self.tokens.get(self.pos - 1)
        } else {
            None
        }
    }

    pub fn advance(&mut self) {
        if self.pos < self.tokens.len() {
            self.pos += 1;
        }
    }

    pub fn check(&self, kind: TokenKind) -> bool {
        self.current().is_some_and(|t| t.kind == kind)
    }

    pub fn check_text(&self, text: &str) -> bool {
        self.current().is_some_and(|t| t.text == text)
    }

    pub fn check_keyword(&self, kw: &str) -> bool {
        self.current()
            .is_some_and(|t| t.kind == TokenKind::Keyword && t.text == kw)
    }

    pub fn match_kind(&mut self, kind: TokenKind) -> bool {
        if self.check(kind) {
            self.advance();
            true
        } else {
            false
        }
    }

    pub fn match_keyword(&mut self, kw: &str) -> bool {
        if self.check_keyword(kw) {
            self.advance();
            true
        } else {
            false
        }
    }

    pub fn expect(&mut self, kind: TokenKind) -> Result<Token, RobinPathError> {
        if let Some(tok) = self.current() {
            if tok.kind == kind {
                let tok = tok.clone();
                self.advance();
                return Ok(tok);
            }
            let line = tok.line;
            let col = tok.column;
            return Err(RobinPathError::parse(
                format!(
                    "Expected {:?}, got {:?} '{}'",
                    kind, tok.kind, tok.text
                ),
                line,
                col,
            ));
        }
        Err(RobinPathError::parse(
            format!("Expected {:?}, got EOF", kind),
            0,
            0,
        ))
    }

    pub fn expect_keyword(&mut self, kw: &str) -> Result<Token, RobinPathError> {
        if let Some(tok) = self.current() {
            if tok.kind == TokenKind::Keyword && tok.text == kw {
                let tok = tok.clone();
                self.advance();
                return Ok(tok);
            }
            let line = tok.line;
            let col = tok.column;
            return Err(RobinPathError::parse(
                format!(
                    "Expected keyword '{}', got {:?} '{}'",
                    kw, tok.kind, tok.text
                ),
                line,
                col,
            ));
        }
        Err(RobinPathError::parse(
            format!("Expected keyword '{}', got EOF", kw),
            0,
            0,
        ))
    }

    pub fn is_at_end(&self) -> bool {
        self.pos >= self.tokens.len()
            || self.current().is_some_and(|t| t.kind == TokenKind::Eof)
    }

    pub fn save(&self) -> usize {
        self.pos
    }

    pub fn restore(&mut self, pos: usize) {
        self.pos = pos;
    }

    pub fn skip_newlines(&mut self) -> usize {
        let mut count = 0;
        while self.check(TokenKind::Newline) {
            self.advance();
            count += 1;
        }
        count
    }

    pub fn skip_comments_and_newlines(&mut self) {
        while !self.is_at_end() {
            if self.check(TokenKind::Newline) || self.check(TokenKind::Comment) {
                self.advance();
            } else {
                break;
            }
        }
    }

    /// Collect tokens until newline or EOF (for a single line)
    pub fn collect_line(&mut self) -> Vec<Token> {
        let mut tokens = Vec::new();
        while !self.is_at_end() {
            if self.check(TokenKind::Newline) || self.check(TokenKind::Eof) {
                break;
            }
            if let Some(tok) = self.current() {
                // Handle line continuation
                if tok.is_continuation {
                    tokens.push(tok.clone());
                    self.advance();
                    continue;
                }
                tokens.push(tok.clone());
                self.advance();
            }
        }
        tokens
    }

    pub fn position(&self) -> usize {
        self.pos
    }

    pub fn len(&self) -> usize {
        self.tokens.len()
    }

    pub fn is_empty(&self) -> bool {
        self.tokens.is_empty()
    }

    /// Get the current token's line number (for error reporting)
    pub fn current_line(&self) -> usize {
        self.current().map_or(0, |t| t.line)
    }

    /// Get the current token's column (for error reporting)
    pub fn current_column(&self) -> usize {
        self.current().map_or(0, |t| t.column)
    }

    /// Check if next non-newline token matches
    pub fn peek_past_newlines(&self) -> Option<&Token> {
        let mut i = self.pos;
        while i < self.tokens.len() {
            let tok = &self.tokens[i];
            if tok.kind != TokenKind::Newline && tok.kind != TokenKind::Comment {
                return Some(tok);
            }
            i += 1;
        }
        None
    }
}