///|
/// Parse a quoted string (basic or literal)
fn Parser::parse_string(self : Parser) -> String raise ParseError {
match self.next() {
Some('"') => self.parse_basic_string()
Some('\'') => self.parse_literal_string()
Some(ch) => raise ParseError::UnexpectedChar(self.pos - 1, ch)
None => raise ParseError::UnexpectedEof
}
}
///|
/// Parse a basic string (with escape sequences)
fn Parser::parse_basic_string(self : Parser) -> String raise ParseError {
let buf = @buffer.new()
while not(self.is_eof()) {
match self.next() {
Some('"') => return buf.to_string()
Some('\\') =>
match self.next() {
Some('t') => buf.write_char('\t')
Some('n') => buf.write_char('\n')
Some('r') => buf.write_char('\r')
Some('\\') => buf.write_char('\\')
Some('"') => buf.write_char('"')
Some(ch) => raise ParseError::InvalidEscape(self.pos - 1, ch)
None => raise ParseError::UnexpectedEof
}
Some(ch) => buf.write_char(ch)
None => raise ParseError::UnexpectedEof
}
}
raise ParseError::UnexpectedEof
}
///|
/// Parse a literal string (no escape sequences except '' for a single quote)
fn Parser::parse_literal_string(self : Parser) -> String raise ParseError {
let buf = @buffer.new()
while not(self.is_eof()) {
match self.next() {
Some('\'') =>
// Check for escaped single quote (two single quotes in a row)
if self.peek() == Some('\'') {
buf.write_char('\'')
self.pos += 1 // Skip the second quote
} else {
return buf.to_string()
}
Some(ch) => buf.write_char(ch)
None => raise ParseError::UnexpectedEof
}
}
raise ParseError::UnexpectedEof
}
///|
/// Parse a bare key (unquoted identifier)
fn Parser::parse_bare_key(self : Parser) -> String raise ParseError {
let buf = @buffer.new()
while not(self.is_eof()) {
match self.peek() {
Some(ch) if is_bare_key_char(ch) => {
buf.write_char(ch)
self.pos += 1
}
_ => break
}
}
let key = buf.to_string()
if key.is_empty() {
raise ParseError::InvalidKey(self.pos, "empty key")
}
key
}
///|
/// Check if character is valid in a bare key
fn is_bare_key_char(ch : Char) -> Bool {
match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' => true
_ => false
}
}
///|
/// Parse a key (bare, basic string, or literal string)
fn Parser::parse_key(self : Parser) -> String raise ParseError {
self.skip_whitespace()
match self.peek() {
Some('"') => {
self.pos += 1
self.parse_basic_string()
}
Some('\'') => {
self.pos += 1
self.parse_literal_string()
}
Some(ch) if is_bare_key_char(ch) => self.parse_bare_key()
Some(ch) => raise ParseError::UnexpectedChar(self.pos, ch)
None => raise ParseError::UnexpectedEof
}
}
///|
/// Parse a number (integer or float)
fn Parser::parse_number(self : Parser) -> TomlValue raise ParseError {
let buf = @buffer.new()
let start_pos = self.pos
let mut is_float = false
// Handle sign
match self.peek() {
Some('+' | '-') => buf.write_char(self.next().unwrap())
_ => ()
}
// Parse digits
while not(self.is_eof()) {
match self.peek() {
Some('0'..='9') => buf.write_char(self.next().unwrap())
Some('_') => self.pos += 1 // Skip underscores
Some('.') => {
is_float = true
buf.write_char(self.next().unwrap())
}
Some('e' | 'E') => {
is_float = true
buf.write_char(self.next().unwrap())
match self.peek() {
Some('+' | '-') => buf.write_char(self.next().unwrap())
_ => ()
}
}
_ => break
}
}
let num_str = buf.to_string()
if is_float {
let result = @strconv.parse_double(num_str) catch {
_ => raise ParseError::InvalidNumber(start_pos, num_str)
}
TomlValue::Float(result)
} else {
let result = @strconv.parse_int64(num_str) catch {
_ => raise ParseError::InvalidNumber(start_pos, num_str)
}
TomlValue::Integer(result)
}
}
///|
/// Parse a boolean value
fn Parser::parse_boolean(self : Parser) -> Bool raise ParseError {
let start_pos = self.pos
if self.try_consume("true") {
true
} else if self.try_consume("false") {
false
} else {
raise ParseError::InvalidKey(start_pos, "expected boolean")
}
}
///|
/// Try to consume a specific string
fn Parser::try_consume(self : Parser, s : String) -> Bool {
let start_pos = self.pos
for i = 0; i < s.length(); i = i + 1 {
match (self.peek(), s.get_char(i)) {
(Some(ch1), Some(ch2)) if ch1 == ch2 => self.pos += 1
_ => {
self.pos = start_pos
return false
}
}
}
true
}