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)
 | |
| }
 |