Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/jexl-parser/src/   (Fast Lexical Analyzer Version 2.6©)  Datei vom 27.6.2026 mit Größe 17 kB image not shown  

Quelle  lexer.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


use std::fmt;

pub type Spanned<Token, Location, Error> = Result<(Location, Token, Location), Error>;

#[derive(Debug, Clone, PartialEq)]
pub enum Token<'input> {
    // Literals
    Number(f64),
    DoubleQuotedString(&'input str),
    SingleQuotedString(&'input str),
    Boolean(bool),
    Null,
    Identifier(&'input str),

    // Operators
    Plus,
    Minus,
    Multiply,
    Divide,
    FloorDivide,
    Modulus,
    Exponent,

    // Comparison
    Equal,
    NotEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
    In,

    // Logical
    And,
    Or,

    // Punctuation
    LeftParen,
    RightParen,
    LeftBracket,
    RightBracket,
    LeftBrace,
    RightBrace,
    Comma,
    Dot,
    Colon,
    Question,
    Pipe,

    // Whitespace (usually ignored)
    Whitespace,
    // End of input is handled automatically by lalrpop
}

impl<'input> fmt::Display for Token<'input> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Token::Number(n) => write!(f, "{}", n),
            Token::DoubleQuotedString(s) => write!(f, "\"{}\"", s),
            Token::SingleQuotedString(s) => write!(f, "'{}'", s),
            Token::Boolean(b) => write!(f, "{}", b),
            Token::Null => write!(f, "null"),
            Token::Identifier(s) => write!(f, "{}", s),
            Token::Plus => write!(f, "+"),
            Token::Minus => write!(f, "-"),
            Token::Multiply => write!(f, "*"),
            Token::Divide => write!(f, "/"),
            Token::FloorDivide => write!(f, "//"),
            Token::Modulus => write!(f, "%"),
            Token::Exponent => write!(f, "^"),
            Token::Equal => write!(f, "=="),
            Token::NotEqual => write!(f, "!="),
            Token::Greater => write!(f, ">"),
            Token::GreaterEqual => write!(f, ">="),
            Token::Less => write!(f, "<"),
            Token::LessEqual => write!(f, "<="),
            Token::In => write!(f, "in"),
            Token::And => write!(f, "&&"),
            Token::Or => write!(f, "||"),
            Token::LeftParen => write!(f, "("),
            Token::RightParen => write!(f, ")"),
            Token::LeftBracket => write!(f, "["),
            Token::RightBracket => write!(f, "]"),
            Token::LeftBrace => write!(f, "{{"),
            Token::RightBrace => write!(f, "}}"),
            Token::Comma => write!(f, ","),
            Token::Dot => write!(f, "."),
            Token::Colon => write!(f, ":"),
            Token::Question => write!(f, "?"),
            Token::Pipe => write!(f, "|"),
            Token::Whitespace => write!(f, " "),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Lexer<'input> {
    input: &'input str,
    position: usize,
    line: usize,
    column: usize,
}

#[derive(Debug, Clone, PartialEq)]
pub struct LexError {
    pub message: String,
    pub line: usize,
    pub column: usize,
}

impl fmt::Display for LexError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Lexical error at line {}, column {}: {}",
            self.line, self.column, self.message
        )
    }
}

impl std::error::Error for LexError {}

impl<'input> Lexer<'input> {
    pub fn new(input: &'input str) -> Self {
        Lexer {
            input,
            position: 0,
            line: 1,
            column: 1,
        }
    }
}

impl<'input> Iterator for Lexer<'input> {
    type Item = Spanned<Token<'input>, usize, LexError>;

    fn next(&mut self) -> Option<Self::Item> {
        // Skip whitespace first
        self.skip_whitespace();

        // Check if we've reached the end after skipping whitespace
        if self.is_at_end() {
            return None;
        }

        let start_pos = self.position;
        match self.next_token_after_whitespace() {
            Ok(token) => Some(Ok((start_pos, token, self.position))),
            Err(error) => Some(Err(error)),
        }
    }
}

