Merge: lib/github: introduces Github hook events
[nit.git] / lib / json / static.nit
index 4ebf1c0..12dbc7c 100644 (file)
 
 # Static interface to get Nit objects from a Json string.
 #
-# `String::json_to_nit_object` returns an equivalent Nit object from
+# `Text::parse_json` returns an equivalent Nit object from
 # the Json source. This object can then be type checked by the usual
 # languages features (`isa` and `as`).
 module static
 
-import standard
+import error
 private import json_parser
 private import json_lexer
 
 # Something that can be translated to JSON.
 interface Jsonable
        # Encode `self` in JSON.
+       #
+       # SEE: `append_json`
        fun to_json: String is abstract
+
+       # Use `append_json` to implement `to_json`.
+       #
+       # Therefore, one that redefine `append_json` may use the following
+       # redefinition to link `to_json` and `append_json`:
+       #
+       # ~~~nitish
+       # redef fun to_json do return to_json_by_append
+       # ~~~
+       #
+       # Note: This is not the default implementation of `to_json` in order to
+       # avoid cyclic references between `append_json` and `to_json` when none are
+       # implemented.
+       protected fun to_json_by_append: String do
+               var buffer = new RopeBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       # Append the JSON representation of `self` to the specified buffer.
+       #
+       # SEE: `to_json`
+       fun append_json(buffer: Buffer) do buffer.append(to_json)
 end
 
 redef class Text
        super Jsonable
 
