201 lines
4.2 KiB
V
201 lines
4.2 KiB
V
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module json2
|
|
|
|
// `Any` is a sum type that lists the possible types to be decoded and used.
|
|
pub type Any = Null | []Any | bool | f32 | f64 | i64 | int | map[string]Any | string |
|
|
u64
|
|
|
|
// `Null` struct is a simple representation of the `null` value in JSON.
|
|
pub struct Null {
|
|
is_null bool = true
|
|
}
|
|
|
|
struct Parser {
|
|
mut:
|
|
scanner &Scanner
|
|
p_tok Token
|
|
tok Token
|
|
n_tok Token
|
|
n_level int
|
|
convert_type bool = true
|
|
}
|
|
|
|
struct InvalidTokenError {
|
|
msg string
|
|
code int
|
|
}
|
|
|
|
struct UnknownTokenError {
|
|
msg string
|
|
code int
|
|
}
|
|
|
|
fn (mut p Parser) next() {
|
|
p.p_tok = p.tok
|
|
p.tok = p.n_tok
|
|
p.n_tok = p.scanner.scan()
|
|
}
|
|
|
|
fn (mut p Parser) next_with_err() ? {
|
|
p.next()
|
|
if p.tok.kind == .error {
|
|
return error(p.emit_error(p.tok.lit.bytestr()))
|
|
}
|
|
}
|
|
|
|
fn (p Parser) emit_error(msg string) string {
|
|
line := p.tok.line
|
|
column := p.tok.col + p.tok.lit.len
|
|
return '[x.json2] $msg ($line:$column)'
|
|
}
|
|
|
|
// TODO: copied from v.util to avoid the entire module and its functions
|
|
// from being imported. remove later once -skip-unused is enabled by default.
|
|
fn skip_bom(file_content string) string {
|
|
mut raw_text := file_content
|
|
// BOM check
|
|
if raw_text.len >= 3 {
|
|
unsafe {
|
|
c_text := raw_text.str
|
|
if c_text[0] == 0xEF && c_text[1] == 0xBB && c_text[2] == 0xBF {
|
|
// skip three BOM bytes
|
|
offset_from_begin := 3
|
|
raw_text = tos(c_text[offset_from_begin], vstrlen(c_text) - offset_from_begin)
|
|
}
|
|
}
|
|
}
|
|
return raw_text
|
|
}
|
|
|
|
fn new_parser(srce string, convert_type bool) Parser {
|
|
src := skip_bom(srce)
|
|
return Parser{
|
|
scanner: &Scanner{
|
|
text: src.bytes()
|
|
}
|
|
convert_type: convert_type
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) decode() ?Any {
|
|
p.next()
|
|
p.next_with_err() ?
|
|
fi := p.decode_value() ?
|
|
if p.tok.kind != .eof {
|
|
return IError(&InvalidTokenError{
|
|
msg: p.emit_error('invalid token `$p.tok.kind`')
|
|
})
|
|
}
|
|
return fi
|
|
}
|
|
|
|
fn (mut p Parser) decode_value() ?Any {
|
|
if p.n_level + 1 == 500 {
|
|
return error(p.emit_error('reached maximum nesting level of 500'))
|
|
}
|
|
match p.tok.kind {
|
|
.lsbr {
|
|
return p.decode_array()
|
|
}
|
|
.lcbr {
|
|
return p.decode_object()
|
|
}
|
|
.int_, .float {
|
|
tl := p.tok.lit.bytestr()
|
|
kind := p.tok.kind
|
|
p.next_with_err() ?
|
|
if p.convert_type {
|
|
if kind == .float {
|
|
return Any(tl.f64())
|
|
}
|
|
return Any(tl.i64())
|
|
}
|
|
return Any(tl)
|
|
}
|
|
.bool_ {
|
|
lit := p.tok.lit.bytestr()
|
|
p.next_with_err() ?
|
|
if p.convert_type {
|
|
return Any(lit.bool())
|
|
}
|
|
return Any(lit)
|
|
}
|
|
.null {
|
|
p.next_with_err() ?
|
|
if p.convert_type {
|
|
return Any(null)
|
|
}
|
|
return Any('null')
|
|
}
|
|
.str_ {
|
|
str := p.tok.lit.bytestr()
|
|
p.next_with_err() ?
|
|
return Any(str)
|
|
}
|
|
else {
|
|
return IError(&InvalidTokenError{
|
|
msg: p.emit_error('invalid token `$p.tok.kind`')
|
|
})
|
|
}
|
|
}
|
|
return Any(null)
|
|
}
|
|
|
|
fn (mut p Parser) decode_array() ?Any {
|
|
mut items := []Any{}
|
|
p.next_with_err() ?
|
|
p.n_level++
|
|
for p.tok.kind != .rsbr {
|
|
item := p.decode_value() ?
|
|
items << item
|
|
if p.tok.kind == .comma {
|
|
p.next_with_err() ?
|
|
if p.tok.kind == .rsbr || p.tok.kind == .rcbr {
|
|
return IError(&InvalidTokenError{
|
|
msg: p.emit_error('invalid token `$p.tok.lit')
|
|
})
|
|
}
|
|
} else if p.tok.kind == .rsbr {
|
|
break
|
|
} else {
|
|
return IError(&UnknownTokenError{
|
|
msg: p.emit_error("unknown token '$p.tok.lit' when decoding array.")
|
|
})
|
|
}
|
|
}
|
|
p.next_with_err() ?
|
|
p.n_level--
|
|
return Any(items)
|
|
}
|
|
|
|
fn (mut p Parser) decode_object() ?Any {
|
|
mut fields := map[string]Any{}
|
|
p.next_with_err() ?
|
|
p.n_level++
|
|
for p.tok.kind != .rcbr {
|
|
is_key := p.tok.kind == .str_ && p.n_tok.kind == .colon
|
|
if !is_key {
|
|
return IError(&InvalidTokenError{
|
|
msg: p.emit_error('invalid token `$p.tok.kind`, expecting `str_`')
|
|
})
|
|
}
|
|
cur_key := p.tok.lit.bytestr()
|
|
p.next_with_err() ?
|
|
p.next_with_err() ?
|
|
fields[cur_key] = p.decode_value() ?
|
|
if p.tok.kind == .comma {
|
|
p.next_with_err() ?
|
|
if p.tok.kind != .str_ {
|
|
return IError(&UnknownTokenError{
|
|
msg: p.emit_error("unknown token '$p.tok.lit' when decoding object.")
|
|
})
|
|
}
|
|
}
|
|
}
|
|
p.next_with_err() ?
|
|
p.n_level--
|
|
return Any(fields)
|
|
}
|