///|
priv struct TextCursor {
text : String
mut index : Int
line : Int
}
///|
fn TextCursor::new(text : String, line : Int) -> TextCursor {
{ text, index: 0, line }
}
///|
fn TextCursor::at_end(self : TextCursor) -> Bool {
self.index >= self.text.length()
}
///|
fn TextCursor::remaining(self : TextCursor) -> String {
self.text[self.index:].to_owned()
}
///|
fn TextCursor::peek(self : TextCursor) -> Int {
if self.at_end() {
-1
} else {
self.text[self.index].to_int()
}
}
///|
fn TextCursor::skip_spaces(self : TextCursor) -> Unit {
while !self.at_end() {
let code = self.peek()
if code == 32 || code == 9 || code == 13 {
self.index = self.index + 1
} else {
break
}
}
}
///|
fn TextCursor::consume_char(self : TextCursor, expected : Int) -> Bool {
if self.peek() == expected {
self.index = self.index + 1
true
} else {
false
}
}
///|
fn TextCursor::consume_text(self : TextCursor, expected : String) -> Bool {
if self.index + expected.length() > self.text.length() {
return false
}
let actual = self.text[self.index:self.index + expected.length()].to_owned()
if actual == expected {
self.index = self.index + expected.length()
true
} else {
false
}
}
///|
fn cursor_error(
cursor : TextCursor,
code : DbcErrorCode,
message : String,
) -> DbcError {
DbcError::new(
code,
message,
cursor.line,
cursor.index + 1,
context=cursor.remaining(),
)
}
///|
fn is_identifier_start(code : Int) -> Bool {
(code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code == 95
}
///|
fn is_identifier_continue(code : Int) -> Bool {
is_identifier_start(code) || (code >= 48 && code <= 57)
}
///|
fn TextCursor::identifier(self : TextCursor) -> Result[String, DbcError] {
self.skip_spaces()
let start = self.index
if !is_identifier_start(self.peek()) {
return Err(
cursor_error(self, InvalidIdentifier, "expected a DBC identifier"),
)
}
self.index = self.index + 1
while is_identifier_continue(self.peek()) {
self.index = self.index + 1
}
Ok(self.text[start:self.index].to_owned())
}
///|
fn TextCursor::integer(self : TextCursor) -> Result[Int, DbcError] {
self.skip_spaces()
let start = self.index
let mut negative = false
if self.consume_char(45) {
negative = true
} else {
ignore(self.consume_char(43))
}
let digit_start = self.index
let mut value = 0
let mut digits = 0
while self.peek() >= 48 && self.peek() <= 57 {
if digits >= 10 {
return Err(
cursor_error(
self,
InvalidInteger,
"integer exceeds the supported digit bound",
),
)
}
value = value * 10 + self.peek() - 48
self.index = self.index + 1
digits = digits + 1
}
if self.index == digit_start {
self.index = start
return Err(cursor_error(self, InvalidInteger, "expected a decimal integer"))
}
Ok(if negative { -value } else { value })
}
///|
fn power_of_ten(exponent : Int) -> Double {
let mut value = 1.0
if exponent >= 0 {
for _ in 0.. Result[Double, DbcError] {
self.skip_spaces()
let start = self.index
let mut negative = false
if self.consume_char(45) {
negative = true
} else {
ignore(self.consume_char(43))
}
let mut value = 0.0
let mut digits = 0
while self.peek() >= 48 && self.peek() <= 57 {
value = value * 10.0 + (self.peek() - 48).to_double()
self.index = self.index + 1
digits = digits + 1
}
if self.consume_char(46) {
let mut scale = 1.0
while self.peek() >= 48 && self.peek() <= 57 {
scale = scale * 10.0
value = value + (self.peek() - 48).to_double() / scale
self.index = self.index + 1
digits = digits + 1
}
}
if digits == 0 {
self.index = start
return Err(cursor_error(self, InvalidNumber, "expected a decimal number"))
}
if self.peek() == 69 || self.peek() == 101 {
self.index = self.index + 1
let exponent = match self.integer() {
Ok(value) => value
Err(_) =>
return Err(
cursor_error(self, InvalidNumber, "invalid numeric exponent"),
)
}
if exponent < -308 || exponent > 308 {
return Err(
cursor_error(self, InvalidNumber, "numeric exponent is out of range"),
)
}
value = value * power_of_ten(exponent)
}
Ok(if negative { -value } else { value })
}
///|
fn TextCursor::quoted(self : TextCursor) -> Result[String, DbcError] {
self.skip_spaces()
if !self.consume_char(34) {
return Err(cursor_error(self, InvalidSyntax, "expected a quoted string"))
}
let mut output = ""
while !self.at_end() {
let code = self.peek()
self.index = self.index + 1
if code == 34 {
return Ok(output)
}
if code == 92 {
if self.at_end() {
return Err(
cursor_error(self, InvalidSyntax, "quoted string ends with an escape"),
)
}
let escaped = self.peek()
self.index = self.index + 1
if escaped == 34 || escaped == 92 {
output = output + escaped.to_char().unwrap().to_string()
} else if escaped == 110 {
output = output + "\n"
} else if escaped == 114 {
output = output + "\r"
} else if escaped == 116 {
output = output + "\t"
} else {
return Err(
cursor_error(self, InvalidSyntax, "unsupported quoted-string escape"),
)
}
} else if code < 32 {
return Err(
cursor_error(
self,
InvalidSyntax,
"quoted string contains a control character",
),
)
} else {
output = output + code.to_char().unwrap().to_string()
}
}
Err(cursor_error(self, InvalidSyntax, "unterminated quoted string"))
}
///|
fn TextCursor::expect_char(
self : TextCursor,
expected : Int,
label : String,
) -> Result[Unit, DbcError] {
self.skip_spaces()
if self.consume_char(expected) {
Ok(())
} else {
Err(cursor_error(self, InvalidSyntax, "expected " + label))
}
}
///|
fn TextCursor::finish(
self : TextCursor,
semicolon? : Bool = false,
) -> Result[Unit, DbcError] {
self.skip_spaces()
if semicolon {
ignore(self.consume_char(59))
self.skip_spaces()
}
if self.at_end() {
Ok(())
} else {
Err(cursor_error(self, InvalidSyntax, "unexpected trailing content"))
}
}