A generic lexer library for handwritten lexers in MoonBit
moon add bobzhang/lexer///|
#valtype
pub(all) struct Position {
line : Int
column : Int
} derive(Eq, Debug)///|
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")
}///|
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")
}///|
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")
}///|
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")
}///|
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')")
}///|
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('ä¸')")
}///|
test "whitespace handling example" {
let lexer = @lexer.Lexer::Lexer(" \t\rHello, world!")
lexer.skip_whitespace()
debug_inspect(lexer.peek(), content="Some('H')")
}///|
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")
}///|
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')")
}///|
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")
}///|
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")
}///|
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\"]")
}///|
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")
}let lexer = Lexer::Lexer("Hello, world!")
inspect(lexer.peek(), content="Some('H')")
lexer.advance()
inspect(lexer.peek(), content="Some('e')")let lexer = Lexer::Lexer(" \t\rHello, world!")
lexer.skip_whitespace()
inspect(lexer.peek(), content="Some('H')")let lexer = Lexer::Lexer("😈xä¸world!")
match lexer.view() {
[.."😈x", .. rest] => lexer.update_view(rest)
_ => ()
}
inspect(lexer.peek(), content="Some('ä¸')")A generic lexer library for handwritten lexers in MoonBit