module decoder2 // increment checks eof and increments checker by one @[inline] fn (mut checker Decoder) increment(message string) ! { if checker.checker_idx + 1 == checker.json.len { if message == '' { return Error{} } checker.checker_error('EOF: ' + message)! } checker.checker_idx++ } // skip_whitespace checks eof and increments checker until next non whitespace character @[inline] fn (mut checker Decoder) skip_whitespace(message string) ! { for checker.json[checker.checker_idx] in whitespace_chars { checker.increment(message)! } } // check_json_format checks if the JSON string is valid and updates the decoder state. fn (mut checker Decoder) check_json_format() ! { checker.skip_whitespace('empty json')! start_idx_position := checker.checker_idx mut actual_value_info_pointer := unsafe { &ValueInfo(nil) } match checker.json[checker.checker_idx] { `"` { checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .string }) actual_value_info_pointer = checker.values_info.last() checker.check_string()! } `-`, `0`...`9` { checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .number }) actual_value_info_pointer = checker.values_info.last() checker.check_number()! } `t`, `f` { checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .boolean }) actual_value_info_pointer = checker.values_info.last() checker.check_boolean()! } `n` { checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .null }) actual_value_info_pointer = checker.values_info.last() checker.check_null()! } `[` { checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .array }) actual_value_info_pointer = checker.values_info.last() checker.check_array()! } `{` { checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .object }) actual_value_info_pointer = checker.values_info.last() checker.check_object()! } else { checker.checker_error('unknown value kind')! } } actual_value_info_pointer.length = checker.checker_idx + 1 - start_idx_position checker.increment('') or { return } checker.skip_whitespace('') or { return } if checker.json[checker.checker_idx] !in [`,`, `:`, `}`, `]`] { checker.checker_error('invalid value. Unexpected character after ${actual_value_info_pointer.value_kind} end')! } } fn (mut checker Decoder) check_string() ! { checker.increment('string not closed')! // check if the JSON string is a valid escape sequence for checker.json[checker.checker_idx] != `"` { if checker.json[checker.checker_idx] == `\\` { checker.increment('invalid escape sequence')! escaped_char := checker.json[checker.checker_idx] match escaped_char { `/`, `b`, `f`, `n`, `r`, `t`, `"`, `\\` {} `u` { // check if the JSON string is a valid unicode escape sequence escaped_char_last_index := checker.checker_idx + 4 if escaped_char_last_index < checker.json.len { // 2 bytes for the unicode escape sequence `\u` checker.increment('invalid escape sequence')! for checker.checker_idx < escaped_char_last_index { match checker.json[checker.checker_idx] { `0`...`9`, `a`...`f`, `A`...`F` { checker.increment('invalid unicode escape sequence')! } else { return checker.checker_error('invalid unicode escape sequence') } } } continue } else { return checker.checker_error('short unicode escape sequence ${checker.json[checker.checker_idx - 1..checker.json.len - 1]}') } } else { return checker.checker_error('unknown escape sequence') } } } checker.increment('string not closed')! } } fn (mut checker Decoder) check_number() ! { // check if the JSON string is a valid float or integer if checker.json[checker.checker_idx] == `-` { checker.increment('expected digit')! } // integer part if checker.json[checker.checker_idx] == `0` { checker.increment('') or { return } } else if checker.json[checker.checker_idx] >= `1` && checker.json[checker.checker_idx] <= `9` { checker.increment('') or { return } for checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` { checker.increment('') or { return } } } else { return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}') } // fraction part if checker.json[checker.checker_idx] == `.` { checker.increment('expected digit')! if !(checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9`) { return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}') } for checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` { checker.increment('') or { return } } } // exponent part if checker.json[checker.checker_idx] == `e` || checker.json[checker.checker_idx] == `E` { checker.increment('expected digit')! if checker.json[checker.checker_idx] == `-` || checker.json[checker.checker_idx] == `+` { checker.increment('expected digit')! } if !(checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9`) { return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}') } for checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` { checker.increment('') or { return } } } checker.checker_idx-- } fn (mut checker Decoder) check_boolean() ! { // check if the JSON string is a valid boolean match checker.json[checker.checker_idx] { `t` { if checker.json.len - checker.checker_idx <= 3 { return checker.checker_error('EOF error: expecting `true`') } is_not_ok := unsafe { vmemcmp(checker.json.str + checker.checker_idx, true_in_string.str, true_in_string.len) } if is_not_ok != 0 { return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx.. checker.checker_idx + 4]}` instead of `true`') } checker.checker_idx += 3 } `f` { if checker.json.len - checker.checker_idx <= 4 { return checker.checker_error('EOF error: expecting `false`') } is_not_ok := unsafe { vmemcmp(checker.json.str + checker.checker_idx, false_in_string.str, false_in_string.len) } if is_not_ok != 0 { return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx.. checker.checker_idx + 5]}` instead of `false`') } checker.checker_idx += 4 } else { return checker.checker_error('invalid boolean') } } } fn (mut checker Decoder) check_null() ! { // check if the JSON string is a null value if checker.json.len - checker.checker_idx <= 3 { return checker.checker_error('EOF error: expecting `null`') } is_not_ok := unsafe { vmemcmp(checker.json.str + checker.checker_idx, null_in_string.str, null_in_string.len) } if is_not_ok != 0 { return checker.checker_error('invalid null value. Got `${checker.json[checker.checker_idx.. checker.checker_idx + 4]}` instead of `null`') } checker.checker_idx += 3 } fn (mut checker Decoder) check_array() ! { checker.increment('expected array end')! checker.skip_whitespace('expected array end')! for checker.json[checker.checker_idx] != `]` { checker.check_json_format()! checker.skip_whitespace('expected array end')! if checker.json[checker.checker_idx] == `,` { checker.increment('expected array value')! checker.skip_whitespace('') or {} if checker.json[checker.checker_idx] == `]` { return checker.checker_error('Cannot use `,`, before `]`') } } } } fn (mut checker Decoder) check_object() ! { checker.increment('expected object end')! checker.skip_whitespace('expected object end')! for checker.json[checker.checker_idx] != `}` { if checker.json[checker.checker_idx] != `"` { checker.checker_error('Expecting object key')! } checker.check_json_format()! checker.skip_whitespace('expected `:`')! if checker.json[checker.checker_idx] != `:` { checker.checker_error('expected `:`, got `${checker.json[checker.checker_idx].ascii_str()}`')! } checker.increment('expected object value')! checker.skip_whitespace('expected object value')! checker.check_json_format()! checker.skip_whitespace('expected object end')! if checker.json[checker.checker_idx] == `,` { checker.increment('expected object key')! checker.skip_whitespace('') or {} if checker.json[checker.checker_idx] == `}` { return checker.checker_error('Cannot use `,`, before `}`') } } } }