-       # Encode `self` in JSON.
-       #
-       #     assert "\t\"http://example.com\"\r\n\0\\".to_json ==
-       #               "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
-       redef fun to_json do
-               var buffer = new FlatBuffer
+       redef fun append_json(buffer) do
                buffer.add '\"'
                for i in [0..self.length[ do
                        var char = self[i]
@@ -72,10 +92,46 @@ redef class Text
                        end
                end
                buffer.add '\"'
-               return buffer.write_to_string
        end
 
-       fun json_to_nit_object: nullable Jsonable do
+       # Encode `self` in JSON.
+       #
+       #     assert "\t\"http://example.com\"\r\n\0\\".to_json ==
+       #               "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
+       redef fun to_json do return to_json_by_append
+
+       # Parse `self` as JSON.
+       #
+       # If `self` is not a valid JSON document or contains an unsupported escape
+       # sequence, return a `JSONParseError`.
+       #
+       # Example with `JsonObject`:
+       #
+       #     var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
+       #     assert obj isa JsonObject
+       #     assert obj["foo"] isa JsonObject
+       #     assert obj["foo"].as(JsonObject)["bar"] == true
+       #
+       # Example with `JsonArray`:
+       #
+       #     var arr = "[1, 2, 3]".parse_json
+       #     assert arr isa JsonArray
+       #     assert arr.length == 3
+       #     assert arr.first == 1
+       #     assert arr.last == 3
+       #
+       # Example with `String`:
+       #
+       #     var str = "\"foo, bar, baz\"".parse_json
+       #     assert str isa String
+       #     assert str == "foo, bar, baz"
+       #
+       # Example of a syntaxic 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
@@ -83,14 +139,8 @@ redef class Text
                var root_node = parser.parse
                if root_node isa NStart then
                        return root_node.n_0.to_nit_object
-               else if root_node isa NLexerError then
-                       var pos = root_node.position
-                       print "Json lexer error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return null
-               else if root_node isa NParserError then
-                       var pos = root_node.position
-                       print "Json parsing error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return null
+               else if root_node isa NError then
+                       return new JsonParseError(root_node.message, root_node.position)
                else abort
        end
 end
@@ -151,16 +201,7 @@ interface JsonMapRead[K: String, V: nullable Jsonable]
        super MapRead[K, V]
        super Jsonable
 
-       # Encode `self` in JSON.
-       #
-       #     var obj = new JsonObject
-       #     obj["foo"] = "bar"
-       #     assert obj.to_json == "\{\"foo\":\"bar\"\}"
-       #     obj = new JsonObject
-       #     obj["baz"] = null
-       #     assert obj.to_json == "\{\"baz\":null\}"
-       redef fun to_json do
-               var buffer = new FlatBuffer
+       redef fun append_json(buffer) do
                buffer.append "\{"
                var it = iterator
                if it.is_ok then
@@ -172,9 +213,18 @@ interface JsonMapRead[K: String, V: nullable Jsonable]
                end
                it.finish
                buffer.append "\}"
-               return buffer.write_to_string
        end
 
+       # Encode `self` in JSON.
+       #
+       #     var obj = new JsonObject
+       #     obj["foo"] = "bar"
+       #     assert obj.to_json == "\{\"foo\":\"bar\"\}"
+       #     obj = new JsonObject
+       #     obj["baz"] = null
+       #     assert obj.to_json == "\{\"baz\":null\}"
+       redef fun to_json do return to_json_by_append
+
        private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
                        buffer: Buffer) do
                buffer.append iterator.key.to_json
@@ -195,16 +245,7 @@ class JsonSequenceRead[E: nullable Jsonable]
        super Jsonable
        super SequenceRead[E]
 
-       # Encode `self` in JSON.
-       #
-       #     var arr = new JsonArray.with_items("foo", null)
-       #     assert arr.to_json == "[\"foo\",null]"
-       #     arr.pop
-       #     assert arr.to_json =="[\"foo\"]"
-       #     arr.pop
-       #     assert arr.to_json =="[]"
-       redef fun to_json do
-               var buffer = new FlatBuffer
+       redef fun append_json(buffer) do
                buffer.append "["
                var it = iterator
                if it.is_ok then
@@ -216,9 +257,18 @@ class JsonSequenceRead[E: nullable Jsonable]
                end
                it.finish
                buffer.append "]"
-               return buffer.write_to_string
        end
 
+       # Encode `self` in JSON.
+       #
+       #     var arr = new JsonArray.with_items("foo", null)
+       #     assert arr.to_json == "[\"foo\",null]"
+       #     arr.pop
+       #     assert arr.to_json =="[\"foo\"]"
+       #     arr.pop
+       #     assert arr.to_json =="[]"
+       redef fun to_json do return to_json_by_append
+
        private fun append_json_entry(iterator: Iterator[nullable Jsonable],
                        buffer: Buffer) do
                buffer.append_json_of(iterator.item)
@@ -232,11 +282,49 @@ class JsonArray
        super Array[nullable Jsonable]
 end
 
+redef class JsonParseError
+       super Jsonable
+
+       # Get the JSON representation of `self`.
+       #
+       #     var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
+       #     assert err.to_json == "\{\"error\":\"JsonParseError\"," +
+       #               "\"position\":\{" +
+       #                       "\"pos_start\":1,\"pos_end\":2," +
+       #                       "\"line_start\":3,\"line_end\":4," +
+       #                       "\"col_start\":5,\"col_end\":6" +
+       #               "\},\"message\":\"foo\"\}"
+       redef fun to_json do
+               return "\{\"error\":\"JsonParseError\"," +
+                               "\"position\":{position.to_json}," +
+                               "\"message\":{message.to_json}\}"
+       end
+end
+
+redef class Position
+       super Jsonable
+
+       # Get the JSON representation of `self`.
+       #
+       #     var pos = new Position(1, 2, 3, 4, 5, 6)
+       #     assert pos.to_json == "\{" +
+       #                       "\"pos_start\":1,\"pos_end\":2," +
+       #                       "\"line_start\":3,\"line_end\":4," +
+       #                       "\"col_start\":5,\"col_end\":6" +
+       #               "\}"
+       redef fun to_json do
+               return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
+                               "\"line_start\":{line_start},\"line_end\":{line_end}," +
+                               "\"col_start\":{col_start},\"col_end\":{col_end}\}"
+       end
+end
+
 ################################################################################
 # Redef parser
 
 redef class Nvalue
-       fun to_nit_object: nullable Jsonable is abstract
+       # The represented value.
+       private fun to_nit_object: nullable Jsonable is abstract
 end
 
 redef class Nvalue_number
@@ -265,7 +353,8 @@ redef class Nvalue_null
 end
 
 redef class Nstring
-       fun to_nit_string: String do
+       # The represented string.
+       private fun to_nit_string: String do
                var res = new FlatBuffer
                var i = 1
                while i < text.length - 1 do
@@ -315,7 +404,8 @@ redef class Nvalue_object
 end
 
 redef class Nmembers
-       fun pairs: Array[Npair] is abstract
+       # All the key-value pairs.
+       private fun pairs: Array[Npair] is abstract
 end
 
 redef class Nmembers_tail
@@ -332,8 +422,11 @@ redef class Nmembers_head
 end
 
 redef class Npair
-       fun name: String do return n_string.to_nit_string
-       fun value: nullable Jsonable do return n_value.to_nit_object
+       # The represented key.
+       private fun name: String do return n_string.to_nit_string
+
+       # The represented value.
+       private fun value: nullable Jsonable do return n_value.to_nit_object
 end
 
 redef class Nvalue_array
@@ -350,7 +443,8 @@ redef class Nvalue_array
 end
 
 redef class Nelements
-       fun items: Array[Nvalue] is abstract
+       # All the items.
+       private fun items: Array[Nvalue] is abstract
 end
 
 redef class Nelements_tail