///|
pub struct ParseError {
message : String
line : Int
}
///|
pub impl Show for ParseError with fn to_string(self) {
"ParseError(line \{self.line}): \{self.message}"
}
///|
priv struct Parser {
source : String
mut pos : Int
mut line : Int
}
///|
fn Parser::new(source : String) -> Parser {
{ source, pos: 0, line: 1 }
}
///|
fn Parser::is_eof(self : Parser) -> Bool {
self.pos >= self.source.length()
}
///|
fn Parser::peek(self : Parser) -> Char? {
if self.is_eof() {
None
} else {
Some(Int::unsafe_to_char(self.source.code_unit_at(self.pos).to_int()))
}
}
///|
fn Parser::read_char(self : Parser) -> Char? {
if self.is_eof() {
None
} else {
let ch = Int::unsafe_to_char(self.source.code_unit_at(self.pos).to_int())
self.pos += 1
Some(ch)
}
}
///|
fn Parser::skip_whitespace(self : Parser) -> Unit {
while !self.is_eof() {
match self.peek() {
Some(' ') | Some('\t') => self.pos += 1
_ => break
}
}
}
///|
fn Parser::skip_to_eol(self : Parser) -> Unit {
while !self.is_eof() {
match self.peek() {
Some('\n') | Some('\r') => break
_ => self.pos += 1
}
}
}
///|
fn Parser::skip_eol(self : Parser) -> Unit {
match self.peek() {
Some('\r') => {
self.pos += 1
self.line += 1
match self.peek() {
Some('\n') => self.pos += 1
_ => ()
}
}
Some('\n') => {
self.pos += 1
self.line += 1
}
_ => ()
}
}
///|
fn try_skip_export(p : Parser) -> Unit {
let src = p.source
let pos = p.pos
if pos + 6 <= src.length() {
let is_export = src.code_unit_at(pos).to_int() == 'e' &&
src.code_unit_at(pos + 1).to_int() == 'x' &&
src.code_unit_at(pos + 2).to_int() == 'p' &&
src.code_unit_at(pos + 3).to_int() == 'o' &&
src.code_unit_at(pos + 4).to_int() == 'r' &&
src.code_unit_at(pos + 5).to_int() == 't'
if is_export {
let after = if pos + 6 < src.length() {
Int::unsafe_to_char(src.code_unit_at(pos + 6).to_int())
} else {
'\n'
}
if after == ' ' || after == '\t' || after == '=' {
p.pos = pos + 6
p.skip_whitespace()
}
}
}
}
///|
fn is_key_char(ch : Char) -> Bool {
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
ch == '_' ||
ch == '.' ||
ch == '-'
}
///|
fn parse_key(p : Parser) -> String {
let start = p.pos
while !p.is_eof() {
match p.peek() {
Some(ch) if is_key_char(ch) => p.pos += 1
_ => break
}
}
if p.pos == start {
return ""
}
p.source[start:p.pos].to_owned()
}
///|
fn parse_value(p : Parser) -> String {
p.skip_whitespace()
if p.is_eof() {
return ""
}
match p.peek() {
Some('\n') | Some('\r') => {
p.skip_eol()
return ""
}
Some('\'') => {
p.pos += 1
return parse_single_quoted(p)
}
Some('"') => {
p.pos += 1
return parse_double_quoted(p)
}
Some('`') => {
p.pos += 1
return parse_backtick_quoted(p)
}
_ => ()
}
// Unquoted value: read until comment or end of line, trim trailing whitespace
let start = p.pos
let mut end = p.pos
while !p.is_eof() {
match p.peek() {
Some('\n') | Some('\r') => break
Some('#') => break
_ => {
p.pos += 1
end = p.pos
}
}
}
// Trim trailing whitespace
while end > start &&
Int::unsafe_to_char(p.source.code_unit_at(end - 1).to_int()) == ' ' {
end -= 1
}
p.skip_eol()
p.source[start:end].to_owned()
}
///|
fn try_parse_value(p : Parser) -> Result[String, ParseError] {
p.skip_whitespace()
if p.is_eof() {
return Ok("")
}
match p.peek() {
Some('\n') | Some('\r') => {
p.skip_eol()
return Ok("")
}
Some('\'') => {
p.pos += 1
return try_parse_single_quoted(p)
}
Some('"') => {
p.pos += 1
return try_parse_double_quoted(p)
}
Some('`') => {
p.pos += 1
return try_parse_backtick_quoted(p)
}
_ => ()
}
let start = p.pos
let mut end = p.pos
while !p.is_eof() {
match p.peek() {
Some('\n') | Some('\r') => break
Some('#') => break
_ => {
p.pos += 1
end = p.pos
}
}
}
while end > start &&
Int::unsafe_to_char(p.source.code_unit_at(end - 1).to_int()) == ' ' {
end -= 1
}
p.skip_eol()
Ok(p.source[start:end].to_owned())
}
///|
fn parse_single_quoted(p : Parser) -> String {
let buf = StringBuilder::new()
while !p.is_eof() {
match p.read_char() {
Some('\'') => break
Some('\n') => buf.write_char('\n')
Some(ch) => buf.write_char(ch)
None => break
}
}
p.skip_to_eol()
p.skip_eol()
buf.to_string()
}
///|
fn try_parse_single_quoted(p : Parser) -> Result[String, ParseError] {
let buf = StringBuilder::new()
while !p.is_eof() {
match p.read_char() {
Some('\'') => {
p.skip_to_eol()
p.skip_eol()
return Ok(buf.to_string())
}
Some('\n') => buf.write_char('\n')
Some(ch) => buf.write_char(ch)
None => return Err({ message: "unclosed single quote", line: p.line })
}
}
Err({ message: "unclosed single quote", line: p.line })
}
///|
fn parse_double_quoted(p : Parser) -> String {
let buf = StringBuilder::new()
while !p.is_eof() {
match p.peek() {
Some('"') => {
p.pos += 1
break
}
Some('\\') =>
if p.pos + 1 < p.source.length() {
let next = Int::unsafe_to_char(
p.source.code_unit_at(p.pos + 1).to_int(),
)
if next == 'n' {
buf.write_char('\n')
p.pos += 2
} else if next == 'r' {
buf.write_char('\r')
p.pos += 2
} else if next == 't' {
buf.write_char('\t')
p.pos += 2
} else if next == '\\' {
buf.write_char('\\')
p.pos += 2
} else {
buf.write_char('\\')
p.pos += 1
}
} else {
buf.write_char('\\')
p.pos += 1
}
Some('\n') => {
buf.write_char('\n')
p.pos += 1
}
Some(ch) => {
buf.write_char(ch)
p.pos += 1
}
None => break
}
}
p.skip_to_eol()
p.skip_eol()
buf.to_string()
}
///|
fn try_parse_double_quoted(p : Parser) -> Result[String, ParseError] {
let buf = StringBuilder::new()
while !p.is_eof() {
match p.peek() {
Some('"') => {
p.pos += 1
p.skip_to_eol()
p.skip_eol()
return Ok(buf.to_string())
}
Some('\\') =>
if p.pos + 1 < p.source.length() {
let next = Int::unsafe_to_char(
p.source.code_unit_at(p.pos + 1).to_int(),
)
if next == 'n' {
buf.write_char('\n')
p.pos += 2
} else if next == 'r' {
buf.write_char('\r')
p.pos += 2
} else if next == 't' {
buf.write_char('\t')
p.pos += 2
} else if next == '\\' {
buf.write_char('\\')
p.pos += 2
} else {
buf.write_char('\\')
p.pos += 1
}
} else {
buf.write_char('\\')
p.pos += 1
}
Some('\n') => {
buf.write_char('\n')
p.pos += 1
}
Some(ch) => {
buf.write_char(ch)
p.pos += 1
}
None => return Err({ message: "unclosed double quote", line: p.line })
}
}
Err({ message: "unclosed double quote", line: p.line })
}
///|
fn parse_backtick_quoted(p : Parser) -> String {
let buf = StringBuilder::new()
while !p.is_eof() {
match p.read_char() {
Some('`') => break
Some('\n') => buf.write_char('\n')
Some(ch) => buf.write_char(ch)
None => break
}
}
p.skip_to_eol()
p.skip_eol()
buf.to_string()
}
///|
fn try_parse_backtick_quoted(p : Parser) -> Result[String, ParseError] {
let buf = StringBuilder::new()
while !p.is_eof() {
match p.read_char() {
Some('`') => {
p.skip_to_eol()
p.skip_eol()
return Ok(buf.to_string())
}
Some('\n') => buf.write_char('\n')
Some(ch) => buf.write_char(ch)
None => return Err({ message: "unclosed backtick", line: p.line })
}
}
Err({ message: "unclosed backtick", line: p.line })
}