200 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			200 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)
 | 
						|
}
 |