///|
/// Parse a TOML value
fn Parser::parse_value(self : Parser) -> TomlValue raise ParseError {
self.skip_whitespace()
match self.peek() {
Some('"' | '\'') => TomlValue::String(self.parse_string())
Some('[') => self.parse_array()
Some('{') => self.parse_inline_table()
Some('t' | 'f') => TomlValue::Boolean(self.parse_boolean())
Some('+' | '-' | '0'..='9') => self.parse_number()
Some(ch) => raise ParseError::UnexpectedChar(self.pos, ch)
None => raise ParseError::UnexpectedEof
}
}
///|
/// Parse an array
fn Parser::parse_array(self : Parser) -> TomlValue raise ParseError {
self.pos += 1 // Skip '['
let arr : Array[TomlValue] = []
self.skip_whitespace()
// Empty array
if self.peek() == Some(']') {
self.pos += 1
return TomlValue::Array(arr)
}
while not(self.is_eof()) {
self.skip_whitespace()
// Allow newlines in arrays
while self.peek() == Some('\n') {
self.pos += 1
self.skip_whitespace()
}
arr.push(self.parse_value())
self.skip_whitespace()
// Allow newlines after value
while self.peek() == Some('\n') {
self.pos += 1
self.skip_whitespace()
}
match self.peek() {
Some(',') => {
self.pos += 1
self.skip_whitespace()
// Allow trailing comma
if self.peek() == Some(']') {
self.pos += 1
return TomlValue::Array(arr)
}
}
Some(']') => {
self.pos += 1
return TomlValue::Array(arr)
}
Some(ch) => raise ParseError::UnexpectedChar(self.pos, ch)
None => raise ParseError::UnexpectedEof
}
}
raise ParseError::UnexpectedEof
}
///|
/// Parse an inline table
fn Parser::parse_inline_table(self : Parser) -> TomlValue raise ParseError {
self.pos += 1 // Skip '{'
let table : Map[String, TomlValue] = {}
self.skip_whitespace()
// Empty table
if self.peek() == Some('}') {
self.pos += 1
return TomlValue::Table(table)
}
while not(self.is_eof()) {
self.skip_whitespace()
let key = self.parse_key()
self.skip_whitespace()
match self.next() {
Some('=') => ()
Some(ch) => raise ParseError::UnexpectedChar(self.pos - 1, ch)
None => raise ParseError::UnexpectedEof
}
self.skip_whitespace()
let value = self.parse_value()
if table.contains(key) {
raise ParseError::DuplicateKey(key)
}
table[key] = value
self.skip_whitespace()
match self.peek() {
Some(',') => self.pos += 1
Some('}') => {
self.pos += 1
return TomlValue::Table(table)
}
Some(ch) => raise ParseError::UnexpectedChar(self.pos, ch)
None => raise ParseError::UnexpectedEof
}
}
raise ParseError::UnexpectedEof
}
///|
/// Parse a key-value pair
fn Parser::parse_keyval(
self : Parser,
table : Map[String, TomlValue],
) -> Unit raise ParseError {
let key = self.parse_key()
self.skip_whitespace()
match self.next() {
Some('=') => ()
Some(ch) => raise ParseError::UnexpectedChar(self.pos - 1, ch)
None => raise ParseError::UnexpectedEof
}
self.skip_whitespace()
let value = self.parse_value()
if table.contains(key) {
raise ParseError::DuplicateKey(key)
}
table[key] = value
// Skip any trailing whitespace and comments on the same line
// But do NOT consume the newline - leave it for the main parse loop
while not(self.is_eof()) && self.peek() != Some('\n') {
match self.peek() {
Some(' ' | '\t' | '\r') => self.pos += 1
Some('#') => {
// Skip comment until end of line (but don't consume the newline)
while not(self.is_eof()) {
self.pos += 1
if self.peek() == Some('\n') {
break
}
}
break
}
_ => break
}
}
}
///|
/// Parse a table header [section]
fn Parser::parse_table_header(self : Parser) -> String raise ParseError {
self.pos += 1 // Skip '['
let key = self.parse_key()
self.skip_whitespace()
match self.next() {
Some(']') => key
Some(ch) => raise ParseError::UnexpectedChar(self.pos - 1, ch)
None => raise ParseError::UnexpectedEof
}
}
///|
/// Parse a TOML document
pub fn parse(input : String) -> TomlValue raise ParseError {
let parser = Parser::new(input)
let root : Map[String, TomlValue] = {}
let mut current_table = root
while not(parser.is_eof()) {
parser.skip_whitespace()
if parser.is_eof() {
break
}
// Skip newlines
if parser.peek() == Some('\n') {
parser.pos += 1
continue
}
match parser.peek() {
Some('[') => {
// Table header
let table_name = parser.parse_table_header()
// Create or get the table
if root.contains(table_name) {
match root[table_name] {
Table(t) => current_table = t
_ => raise ParseError::DuplicateKey(table_name)
}
} else {
let new_table : Map[String, TomlValue] = {}
root[table_name] = TomlValue::Table(new_table)
current_table = new_table
}
}
Some(_) =>
// Key-value pair
parser.parse_keyval(current_table)
None => break
}
parser.skip_whitespace()
// Expect newline or EOF after each item
if not(parser.is_eof()) && parser.peek() != Some('\n') {
match parser.peek() {
Some(ch) => raise ParseError::UnexpectedChar(parser.pos, ch)
None => ()
}
}
if parser.peek() == Some('\n') {
parser.pos += 1
}
}
TomlValue::Table(root)
}