lexer

    A generic lexer library for handwritten lexers in MoonBit

    lexer
    programming language
    Download zip
    Author
    Version
    0.2.2
    License
    Apache-2.0
    Last updated
    9 hours ago
    Downloads
    113K

    #bobzhang/lexer

    A generic lexer library for building handwritten lexers in MoonBit. This library provides essential lexing utilities including position tracking, character advancement, whitespace handling, and error reporting.

    #Features

    • Position Tracking: Accurate line and column tracking for better error reporting
    • Unicode Support: Proper handling of surrogate pairs and multi-byte characters
    • Flexible Character Handling: Peek, advance, and expect specific characters or strings
    • Whitespace Management: Skip whitespace while preserving significant newlines
    • String View Integration: Efficient string processing using MoonBit's string views
    • Error Reporting: Detailed error messages with position information

    #Installation

    moon add bobzhang/lexer

    #Types

    #Position

    The Position struct tracks location information in the input:

    ///|
    #valtype
    pub(all) struct Position {
    line : Int
    column : Int
    } derive(Eq, Debug)

    #Lexer

    The main lexer struct maintains state and position:

    • input: String - The input text being lexed
    • position: Int - Current byte position in input
    • line: Int - Current line number (1-based)
    • column: Int - Current column position (1-based)

    #Quick Start

    ///|
    test "quick start example" {
    let lexer = @lexer.Lexer::Lexer("key = value")

    // Peek at current character without advancing
    match lexer.peek() {
    Some('k') => inspect("Found 'k'", content="Found 'k'")
    _ => ()
    }

    // Advance through characters
    lexer.advance() // moves to 'e'
    lexer.advance() // moves to 'y'
    lexer.advance() // moves to ' '

    // Skip whitespace
    lexer.skip_whitespace() // skips spaces, tabs, carriage returns

    // Expect specific characters or strings
    lexer.expect_char('=') // advances if '=' is found, otherwise raises error
    lexer.skip_whitespace()
    lexer.expect_string("value") // expects and consumes "value"

    // Get current position for error reporting
    let pos = lexer.get_loc() // returns Position { line: 1, column: 12 }
    inspect(pos.line, content="1")
    inspect(pos.column, content="12")
    }

    #Basic Usage

    #Creating a Lexer

    ///|
    test "creating a lexer" {
    let lexer = @lexer.Lexer::Lexer("key = value")
    inspect(lexer.get_position(), content="0")
    inspect(lexer.get_loc().line, content="1")
    inspect(lexer.get_loc().column, content="1")
    }

    #Position Tracking

    ///|
    test "position tracking example" {
    let lexer = @lexer.Lexer::Lexer("hello\nworld")
    inspect(lexer.get_loc().line, content="1")
    inspect(lexer.get_loc().column, content="1")

    // Advance through characters
    lexer.advance() // h
    inspect(lexer.get_loc().column, content="2")

    // Handle newlines explicitly
    lexer.advance() // move past \n
    lexer.new_line() // update line tracking
    inspect(lexer.get_loc().line, content="2")
    inspect(lexer.get_loc().column, content="1")
    }

    #API Reference

    #Methods

    #Creation

    • Lexer::Lexer(input : String) -> Lexer - Create a new lexer with the given input

    #Character Operations

    • peek() -> Char? - Get current character without advancing
    • advance() -> Unit - Advance to next character (handles Unicode properly)
    • expect_char(ch : Char, msg? : String) -> Unit raise - Expect and consume a specific character

    #String Operations

    • expect_string(str : String, msg? : String) -> Unit raise - Expect and consume a specific string
    • view() -> StringView - Get a view of the remaining input
    • update_view(view : StringView) -> Unit - Update position based on a new view

    #Whitespace Handling

    • skip_whitespace() -> Unit - Skip spaces, tabs, and carriage returns (not newlines)
    • skip_single_newline() -> Unit - Skip a single newline and update line tracking

    #Position Tracking

    • get_loc() -> Position - Get current line and column position
    • get_position() -> Int - Get current byte position in input
    • new_line() -> Unit - Explicitly advance to new line (updates line/column tracking)

    #Error Handling

    • error(msg : String) -> String - Create detailed error message with position info

    #Core Methods

    ///|
    test "core methods example" {
    let lexer = @lexer.Lexer::Lexer("hello\nworld")
    inspect(lexer.get_loc().line, content="1")
    inspect(lexer.get_loc().column, content="1")

    // Advance through characters
    lexer.advance() // h
    inspect(lexer.get_loc().column, content="2")

    // Handle newlines explicitly
    lexer.advance() // move past \n
    lexer.new_line() // update line tracking
    inspect(lexer.get_loc().line, content="2")
    inspect(lexer.get_loc().column, content="1")
    }

    #Core Methods

    #Character Access

    ///|
    test "character access example" {
    let lexer = @lexer.Lexer::Lexer("Hello")
    debug_inspect(lexer.peek(), content="Some('H')")
    lexer.advance()
    debug_inspect(lexer.peek(), content="Some('e')")
    }

    #String Views

    The lexer integrates with MoonBit's string views for efficient processing:

    ///|
    test "string views example" {
    let lexer = @lexer.Lexer::Lexer("😈x中world!")
    match lexer.view() {
    [.. "😈x", .. rest] => lexer.update_view(rest)
    _ => ()
    }
    debug_inspect(lexer.peek(), content="Some('中')")
    }

    #Whitespace Handling

    ///|
    test "whitespace handling example" {
    let lexer = @lexer.Lexer::Lexer(" \t\rHello, world!")
    lexer.skip_whitespace()
    debug_inspect(lexer.peek(), content="Some('H')")
    }

    #Expectations and Error Handling

    The lexer provides methods to expect specific characters or strings:

    ///|
    test "expectations example" {
    let lexer = @lexer.Lexer::Lexer("=true")
    // Expect a specific character
    lexer.expect_char('=', msg="Expected equals sign")

    // Expect a string
    lexer.expect_string("true", msg="Expected boolean value")

    // Error messages include precise position information
    let error_msg = lexer.error("Unexpected character")
    inspect(error_msg, content="Unexpected character at line 1, column 6")
    }

    #Advanced Usage

    #Unicode Support

    The lexer properly handles Unicode characters including surrogate pairs:

    ///|
    test "unicode support example" {
    let lexer = @lexer.Lexer::Lexer("😈x")
    inspect(lexer.get_position(), content="0")
    lexer.advance() // Correctly advances past the 2-byte emoji
    inspect(lexer.get_position(), content="2") // 2 bytes for emoji
    debug_inspect(lexer.peek(), content="Some('x')")
    }

    #Position Management

    Get current position information:

    ///|
    test "position management example" {
    let lexer = @lexer.Lexer::Lexer("test")
    let pos = lexer.get_loc() // Get Position struct
    let offset = lexer.get_position() // Get byte offset
    inspect(pos.line, content="1")
    inspect(pos.column, content="1")
    inspect(offset, content="0")
    }

    #Position-Aware Error Handling

    ///|
    test "position aware error handling example" {
    let lexer = @lexer.Lexer::Lexer("abc")
    lexer.advance() // move to 'b'
    lexer.advance() // move to 'c'
    let start_pos = lexer.get_loc()
    let msg = "Invalid identifier at line " + start_pos.line.to_string()
    inspect(msg, content="Invalid identifier at line 1")
    }

    #Custom Lexer Implementation

    ///|
    test "custom lexer implementation example" {
    let lexer = @lexer.Lexer::Lexer("a=b")
    let tokens = []

    // Simple tokenization - process each character
    while lexer.peek() is Some(_) {
    lexer.skip_whitespace()
    match lexer.peek() {
    None => ()
    Some('=') => {
    lexer.advance()
    tokens.push("EQUALS")
    }
    Some(c) if c.is_ascii_alphabetic() => {
    let mut identifier = ""
    while lexer.peek() is Some(ch) && ch.is_ascii_alphabetic() {
    identifier = identifier + Char::to_string(ch)
    lexer.advance()
    }
    tokens.push("ID:" + identifier)
    }
    Some(_) => lexer.advance() // skip unknown characters
    }
    }
    debug_inspect(tokens, content="[\"ID:a\", \"EQUALS\", \"ID:b\"]")
    }

    #Example: Basic Parsing

    ///|
    test "basic parsing example" {
    let lexer = @lexer.Lexer::Lexer("key = \"value\"")

    // Skip initial whitespace
    lexer.skip_whitespace()

    // Parse key name
    let mut key = ""
    while lexer.peek() is Some(ch) && ch != ' ' {
    key = key + Char::to_string(ch)
    lexer.advance()
    }

    // Skip whitespace and expect equals
    lexer.skip_whitespace()
    lexer.expect_char('=')
    lexer.skip_whitespace()

    // Expect quoted string
    lexer.expect_char('"')
    let mut value = ""
    while lexer.peek() is Some(ch) && ch != '"' {
    value = value + Char::to_string(ch)
    lexer.advance()
    }
    lexer.expect_char('"')
    inspect(key, content="key")
    inspect(value, content="value")
    }

    #License

    Apache-2.0

    Lexer

    type Lexer

    Lexer state with position tracking for better error reporting

    Lexer::Lexer

    fn Lexer::Lexer(input : String) -> Lexer

    Create a new lexer

    Lexer::advance

    fn Lexer::advance(self : Lexer) -> Unit

    Get current character and advance position handle surrogate pairs and multi-byte characters Note: Does not automatically track newlines - call new_line() explicitly when needed

    Lexer::error

    fn Lexer::error(self : Lexer, msg : String) -> String

    Create a detailed error message with position information

    Lexer::expect_char

    fn Lexer::expect_char(self : Lexer, ch : Char, msg? : String) -> Unit raise

    Expect a specific character and advance, or fail with detailed error

    Lexer::expect_string

    fn Lexer::expect_string(self : Lexer, str : String, msg? : String) -> Unit raise

    Expect a string and advance, or fail with detailed error Note the parameter str is not expected to have a newline otherwise the line position is not correct

    Example

    Lexer::get_loc

    fn Lexer::get_loc(self : Lexer) -> Position

    Return the current 1-based (line, column) position.

    Use this to capture the start of a lexeme before consuming it; pair the captured position with the post-consumption value to build the half-open span attached to the resulting token.

    Lexer::get_position

    fn Lexer::get_position(self : Lexer) -> Int

    Get current position for error reporting

    Lexer::new_line

    fn Lexer::new_line(self : Lexer) -> Unit

    Explicitly advance to a new line (call when encountering '\n') This updates line and column tracking appropriately

    Lexer::peek

    fn Lexer::peek(self : Lexer) -> Char?

    Get current character without advancing

    Example:

    let lexer = Lexer::Lexer("Hello, world!") inspect(lexer.peek(), content="Some('H')") lexer.advance() inspect(lexer.peek(), content="Some('e')")

    Lexer::peek_charcode

    fn Lexer::peek_charcode(self : Lexer) -> UInt16?

    Return the raw UTF-16 code unit at the cursor without advancing.

    Unlike peek, which decodes a full Char (and so may span a surrogate pair), this returns the underlying 16-bit unit directly. Useful in hot paths where the lexer only needs to test ASCII bytes and wants to skip the UTF-decoding cost. Returns None at end of input.

    Lexer::skip_single_newline

    fn Lexer::skip_single_newline(self : Lexer) -> Unit

    Consume one line terminator if the cursor is sitting on one.

    Both \n and \r\n are treated as a single newline, and the line counter is advanced. If the next character is not a newline this is a no-op, which makes the call safe to use after constructs that may or may not have left a trailing newline behind (e.g. comments at EOF).

    Lexer::skip_whitespace

    fn Lexer::skip_whitespace(self : Lexer) -> Unit

    Skip whitespace characters not including newlines Note: This method does not skip '\n' characters as those are significant in TOML

    Example:

    let lexer = Lexer::Lexer(" \t\rHello, world!") lexer.skip_whitespace() inspect(lexer.peek(), content="Some('H')")

    Lexer::update_view

    fn Lexer::update_view(self : Lexer, view : StringView) -> Unit

    Update the lexer's position and column based on a new view

    Example:

    let lexer = Lexer::Lexer("😈x中world!") match lexer.view() { [.."😈x", .. rest] => lexer.update_view(rest) _ => () } inspect(lexer.peek(), content="Some('中')")

    Lexer::view

    fn Lexer::view(self : Lexer) -> StringView

    Get a view of the input string

    Example:

    let lexer = Lexer::Lexer("Hello, world!") lexer.advance() lexer.advance() inspect(lexer.view(), content="llo, world!")

    Position

    pub(all) struct Position {
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    Lexer implementation for TOML

    Position::equal

    #deprecated("compare with `==`; the Eq impl is unaffected")
    fn Position::equal(Position, Position) -> Bool

    Position::not_equal

    #deprecated("compare with `==`; the Eq impl is unaffected")
    fn Position::not_equal(x : Position, y : Position) -> Bool

    Position::to_repr

    #deprecated("render via the Debug trait, e.g. `debug_inspect`")
    fn Position::to_repr(Position) ->
    Repr

    Source Files

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io