///|
priv struct TextCursor {
src : String
mut i : Int
}
///|
fn cursor_eof(c : TextCursor) -> Bool {
c.i >= c.src.length()
}
///|
fn cursor_peek(c : TextCursor) -> Int {
if cursor_eof(c) {
-1
} else {
c.src[c.i].to_int()
}
}
///|
fn cursor_peek_at(c : TextCursor, n : Int) -> Int {
let j = c.i + n
if j >= c.src.length() {
-1
} else {
c.src[j].to_int()
}
}
///|
fn cursor_bump(c : TextCursor) -> Unit {
if !cursor_eof(c) {
c.i += 1
}
}
///|
fn skip_ws_comments(c : TextCursor) -> Result[Unit, IonError] {
let mut running = true
while running {
let ch = cursor_peek(c)
if ch == 32 || ch == 9 || ch == 10 || ch == 13 {
cursor_bump(c)
} else if ch == 47 && cursor_peek_at(c, 1) == 47 {
cursor_bump(c)
cursor_bump(c)
while !cursor_eof(c) && cursor_peek(c) != 10 {
cursor_bump(c)
}
} else if ch == 47 && cursor_peek_at(c, 1) == 42 {
cursor_bump(c)
cursor_bump(c)
let mut closed = false
while !cursor_eof(c) {
if cursor_peek(c) == 42 && cursor_peek_at(c, 1) == 47 {
cursor_bump(c)
cursor_bump(c)
closed = true
break
}
cursor_bump(c)
}
if !closed {
return Err(InvalidText("unterminated block comment"))
}
} else {
running = false
}
}
Ok(())
}
///|
fn read_ident(c : TextCursor) -> String {
let start = c.i
while is_ident_part(cursor_peek(c)) {
cursor_bump(c)
}
slice_text(c.src, start, c.i)
}
///|
fn read_operator(c : TextCursor) -> String {
let start = c.i
while is_operator_char(cursor_peek(c)) {
cursor_bump(c)
}
slice_text(c.src, start, c.i)
}
///|
fn read_quoted_symbol(c : TextCursor) -> Result[String, IonError] {
if cursor_peek(c) != 39 {
return Err(InvalidText("expected quoted symbol"))
}
cursor_bump(c)
let out = StringBuilder()
while !cursor_eof(c) {
let ch = cursor_peek(c)
if ch == 39 {
cursor_bump(c)
return Ok(out.to_string())
}
if ch == 92 {
cursor_bump(c)
match read_escape(c) {
Ok(v) => out.write_char(v)
Err(e) => return Err(e)
}
} else {
out.write_char(ch.unsafe_to_char())
cursor_bump(c)
}
}
Err(InvalidText("unterminated quoted symbol"))
}
///|
fn read_escape(c : TextCursor) -> Result[Char, IonError] {
let ch = cursor_peek(c)
if ch < 0 {
return Err(InvalidText("unterminated escape"))
}
cursor_bump(c)
if ch == 110 {
Ok('\n')
} else if ch == 114 {
Ok('\r')
} else if ch == 116 {
Ok('\t')
} else if ch == 34 || ch == 39 || ch == 92 || ch == 47 {
Ok(ch.unsafe_to_char())
} else if ch == 120 {
let h1 = nibble_value_int(cursor_peek(c))
cursor_bump(c)
let h2 = nibble_value_int(cursor_peek(c))
cursor_bump(c)
match (h1, h2) {
(Some(a), Some(b)) => Ok(((a << 4) | b).unsafe_to_char())
_ => Err(InvalidText("bad hex escape"))
}
} else {
Ok(ch.unsafe_to_char())
}
}
///|
fn read_quoted_string(c : TextCursor) -> Result[String, IonError] {
if cursor_peek(c) != 34 {
return Err(InvalidText("expected string"))
}
cursor_bump(c)
let out = StringBuilder()
while !cursor_eof(c) {
let ch = cursor_peek(c)
if ch == 34 {
cursor_bump(c)
return Ok(out.to_string())
}
if ch == 92 {
cursor_bump(c)
match read_escape(c) {
Ok(v) => out.write_char(v)
Err(e) => return Err(e)
}
} else {
out.write_char(ch.unsafe_to_char())
cursor_bump(c)
}
}
Err(InvalidText("unterminated string"))
}
///|
fn parse_blob_or_clob(c : TextCursor) -> Result[IonValue, IonError] {
if cursor_peek(c) != 123 || cursor_peek_at(c, 1) != 123 {
return Err(InvalidText("expected blob"))
}
cursor_bump(c)
cursor_bump(c)
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == 34 {
let s = match read_quoted_string(c) {
Ok(v) => v
Err(e) => return Err(e)
}
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) != 125 || cursor_peek_at(c, 1) != 125 {
return Err(InvalidText("unterminated clob"))
}
cursor_bump(c)
cursor_bump(c)
Ok(ion_clob(utf8_encode(s)))
} else {
let body = StringBuilder()
while !cursor_eof(c) &&
!(cursor_peek(c) == 125 && cursor_peek_at(c, 1) == 125) {
let ch = cursor_peek(c)
if ch != 32 && ch != 10 && ch != 13 && ch != 9 {
body.write_char(ch.unsafe_to_char())
}
cursor_bump(c)
}
if cursor_eof(c) {
return Err(InvalidText("unterminated blob"))
}
cursor_bump(c)
cursor_bump(c)
match base64_decode(body.to_string()) {
Ok(b) => Ok(ion_blob(b))
Err(e) => Err(e)
}
}
}
///|
fn parse_container(
c : TextCursor,
close : Int,
sexp : Bool,
) -> Result[Array[IonValue], IonError] {
let items : Array[IonValue] = []
let mut running = true
while running {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == close {
cursor_bump(c)
running = false
} else if cursor_eof(c) {
return Err(InvalidText("unterminated container"))
} else {
match parse_annotated_value(c, sexp) {
Ok(v) => items.push(v)
Err(e) => return Err(e)
}
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == 44 {
cursor_bump(c)
}
}
}
Ok(items)
}
///|
fn parse_struct_fields(c : TextCursor) -> Result[Array[IonField], IonError] {
let fields : Array[IonField] = []
let mut running = true
while running {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == 125 {
cursor_bump(c)
running = false
} else if cursor_eof(c) {
return Err(InvalidText("unterminated struct"))
} else {
let name = match parse_field_name(c) {
Ok(v) => v
Err(e) => return Err(e)
}
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) != 58 {
return Err(InvalidText("expected ':' in struct"))
}
cursor_bump(c)
match parse_annotated_value(c, false) {
Ok(v) => fields.push(ion_field(name, v))
Err(e) => return Err(e)
}
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == 44 {
cursor_bump(c)
}
}
}
Ok(fields)
}
///|
fn parse_field_name(c : TextCursor) -> Result[String, IonError] {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
let ch = cursor_peek(c)
if is_ident_start(ch) {
Ok(read_ident(c))
} else if ch == 39 {
read_quoted_symbol(c)
} else if ch == 34 {
read_quoted_string(c)
} else {
Err(InvalidText("invalid field name"))
}
}
///|
fn parse_null_or_typed(
c : TextCursor,
ident : String,
) -> Result[IonValue, IonError] {
if ident != "null" {
return Err(InvalidText(ident))
}
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == 46 {
cursor_bump(c)
let typ = read_ident(c)
let ion_t = match typ {
"null" => NullT
"bool" => BoolT
"int" => IntT
"float" => FloatT
"decimal" => DecimalT
"timestamp" => TimestampT
"symbol" => SymbolT
"string" => StringT
"clob" => ClobT
"blob" => BlobT
"list" => ListT
"sexp" => SexpT
"struct" => StructT
_ => return Err(InvalidText("null." + typ))
}
Ok(ion_null(ion_t))
} else {
Ok(ion_null(NullT))
}
}
///|
fn parse_number_or_timestamp(c : TextCursor) -> Result[IonValue, IonError] {
let start = c.i
if cursor_peek(c) == 45 || cursor_peek(c) == 43 {
cursor_bump(c)
}
if cursor_peek(c) == 105 &&
cursor_peek_at(c, 1) == 110 &&
cursor_peek_at(c, 2) == 102 {
cursor_bump(c)
cursor_bump(c)
cursor_bump(c)
let tok = slice_text(c.src, start, c.i)
if tok == "+inf" || tok == "inf" {
return Ok(ion_float(1.0 / 0.0))
} else if tok == "-inf" {
return Ok(ion_float(0.0 - 1.0 / 0.0))
}
}
if starts_with_at(c.src, c.i, "0x") || starts_with_at(c.src, c.i, "0X") {
c.i += 2
if cursor_peek(c) == 45 || cursor_peek(c) == 43 {
cursor_bump(c)
}
while is_hex_char(cursor_peek(c)) || cursor_peek(c) == 95 {
cursor_bump(c)
}
let tok = slice_text(c.src, start, c.i)
let digits = strip_underscores(slice_text(c.src, start + 2, c.i))
match bigint_from_radix_string(digits, 16) {
Ok(v) => Ok(ion_bigint(v))
Err(_) => Err(InvalidNumber(tok))
}
} else if starts_with_at(c.src, c.i, "0b") || starts_with_at(c.src, c.i, "0B") {
c.i += 2
while cursor_peek(c) == 48 || cursor_peek(c) == 49 || cursor_peek(c) == 95 {
cursor_bump(c)
}
let tok = slice_text(c.src, start, c.i)
let digits = strip_underscores(slice_text(c.src, start + 2, c.i))
match bigint_from_radix_string(digits, 2) {
Ok(v) => Ok(ion_bigint(v))
Err(_) => Err(InvalidNumber(tok))
}
} else {
while is_digit(cursor_peek(c)) || cursor_peek(c) == 95 {
cursor_bump(c)
}
if cursor_peek(c) == 45 && is_digit(cursor_peek_at(c, 1)) {
while !cursor_eof(c) &&
(
is_digit(cursor_peek(c)) ||
cursor_peek(c) == 45 ||
cursor_peek(c) == 84 ||
cursor_peek(c) == 116 ||
cursor_peek(c) == 58 ||
cursor_peek(c) == 46 ||
cursor_peek(c) == 43 ||
cursor_peek(c) == 90 ||
cursor_peek(c) == 122
) {
cursor_bump(c)
}
let tok = slice_text(c.src, start, c.i)
match parse_timestamp_text(tok) {
Ok(ts) => Ok(ion_timestamp(ts))
Err(e) => Err(e)
}
} else {
let mut seen_dot = false
let mut seen_exp = false
let mut seen_dec = false
if cursor_peek(c) == 46 {
seen_dot = true
cursor_bump(c)
while is_digit(cursor_peek(c)) || cursor_peek(c) == 95 {
cursor_bump(c)
}
}
let ch = cursor_peek(c)
if ch == 100 || ch == 68 {
seen_dec = true
cursor_bump(c)
if cursor_peek(c) == 43 || cursor_peek(c) == 45 {
cursor_bump(c)
}
while is_digit(cursor_peek(c)) || cursor_peek(c) == 95 {
cursor_bump(c)
}
} else if ch == 101 || ch == 69 {
seen_exp = true
cursor_bump(c)
if cursor_peek(c) == 43 || cursor_peek(c) == 45 {
cursor_bump(c)
}
while is_digit(cursor_peek(c)) || cursor_peek(c) == 95 {
cursor_bump(c)
}
}
let tok = strip_underscores(slice_text(c.src, start, c.i))
if seen_dec {
match parse_decimal_text(tok) {
Ok(d) => Ok(ion_decimal(d))
Err(e) => Err(e)
}
} else if seen_dot || seen_exp {
match parse_float_text(tok) {
Ok(v) => Ok(ion_float(v))
Err(e) => Err(e)
}
} else if tok.length() == 5 && tok[4].to_int() == 84 {
match parse_timestamp_text(tok) {
Ok(ts) => Ok(ion_timestamp(ts))
Err(e) => Err(e)
}
} else {
match bigint_from_decimal_string(tok) {
Ok(v) => Ok(ion_bigint(v))
Err(e) => Err(e)
}
}
}
}
}
///|
fn strip_underscores(text : String) -> String {
let out = StringBuilder()
let mut i = 0
while i < text.length() {
if text[i].to_int() != 95 {
out.write_char(text[i].to_int().unsafe_to_char())
}
i += 1
}
out.to_string()
}
///|
fn parse_float_text(text : String) -> Result[Double, IonError] {
if text == "nan" {
Ok(1.0 / 0.0 - 1.0 / 0.0)
} else if text == "+inf" || text == "inf" {
Ok(1.0 / 0.0)
} else if text == "-inf" {
Ok(0.0 - 1.0 / 0.0)
} else {
parse_float_manual(text)
}
}
///|
fn parse_float_manual(text : String) -> Result[Double, IonError] {
let mut i = 0
let mut sign = 1.0
if i < text.length() && text[i].to_int() == 45 {
sign = -1.0
i += 1
} else if i < text.length() && text[i].to_int() == 43 {
i += 1
}
let mut int_part = 0.0
let mut seen = false
while i < text.length() && is_digit(text[i].to_int()) {
seen = true
int_part = int_part * 10.0 + (text[i].to_int() - 48).to_double()
i += 1
}
let mut frac = 0.0
let mut scale = 1.0
if i < text.length() && text[i].to_int() == 46 {
i += 1
while i < text.length() && is_digit(text[i].to_int()) {
seen = true
scale = scale * 10.0
frac = frac * 10.0 + (text[i].to_int() - 48).to_double()
i += 1
}
}
let mut exp = 0
if i < text.length() && (text[i].to_int() == 101 || text[i].to_int() == 69) {
i += 1
match parse_signed_int(slice_text(text, i, text.length())) {
Ok(v) => {
exp = v
i = text.length()
}
Err(_) => return Err(InvalidNumber(text))
}
}
if !seen || i != text.length() {
return Err(InvalidNumber(text))
}
Ok(ldexp10(sign * (int_part + frac / scale), exp))
}
///|
fn ldexp10(value : Double, exp : Int) -> Double {
let mut v = value
let mut n = exp
while n > 0 {
v = v * 10.0
n -= 1
}
while n < 0 {
v = v / 10.0
n += 1
}
v
}
///|
fn parse_atom(c : TextCursor, sexp : Bool) -> Result[IonValue, IonError] {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
let ch = cursor_peek(c)
if ch < 0 {
Err(UnexpectedEof)
} else if ch == 91 {
cursor_bump(c)
match parse_container(c, 93, false) {
Ok(v) => Ok(ion_list(v))
Err(e) => Err(e)
}
} else if ch == 40 {
cursor_bump(c)
match parse_container(c, 41, true) {
Ok(v) => Ok(ion_sexp(v))
Err(e) => Err(e)
}
} else if ch == 123 && cursor_peek_at(c, 1) == 123 {
parse_blob_or_clob(c)
} else if ch == 123 {
cursor_bump(c)
match parse_struct_fields(c) {
Ok(v) => Ok(ion_struct(v))
Err(e) => Err(e)
}
} else if ch == 34 {
match read_quoted_string(c) {
Ok(v) => Ok(ion_string(v))
Err(e) => Err(e)
}
} else if ch == 39 {
match read_quoted_symbol(c) {
Ok(v) => Ok(ion_symbol(v))
Err(e) => Err(e)
}
} else if is_digit(ch) ||
(
(ch == 45 || ch == 43) &&
(is_digit(cursor_peek_at(c, 1)) || cursor_peek_at(c, 1) == 105)
) {
parse_number_or_timestamp(c)
} else if is_ident_start(ch) {
let ident = read_ident(c)
if ident == "true" {
Ok(ion_bool(true))
} else if ident == "false" {
Ok(ion_bool(false))
} else if ident == "null" {
parse_null_or_typed(c, ident)
} else if ident == "nan" {
Ok(ion_float(1.0 / 0.0 - 1.0 / 0.0))
} else if ident == "+inf" || ident == "inf" {
Ok(ion_float(1.0 / 0.0))
} else if ident == "-inf" {
Ok(ion_float(0.0 - 1.0 / 0.0))
} else {
Ok(ion_symbol(ident))
}
} else if sexp && is_operator_char(ch) {
Ok(ion_symbol(read_operator(c)))
} else {
Err(InvalidText("unexpected character"))
}
}
///|
fn parse_annotated_value(
c : TextCursor,
sexp : Bool,
) -> Result[IonValue, IonError] {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
let anns : Array[String] = []
let mut looping = true
while looping {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
let save = c.i
let name = if is_ident_start(cursor_peek(c)) {
Some(read_ident(c))
} else if cursor_peek(c) == 39 {
match read_quoted_symbol(c) {
Ok(v) => Some(v)
Err(e) => return Err(e)
}
} else {
None
}
match name {
Some(sym) => {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_peek(c) == 58 && cursor_peek_at(c, 1) == 58 {
if sym.length() == 0 {
return Err(InvalidAnnotation("empty"))
}
cursor_bump(c)
cursor_bump(c)
anns.push(sym)
} else {
c.i = save
looping = false
}
}
None => looping = false
}
}
match parse_atom(c, sexp) {
Ok(v) =>
if anns.length() == 0 {
Ok(v)
} else {
Ok({ annotations: anns, datum: v.datum, })
}
Err(e) => Err(e)
}
}
///|
pub fn loads_text(text : String) -> Result[Array[IonValue], IonError] {
let c : TextCursor = { src: text, i: 0, }
let values : Array[IonValue] = []
let mut first = true
let mut running = true
while running {
match skip_ws_comments(c) {
Err(e) => return Err(e)
Ok(_) => ()
}
if cursor_eof(c) {
running = false
} else {
match parse_annotated_value(c, false) {
Ok(v) => {
let is_version = match v.datum {
SymbolD(s) => s == "$ion_1_0"
_ => false
}
if first && is_version {
first = false
} else if v.is_symbol_table() {
first = false
} else {
first = false
values.push(v)
}
}
Err(e) => return Err(e)
}
}
}
Ok(values)
}
///|
pub fn load_text(text : String) -> Result[IonValue, IonError] {
match loads_text(text) {
Ok(vs) => if vs.length() == 0 { Err(EmptyDocument) } else { Ok(vs[0]) }
Err(e) => Err(e)
}
}