274 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			V
		
	
	
// Copyright (c) 2019-2022 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
 | 
						|
}
 | 
						|
 | 
						|
pub enum ValueKind {
 | 
						|
	unknown
 | 
						|
	array
 | 
						|
	object
 | 
						|
	string_
 | 
						|
	number
 | 
						|
}
 | 
						|
 | 
						|
// str returns the string representation of the specific ValueKind
 | 
						|
pub fn (k ValueKind) str() string {
 | 
						|
	return match k {
 | 
						|
		.unknown { 'unknown' }
 | 
						|
		.array { 'array' }
 | 
						|
		.object { 'object' }
 | 
						|
		.string_ { 'string' }
 | 
						|
		.number { 'number' }
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
fn format_message(msg string, line int, column int) string {
 | 
						|
	return '[x.json2] $msg ($line:$column)'
 | 
						|
}
 | 
						|
 | 
						|
pub struct DecodeError {
 | 
						|
	line    int
 | 
						|
	column  int
 | 
						|
	message string
 | 
						|
}
 | 
						|
 | 
						|
// code returns the error code of DecodeError
 | 
						|
pub fn (err DecodeError) code() int {
 | 
						|
	return 3
 | 
						|
}
 | 
						|
 | 
						|
// msg returns the message of the DecodeError
 | 
						|
pub fn (err DecodeError) msg() string {
 | 
						|
	return format_message(err.message, err.line, err.column)
 | 
						|
}
 | 
						|
 | 
						|
pub struct InvalidTokenError {
 | 
						|
	DecodeError
 | 
						|
	token    Token
 | 
						|
	expected TokenKind
 | 
						|
}
 | 
						|
 | 
						|
// code returns the error code of the InvalidTokenError
 | 
						|
pub fn (err InvalidTokenError) code() int {
 | 
						|
	return 2
 | 
						|
}
 | 
						|
 | 
						|
// msg returns the message of the InvalidTokenError
 | 
						|
pub fn (err InvalidTokenError) msg() string {
 | 
						|
	footer_text := if err.expected != .none_ { ', expecting `$err.expected`' } else { '' }
 | 
						|
	return format_message('invalid token `$err.token.kind`$footer_text', err.token.line,
 | 
						|
		err.token.full_col())
 | 
						|
}
 | 
						|
 | 
						|
pub struct UnknownTokenError {
 | 
						|
	DecodeError
 | 
						|
	token Token
 | 
						|
	kind  ValueKind = .unknown
 | 
						|
}
 | 
						|
 | 
						|
// code returns the error code of the UnknownTokenError
 | 
						|
pub fn (err UnknownTokenError) code() int {
 | 
						|
	return 1
 | 
						|
}
 | 
						|
 | 
						|
// msg returns the error message of the UnknownTokenError
 | 
						|
pub fn (err UnknownTokenError) msg() string {
 | 
						|
	return format_message("unknown token '$err.token.lit' when decoding ${err.kind}.",
 | 
						|
		err.token.line, err.token.full_col())
 | 
						|
}
 | 
						|
 | 
						|
struct Parser {
 | 
						|
mut:
 | 
						|
	scanner      &Scanner
 | 
						|
	p_tok        Token
 | 
						|
	tok          Token
 | 
						|
	n_tok        Token
 | 
						|
	n_level      int
 | 
						|
	convert_type bool = true
 | 
						|
}
 | 
						|
 | 
						|
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 IError(DecodeError{
 | 
						|
			line: p.tok.line
 | 
						|
			column: p.tok.full_col()
 | 
						|
			message: p.tok.lit.bytestr()
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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{
 | 
						|
			token: p.tok
 | 
						|
		})
 | 
						|
	}
 | 
						|
	return fi
 | 
						|
}
 | 
						|
 | 
						|
fn (mut p Parser) decode_value() ?Any {
 | 
						|
	if p.n_level + 1 == 500 {
 | 
						|
		return IError(DecodeError{
 | 
						|
			message: '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 !nofloat ? {
 | 
						|
					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{
 | 
						|
				token: p.tok
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return Any(null)
 | 
						|
}
 | 
						|
 | 
						|
[manualfree]
 | 
						|
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 {
 | 
						|
				return IError(InvalidTokenError{
 | 
						|
					token: p.tok
 | 
						|
				})
 | 
						|
			}
 | 
						|
		} else if p.tok.kind != .rsbr {
 | 
						|
			return IError(UnknownTokenError{
 | 
						|
				token: p.tok
 | 
						|
				kind: .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 {
 | 
						|
		if p.tok.kind != .str_ {
 | 
						|
			return IError(InvalidTokenError{
 | 
						|
				token: p.tok
 | 
						|
				expected: .str_
 | 
						|
			})
 | 
						|
		}
 | 
						|
 | 
						|
		cur_key := p.tok.lit.bytestr()
 | 
						|
		p.next_with_err() ?
 | 
						|
		if p.tok.kind != .colon {
 | 
						|
			return IError(InvalidTokenError{
 | 
						|
				token: p.tok
 | 
						|
				expected: .colon
 | 
						|
			})
 | 
						|
		}
 | 
						|
 | 
						|
		p.next_with_err() ?
 | 
						|
		fields[cur_key] = p.decode_value() ?
 | 
						|
		if p.tok.kind != .comma && p.tok.kind != .rcbr {
 | 
						|
			return IError(UnknownTokenError{
 | 
						|
				token: p.tok
 | 
						|
				kind: .object
 | 
						|
			})
 | 
						|
		} else if p.tok.kind == .comma {
 | 
						|
			p.next_with_err() ?
 | 
						|
		}
 | 
						|
	}
 | 
						|
	p.next_with_err() ?
 | 
						|
	p.n_level--
 | 
						|
	return Any(fields)
 | 
						|
}
 |