impl<'input> Lexer<'input> {
    fn next_token_after_whitespace(&mut self) -> Result<Token<'input>, LexError> {
        // Whitespace has already been skipped by the caller
        let ch = self.current_char();

        match ch {
            // Single-character tokens
            '+' => {
                self.advance();
                Ok(Token::Plus)
            }
            '-' => {
                self.advance();
                Ok(Token::Minus)
            }
            '*' => {
                self.advance();
                Ok(Token::Multiply)
            }
            '%' => {
                self.advance();
                Ok(Token::Modulus)
            }
            '^' => {
                self.advance();
                Ok(Token::Exponent)
            }
            '(' => {
                self.advance();
                Ok(Token::LeftParen)
            }
            ')' => {
                self.advance();
                Ok(Token::RightParen)
            }
            '[' => {
                self.advance();
                Ok(Token::LeftBracket)
            }
            ']' => {
                self.advance();
                Ok(Token::RightBracket)
            }
            '{' => {
                self.advance();
                Ok(Token::LeftBrace)
            }
            '}' => {
                self.advance();
                Ok(Token::RightBrace)
            }
            ',' => {
                self.advance();
                Ok(Token::Comma)
            }
            ':' => {
                self.advance();
                Ok(Token::Colon)
            }
            '?' => {
                self.advance();
                Ok(Token::Question)
            }
            '|' => {
                self.advance();
                if self.current_char() == '|' {
                    self.advance();
                    Ok(Token::Or)
                } else {
                    Ok(Token::Pipe)
                }
            }

            // Multi-character tokens
            '/' => {
                self.advance();
                if self.current_char() == '/' {
                    self.advance();
                    Ok(Token::FloorDivide)
                } else {
                    Ok(Token::Divide)
                }
            }

            '=' => {
                self.advance();
                if self.current_char() == '=' {
                    self.advance();
                    Ok(Token::Equal)
                } else {
                    Err(LexError {
                        message: "Unexpected character '='. Did you mean '=='?".to_string(),
                        line: self.line,
                        column: self.column,
                    })
                }
            }

            '!' => {
                self.advance();
                if self.current_char() == '=' {
                    self.advance();
                    Ok(Token::NotEqual)
                } else {
                    Err(LexError {
                        message: "Unexpected character '!'. Did you mean '!='?".to_string(),
                        line: self.line,
                        column: self.column,
                    })
                }
            }

            '>' => {
                self.advance();
                if self.current_char() == '=' {
                    self.advance();
                    Ok(Token::GreaterEqual)
                } else {
                    Ok(Token::Greater)
                }
            }

            '<' => {
                self.advance();
                if self.current_char() == '=' {
                    self.advance();
                    Ok(Token::LessEqual)
                } else {
                    Ok(Token::Less)
                }
            }

            '&' => {
                self.advance();
                if self.current_char() == '&' {
                    self.advance();
                    Ok(Token::And)
                } else {
                    Err(LexError {
                        message: "Unexpected character '&'. Did you mean '&&'?".to_string(),
                        line: self.line,
                        column: self.column,
                    })
                }
            }

            // String literals
            '"' => self.scan_double_quoted_string(),
            '\'' => self.scan_single_quoted_string(),

            // Numbers
            c if c.is_ascii_digit() => self.scan_number(),

            // Handle numbers starting with a dot (like .89)
            '.' => {
                if self.position + 1 < self.input.len() {
                    let next_char = self.input.chars().nth(self.position + 1).unwrap_or('\0');
                    if next_char.is_ascii_digit() {
                        self.scan_number()
                    } else {
                        self.advance();
                        Ok(Token::Dot)
                    }
                } else {
                    self.advance();
                    Ok(Token::Dot)
                }
            }

            // Identifiers and keywords
            c if c.is_alphabetic() || c == '_' => self.scan_identifier(),

            _ => Err(LexError {
                message: format!("Unexpected character '{}'", ch),
                line: self.line,
                column: self.column,
            }),
        }
    }

    fn scan_double_quoted_string(&mut self) -> Result<Token<'input>, LexError> {
        self.advance(); // consume opening quote
        let start_pos = self.position;

        // Match pattern: ([^"\\]*(\\")?)*
        while !self.is_at_end() {
            let ch = self.current_char();

            if ch == '"' {
                // End of string
                let end_pos = self.position;
                self.advance(); // consume closing quote
                let string_slice = &self.input[start_pos..end_pos];
                return Ok(Token::DoubleQuotedString(string_slice));
            } else if ch == '\\' {
                // Must be followed by "
                self.advance(); // consume backslash
                if !self.is_at_end() && self.current_char() == '"' {
                    self.advance(); // consume escaped quote
                } else {
                    return Err(LexError {
                        message: "Invalid escape sequence in double-quoted string".to_string(),
                        line: self.line,
                        column: self.column,
                    });
                }
            } else {
                self.advance();
            }
        }

        Err(LexError {
            message: "Unterminated string literal".to_string(),
            line: self.line,
            column: self.column,
        })
    }

    fn scan_single_quoted_string(&mut self) -> Result<Token<'input>, LexError> {
        self.advance(); // consume opening quote
        let start_pos = self.position;

        // Match pattern: ([^'\\]*(\\')?)*
        while !self.is_at_end() {
            let ch = self.current_char();

            if ch == '\'' {
                // End of string
                let end_pos = self.position;
                self.advance(); // consume closing quote
                let string_slice = &self.input[start_pos..end_pos];
                return Ok(Token::SingleQuotedString(string_slice));
            } else if ch == '\\' {
                // Must be followed by '
                self.advance(); // consume backslash
                if !self.is_at_end() && self.current_char() == '\'' {
                    self.advance(); // consume escaped quote
                } else {
                    return Err(LexError {
                        message: "Invalid escape sequence in single-quoted string".to_string(),
                        line: self.line,
                        column: self.column,
                    });
                }
            } else {
                self.advance();
            }
        }

        Err(LexError {
            message: "Unterminated string literal".to_string(),
            line: self.line,
            column: self.column,
        })
    }

    fn scan_number(&mut self) -> Result<Token<'input>, LexError> {
        let start_pos = self.position;

        // Handle numbers starting with a dot
        if self.current_char() == '.' {
            self.advance();
        }

        // Scan digits (either integer part or fractional part)
        while !self.is_at_end() && self.current_char().is_ascii_digit() {
            self.advance();
        }

        // Check for decimal point (only if we didn't start with one)
        if !&self.input[start_pos..self.position].starts_with('.')
            && !self.is_at_end()
            && self.current_char() == '.'
        {
            // Look ahead to see if there's a digit after the dot
            if self.position + 1 < self.input.len() {
                let next_char = self.input.chars().nth(self.position + 1).unwrap_or('\0');
                if next_char.is_ascii_digit() {
                    self.advance(); // consume dot

                    // Scan fractional part
                    while !self.is_at_end() && self.current_char().is_ascii_digit() {
                        self.advance();
                    }
                }
            }
        }

        let number_str = &self.input[start_pos..self.position];
        match number_str.parse::<f64>() {
            Ok(num) => Ok(Token::Number(num)),
            Err(_) => Err(LexError {
                message: format!("Invalid number format: {}", number_str),
                line: self.line,
                column: self.column,
            }),
        }
    }

    fn scan_identifier(&mut self) -> Result<Token<'input>, LexError> {
        let start_pos = self.position;

        while !self.is_at_end() {
            let ch = self.current_char();
            if ch.is_alphanumeric() || ch == '_' {
                self.advance();
            } else {
                break;
            }
        }

        let identifier = &self.input[start_pos..self.position];

        // Check for keywords
        let token = match identifier {
            "true" => Token::Boolean(true),
            "false" => Token::Boolean(false),
            "null" => Token::Null,
            "in" => Token::In,
            _ => Token::Identifier(identifier),
        };

        Ok(token)
    }

    fn skip_whitespace(&mut self) {
        while !self.is_at_end() && self.current_char().is_whitespace() {
            if self.current_char() == '\n' {
                self.line += 1;
                self.column = 1;
            } else {
                self.column += 1;
            }
            self.advance();
        }
    }

    fn current_char(&self) -> char {
        self.input.chars().nth(self.position).unwrap_or('\0')
    }

    fn advance(&mut self) {
        if !self.is_at_end() {
            self.position += 1;
            self.column += 1;
        }
    }

    fn is_at_end(&self) -> bool {
        self.position >= self.input.len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_tokens() {
        let lexer = Lexer::new("+ - * / % ^");
        let tokens: Result<Vec<_>, _> = lexer.collect();
        let tokens = tokens.unwrap();

        let expected_tokens = vec![
            Token::Plus,
            Token::Minus,
            Token::Multiply,
            Token::Divide,
            Token::Modulus,
            Token::Exponent,
        ];

        let actual_tokens: Vec<Token> = tokens.into_iter().map(|(_, token, _)| token).collect();
        assert_eq!(actual_tokens, expected_tokens);
    }

    #[test]
    fn test_numbers() {
        let lexer = Lexer::new("123 45.67 .89");
        let tokens: Result<Vec<_>, _> = lexer.collect();
        let tokens = tokens.unwrap();

        let expected_tokens = vec![
            Token::Number(123.0),
            Token::Number(45.67),
            Token::Number(0.89),
        ];

        let actual_tokens: Vec<Token> = tokens.into_iter().map(|(_, token, _)| token).collect();
        assert_eq!(actual_tokens, expected_tokens);
    }

    #[test]
    fn test_strings() {
        let lexer = Lexer::new(r#""hello" 'world'"#);
        let tokens: Result<Vec<_>, _> = lexer.collect();
        let tokens = tokens.unwrap();

        let expected_tokens = vec![
            Token::DoubleQuotedString("hello"),
            Token::SingleQuotedString("world"),
        ];

        let actual_tokens: Vec<Token> = tokens.into_iter().map(|(_, token, _)| token).collect();
        assert_eq!(actual_tokens, expected_tokens);
    }

    #[test]
    fn test_identifiers_and_keywords() {
        let lexer = Lexer::new("foo true false null in");
        let tokens: Result<Vec<_>, _> = lexer.collect();
        let tokens = tokens.unwrap();

        let expected_tokens = vec![
            Token::Identifier("foo"),
            Token::Boolean(true),
            Token::Boolean(false),
            Token::Null,
            Token::In,
        ];

        let actual_tokens: Vec<Token> = tokens.into_iter().map(|(_, token, _)| token).collect();
        assert_eq!(actual_tokens, expected_tokens);
    }

    #[test]
    fn test_complex_expression() {
        let lexer = Lexer::new("foo.bar[0] == 'test' && (x > 1)");
        let tokens: Result<Vec<_>, _> = lexer.collect();
        let tokens = tokens.unwrap();

        let expected_tokens = vec![
            Token::Identifier("foo"),
            Token::Dot,
            Token::Identifier("bar"),
            Token::LeftBracket,
            Token::Number(0.0),
            Token::RightBracket,
            Token::Equal,
            Token::SingleQuotedString("test"),
            Token::And,
            Token::LeftParen,
            Token::Identifier("x"),
            Token::Greater,
            Token::Number(1.0),
            Token::RightParen,
        ];

        let actual_tokens: Vec<Token> = tokens.into_iter().map(|(_, token, _)| token).collect();
        assert_eq!(actual_tokens, expected_tokens);
    }
}

Messung V0.5 in Prozent
C=90 H=95 G=92

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.