X-Git-Url: http://nitlanguage.org diff --git a/lib/json/static.nit b/lib/json/static.nit index f5eef1d..0d25f10 100644 --- a/lib/json/static.nit +++ b/lib/json/static.nit @@ -22,17 +22,10 @@ # This object can then be type checked as usual with `isa` and `as`. module static -import error -private import json_parser -private import json_lexer - -# Something that can be translated to JSON. -interface Jsonable - super Serializable -end +import parser_base +intrude import error redef class Text - super Jsonable # Removes JSON-escaping if necessary in a JSON string # @@ -48,14 +41,14 @@ redef class Text # # assert not "string".json_need_escape # assert "\\\"string\\\"".json_need_escape - protected fun json_need_escape: Bool do return has('\\') + private fun json_need_escape: Bool do return has('\\') # Escapes `self` from a JSON string to a Nit string # # assert "\\\"string\\\"".json_to_nit_string == "\"string\"" # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n" # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓" - protected fun json_to_nit_string: String do + private fun json_to_nit_string: String do var res = new FlatBuffer.with_capacity(byte_length) var i = 0 var ln = self.length @@ -123,23 +116,12 @@ redef class Text # assert str isa String # assert str == "foo, bar, baz" # - # Example of a syntaxic error: + # Example of a syntax error: # - # var bad = "\{foo: \"bar\"\}".parse_json - # assert bad isa JsonParseError - # assert bad.position.col_start == 2 - fun parse_json: nullable Jsonable do - var lexer = new Lexer_json(to_s) - var parser = new Parser_json - var tokens = lexer.lex - parser.tokens.add_all(tokens) - var root_node = parser.parse - if root_node isa NStart then - return root_node.n_0.to_nit_object - else if root_node isa NError then - return new JsonParseError(root_node.message, root_node.position) - else abort - end + # var error = "\{foo: \"bar\"\}".parse_json + # assert error isa JsonParseError + # assert error.to_s == "Bad key format Error: bad JSON entity" + fun parse_json: nullable Serializable do return (new JSONStringParser(self.to_s)).parse_entity end redef class FlatText @@ -152,153 +134,347 @@ redef class FlatText end end -redef class Int - super Jsonable -end - -redef class Float - super Jsonable -end - -redef class Bool - super Jsonable -end - -# A map that can be translated into a JSON object. -interface JsonMapRead[K: String, V: nullable Jsonable] - super MapRead[K, V] - super Jsonable -end - -# A JSON Object. -class JsonObject - super JsonMapRead[String, nullable Jsonable] - super HashMap[String, nullable Jsonable] -end - -# A sequence that can be translated into a JSON array. -class JsonSequenceRead[E: nullable Jsonable] - super Jsonable - super SequenceRead[E] -end - -# A JSON array. -class JsonArray - super JsonSequenceRead[nullable Jsonable] - super Array[nullable Jsonable] -end - -redef class JsonParseError - super Jsonable -end - -redef class Position - super Jsonable -end - -################################################################################ -# Redef parser - -redef class Nvalue - # The represented value. - private fun to_nit_object: nullable Jsonable is abstract -end - -redef class Nvalue_number - redef fun to_nit_object - do - var text = n_number.text - if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f - return text.to_i +redef class Char + # Is `self` a valid number start ? + private fun is_json_num_start: Bool do + if self == '-' then return true + if self.is_numeric then return true + return false end -end - -redef class Nvalue_string - redef fun to_nit_object do return n_string.to_nit_string -end -redef class Nvalue_true - redef fun to_nit_object do return true + # Is `self` a valid JSON separator ? + private fun is_json_separator: Bool do + if self == ':' then return true + if self == ',' then return true + if self == '{' then return true + if self == '}' then return true + if self == '[' then return true + if self == ']' then return true + if self == '"' then return true + if self.is_whitespace then return true + return false + end end -redef class Nvalue_false - redef fun to_nit_object do return false -end +# A simple ad-hoc JSON parser +# +# To parse a simple JSON document, read it as a String and give it to `parse_entity` +# NOTE: if your document contains several non-nested entities, use `parse_entity` for each +# JSON entity to parse +class JSONStringParser + super StringProcessor -redef class Nvalue_null - redef fun to_nit_object do return null -end + # Parses a JSON Entity + # + # ~~~nit + # var p = new JSONStringParser("""{"numbers": [1,23,3], "string": "string"}""") + # assert p.parse_entity isa JsonObject + # ~~~ + fun parse_entity: nullable Serializable do + var srclen = len + ignore_whitespaces + if pos >= srclen then return make_parse_error("Empty JSON") + var c = src[pos] + if c == '[' then + pos += 1 + return parse_json_array + else if c == '"' then + var s = parse_json_string + return s + else if c == '{' then + pos += 1 + return parse_json_object + else if c == 'f' then + if pos + 4 >= srclen then make_parse_error("Error: bad JSON entity") + if src[pos + 1] == 'a' and src[pos + 2] == 'l' and src[pos + 3] == 's' and src[pos + 4] == 'e' then + pos += 5 + return false + end + return make_parse_error("Error: bad JSON entity") + else if c == 't' then + if pos + 3 >= srclen then make_parse_error("Error: bad JSON entity") + if src[pos + 1] == 'r' and src[pos + 2] == 'u' and src[pos + 3] == 'e' then + pos += 4 + return true + end + return make_parse_error("Error: bad JSON entity") + else if c == 'n' then + if pos + 3 >= srclen then make_parse_error("Error: bad JSON entity") + if src[pos + 1] == 'u' and src[pos + 2] == 'l' and src[pos + 3] == 'l' then + pos += 4 + return null + end + return make_parse_error("Error: bad JSON entity") + end + if not c.is_json_num_start then return make_parse_error("Bad JSON character") + return parse_json_number + end -redef class Nstring - # The represented string. - private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s -end + # Parses a JSON Array + fun parse_json_array: Serializable do + var max = len + if pos >= max then return make_parse_error("Incomplete JSON array") + var arr = new JsonArray + var c = src[pos] + while not c == ']' do + ignore_whitespaces + if pos >= max then return make_parse_error("Incomplete JSON array") + if src[pos] == ']' then break + var ent = parse_entity + #print "Parsed an entity {ent} for a JSON array" + if ent isa JsonParseError then return ent + arr.add ent + ignore_whitespaces + if pos >= max then return make_parse_error("Incomplete JSON array") + c = src[pos] + if c == ']' then break + if c != ',' then return make_parse_error("Bad array separator {c}") + pos += 1 + end + pos += 1 + return arr + end -redef class Nvalue_object - redef fun to_nit_object do + # Parses a JSON Object + fun parse_json_object: Serializable do + var max = len + if pos >= max then return make_parse_error("Incomplete JSON object") var obj = new JsonObject - var members = n_members - if members != null then - var pairs = members.pairs - for pair in pairs do obj[pair.name] = pair.value + var c = src[pos] + while not c == '}' do + ignore_whitespaces + if pos >= max then return make_parse_error("Malformed JSON object") + if src[pos] == '}' then break + var key = parse_entity + #print "Parsed key {key} for JSON object" + if not key isa String then return make_parse_error("Bad key format {key or else "null"}") + ignore_whitespaces + if pos >= max then return make_parse_error("Incomplete JSON object") + if not src[pos] == ':' then return make_parse_error("Bad key/value separator {src[pos]}") + pos += 1 + ignore_whitespaces + var value = parse_entity + #print "Parsed value {value} for JSON object" + if value isa JsonParseError then return value + obj[key] = value + ignore_whitespaces + if pos >= max then return make_parse_error("Incomplete JSON object") + c = src[pos] + if c == '}' then break + if c != ',' then return make_parse_error("Bad object separator {src[pos]}") + pos += 1 end + pos += 1 return obj end -end -redef class Nmembers - # All the key-value pairs. - private fun pairs: Array[Npair] is abstract -end - -redef class Nmembers_tail - redef fun pairs - do - var arr = n_members.pairs - arr.add n_pair - return arr + # Creates a `JsonParseError` with the right message and location + protected fun make_parse_error(message: String): JsonParseError do + var err = new JsonParseError(message) + err.location = hot_location + return err end -end - -redef class Nmembers_head - redef fun pairs do return [n_pair] -end -redef class Npair - # The represented key. - private fun name: String do return n_string.to_nit_string + # Parses an Int or Float + fun parse_json_number: Serializable do + var max = len + var p = pos + var c = src[p] + var is_neg = false + if c == '-' then + is_neg = true + p += 1 + if p >= max then return make_parse_error("Bad JSON number") + c = src[p] + end + var val = 0 + while c.is_numeric do + val *= 10 + val += c.to_i + p += 1 + if p >= max then break + c = src[p] + end + if c == '.' then + p += 1 + if p >= max then return make_parse_error("Bad JSON number") + c = src[p] + var fl = val.to_f + var frac = 0.1 + while c.is_numeric do + fl += c.to_i.to_f * frac + frac /= 10.0 + p += 1 + if p >= max then break + c = src[p] + end + if c == 'e' or c == 'E' then + p += 1 + var exp = 0 + if p >= max then return make_parse_error("Malformed JSON number") + c = src[p] + while c.is_numeric do + exp *= 10 + exp += c.to_i + p += 1 + if p >= max then break + c = src[p] + end + fl *= (10 ** exp).to_f + end + if p < max and not c.is_json_separator then return make_parse_error("Malformed JSON number") + pos = p + if is_neg then return -fl + return fl + end + if c == 'e' or c == 'E' then + p += 1 + if p >= max then return make_parse_error("Bad JSON number") + var exp = src[p].to_i + c = src[p] + while c.is_numeric do + exp *= 10 + exp += c.to_i + p += 1 + if p >= max then break + c = src[p] + end + val *= (10 ** exp) + end + if p < max and not src[p].is_json_separator then return make_parse_error("Malformed JSON number") + pos = p + if is_neg then return -val + return val + end - # The represented value. - private fun value: nullable Jsonable do return n_value.to_nit_object -end + private var parse_str_buf = new FlatBuffer + + # Parses and returns a Nit string from a JSON String + fun parse_json_string: Serializable do + var src = src + var ln = src.length + var p = pos + p += 1 + if p > ln then return make_parse_error("Malformed JSON String") + var c = src[p] + var ret = parse_str_buf + var chunk_st = p + while c != '"' do + if c != '\\' then + p += 1 + if p >= ln then return make_parse_error("Malformed JSON string") + c = src[p] + continue + end + ret.append_substring_impl(src, chunk_st, p - chunk_st) + p += 1 + if p >= ln then return make_parse_error("Malformed Escape sequence in JSON string") + c = src[p] + if c == 'r' then + ret.add '\r' + p += 1 + else if c == 'n' then + ret.add '\n' + p += 1 + else if c == 't' then + ret.add '\t' + p += 1 + else if c == 'u' then + var cp = 0 + p += 1 + for i in [0 .. 4[ do + cp <<= 4 + if p >= ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + c = src[p] + if c >= '0' and c <= '9' then + cp += c.code_point - '0'.code_point + else if c >= 'a' and c <= 'f' then + cp += c.code_point - 'a'.code_point + 10 + else if c >= 'A' and c <= 'F' then + cp += c.code_point - 'A'.code_point + 10 + else + make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + end + p += 1 + end + c = cp.code_point + if cp >= 0xD800 and cp <= 0xDBFF then + if p >= ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + c = src[p] + if c != '\\' then make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + p += 1 + c = src[p] + if c != 'u' then make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + var locp = 0 + p += 1 + for i in [0 .. 4[ do + locp <<= 4 + if p > ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + c = src[p] + if c >= '0' and c <= '9' then + locp += c.code_point - '0'.code_point + else if c >= 'a' and c <= 'f' then + locp += c.code_point - 'a'.code_point + 10 + else if c >= 'A' and c <= 'F' then + locp += c.code_point - 'A'.code_point + 10 + else + make_parse_error("Malformed \uXXXX Escape sequence in JSON string") + end + p += 1 + end + c = (((locp & 0x3FF) | ((cp & 0x3FF) << 10)) + 0x10000).code_point + end + ret.add c + else if c == 'b' then + ret.add 8.code_point + p += 1 + else if c == 'f' then + ret.add '\f' + p += 1 + else + p += 1 + ret.add c + end + chunk_st = p + c = src[p] + end + pos = p + 1 + if ret.is_empty then return src.substring(chunk_st, p - chunk_st) + ret.append_substring_impl(src, chunk_st, p - chunk_st) + var rets = ret.to_s + ret.clear + return rets + end -redef class Nvalue_array - redef fun to_nit_object - do - var arr = new JsonArray - var elements = n_elements - if elements != null then - var items = elements.items - for item in items do arr.add(item.to_nit_object) + # Ignores any character until a JSON separator is encountered + fun ignore_until_separator do + var max = len + while pos < max do + if not src[pos].is_json_separator then return end - return arr end end -redef class Nelements - # All the items. - private fun items: Array[Nvalue] is abstract +# A map that can be translated into a JSON object. +interface JsonMapRead[K: String, V: nullable Serializable] + super MapRead[K, V] + super Serializable end -redef class Nelements_tail - redef fun items - do - var items = n_elements.items - items.add(n_value) - return items - end +# A JSON Object. +class JsonObject + super JsonMapRead[String, nullable Serializable] + super HashMap[String, nullable Serializable] end -redef class Nelements_head - redef fun items do return [n_value] +# A sequence that can be translated into a JSON array. +class JsonSequenceRead[E: nullable Serializable] + super Serializable + super SequenceRead[E] +end + +# A JSON array. +class JsonArray + super JsonSequenceRead[nullable Serializable] + super Array[nullable Serializable] end