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

Introduced properties

fun ignore_until_separator

json :: JSONStringParser :: ignore_until_separator

Ignores any character until a JSON separator is encountered
protected fun make_parse_error(message: String): JsonParseError

json :: JSONStringParser :: make_parse_error

Creates a JsonParseError with the right message and location
fun parse_entity: nullable Serializable

json :: JSONStringParser :: parse_entity

Parses a JSON Entity
fun parse_json_string: Serializable

json :: JSONStringParser :: parse_json_string

Parses and returns a Nit string from a JSON String

Redefined properties

redef type SELF: JSONStringParser

json $ JSONStringParser :: SELF

Type of this instance, automatically specialized in every class

All properties

fun !=(other: nullable Object): Bool

core :: Object :: !=

Have self and other different values?
fun ==(other: nullable Object): Bool

core :: Object :: ==

Have self and other the same value?
type CLASS: Class[SELF]

core :: Object :: CLASS

The type of the class of self.
type SELF: Object

core :: Object :: SELF

Type of this instance, automatically specialized in every class
protected fun class_factory(name: String): CLASS

core :: Object :: class_factory

Implementation used by get_class to create the specific class.
fun class_name: String

core :: Object :: class_name

The class name of the object.
fun current_location: Location

parser_base :: StringProcessor :: current_location

Gives the current location in the src
protected fun eof: Bool

parser_base :: StringProcessor :: eof

Is pos at the end of the source?
fun get_class: CLASS

core :: Object :: get_class

The meta-object representing the dynamic type of self.
fun hash: Int

core :: Object :: hash

The hash code of the object.
protected fun hot_location: Location

parser_base :: StringProcessor :: hot_location

Returns the current location as a Location object
protected fun ignore_until(s: String): Int

parser_base :: StringProcessor :: ignore_until

Reads characters until pattern s is found
fun ignore_until_separator

json :: JSONStringParser :: ignore_until_separator

Ignores any character until a JSON separator is encountered
protected fun ignore_until_whitespace: Int

parser_base :: StringProcessor :: ignore_until_whitespace

Ignores any printable character until a whitespace is encountered
protected fun ignore_until_whitespace_or_comment: Int

parser_base :: StringProcessor :: ignore_until_whitespace_or_comment

Advance pos until a whitespace or # is encountered
protected fun ignore_whitespaces

parser_base :: StringProcessor :: ignore_whitespaces

Advances in src until a non-whitespace character is encountered
init init

core :: Object :: init

fun inspect: String

core :: Object :: inspect

Developer readable representation of self.
protected fun inspect_head: String

core :: Object :: inspect_head

Return "CLASSNAME:#OBJECTID".
intern fun is_same_instance(other: nullable Object): Bool

core :: Object :: is_same_instance

Return true if self and other are the same instance (i.e. same identity).
fun is_same_serialized(other: nullable Object): Bool

core :: Object :: is_same_serialized

Is self the same as other in a serialization context?
intern fun is_same_type(other: Object): Bool

core :: Object :: is_same_type

Return true if self and other have the same dynamic type.
protected fun len: Int

parser_base :: StringProcessor :: len

Length of the source document
protected fun len=(len: Int)

parser_base :: StringProcessor :: len=

Length of the source document
protected fun line: Int

parser_base :: StringProcessor :: line

Current line in src
protected fun line=(line: Int)

parser_base :: StringProcessor :: line=

Current line in src
protected fun line_offset: Int

parser_base :: StringProcessor :: line_offset

Offset in the current line
protected fun line_start: Int

parser_base :: StringProcessor :: line_start

Position at which current line started
protected fun line_start=(line_start: Int)

parser_base :: StringProcessor :: line_start=

Position at which current line started
protected fun make_parse_error(message: String): JsonParseError

json :: JSONStringParser :: make_parse_error

Creates a JsonParseError with the right message and location
intern fun object_id: Int

core :: Object :: object_id

An internal hash code for the object based on its identity.
fun output

core :: Object :: output

Display self on stdout (debug only).
intern fun output_class_name

core :: Object :: output_class_name

Display class name on stdout (debug only).
fun parse_entity: nullable Serializable

json :: JSONStringParser :: parse_entity

Parses a JSON Entity
fun parse_json_string: Serializable

json :: JSONStringParser :: parse_json_string

Parses and returns a Nit string from a JSON String
protected fun pos: Int

parser_base :: StringProcessor :: pos

Current position in src
protected fun pos=(pos: Int)

parser_base :: StringProcessor :: pos=

Current position in src
protected fun read_number: Float

parser_base :: StringProcessor :: read_number

Read a token and parse it as a Float
protected fun read_token: String

parser_base :: StringProcessor :: read_token

Read a single token after skipping preceding whitespaces
protected fun read_until_eol_or_comment: String

parser_base :: StringProcessor :: read_until_eol_or_comment

Advance pos until the next end of line or a #
protected fun read_vec3: Vec3

parser_base :: StringProcessor :: read_vec3

Read 2 or 3 numbers and return them as a Vec3
protected fun read_vec4: Vec4

parser_base :: StringProcessor :: read_vec4

Read 3 or 4 numbers and return them as a Vec4
fun serialization_hash: Int

core :: Object :: serialization_hash

Hash value use for serialization
protected fun skip_eol

parser_base :: StringProcessor :: skip_eol

Advance pos to skip the next end of line
protected fun src: String

parser_base :: StringProcessor :: src

Source document to parse
protected fun src=(src: String)

parser_base :: StringProcessor :: src=

Source document to parse
intern fun sys: Sys

core :: Object :: sys

Return the global sys object, the only instance of the Sys class.
abstract fun to_jvalue(env: JniEnv): JValue

core :: Object :: to_jvalue

fun to_s: String

core :: Object :: to_s

User readable representation of self.
package_diagram json::JSONStringParser JSONStringParser parser_base::StringProcessor StringProcessor json::JSONStringParser->parser_base::StringProcessor core::Object Object parser_base::StringProcessor->core::Object ...core::Object ... ...core::Object->core::Object

Ancestors

interface Object

core :: Object

The root of the class hierarchy.

Parents

class StringProcessor

parser_base :: StringProcessor

Basic facilities for common parser operations on String sources

Class definitions

json $ JSONStringParser
# 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

	# 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

	# 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

	# 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 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

	# 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

	# 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

	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

	# 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
	end
end
lib/json/static.nit:159,1--456,3