Merge: nitunit: use annotations
authorJean Privat <jean@pryen.org>
Mon, 25 Sep 2017 20:23:07 +0000 (16:23 -0400)
committerJean Privat <jean@pryen.org>
Mon, 25 Sep 2017 20:23:07 +0000 (16:23 -0400)
This PR replace the name heuristics used by nitunit by nice and tidy annotations.

Before:

~~~nit
module test_my_test is test_suite

import test_suite

class TestMyTest
    super TestSuite

    redef fun before do # something

    fun test_my_test do
        assert true
    end
end
~~~

After:

~~~nit
module my_test is test

class MyTest
    test

    fun setup is before do # something

    fun my_test is test do
        assert true
    end
end
~~~

Motivations:
* cleaner API / naming policy
* [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) friendly
* more flexibility as one can define a `after` method in the lib that will only be executed on children module tagged with `test`
* more extensible as one can improve nitunit to support `test(timeout = 150)` or `test(res= "res/test_file.res")`

Used annotations:
* `test` on modules, classes and properties to indicate that nitunit must run this module/class/method
* `before`, `after` on properties from all classes but `Sys` for the before/after each case hooks
* `before_all`, `after_all` on properties from `Sys` for the before/after all cases hooks

This also removes the need of the `lib/test_suite` module.

I also migrated the existing test suites to the new annotations system. Let's see what Jenkins has to say about it.

Should fix #2165

Pull-Request: #2474
Reviewed-by: Jean Privat <jean@pryen.org>

127 files changed:
.gitattributes
contrib/asteronits/src/touch_ui.nit
contrib/benitlux/src/client/views/home_views.nit
contrib/jwrapper/src/code_generator.nit
contrib/jwrapper/src/model.nit
contrib/model_viewer/src/globe.nit
contrib/neo_doxygen/src/doxml/entitydef.nit
contrib/neo_doxygen/src/model/class_compound.nit
contrib/neo_doxygen/src/model/graph.nit
contrib/neo_doxygen/src/model/location.nit
contrib/neo_doxygen/src/model/module_compound.nit
contrib/neo_doxygen/src/tests/neo_doxygen_file_compound.nit
contrib/tnitter/src/tnitter_app.nit
examples/calculator/art/icon-sci.svg
examples/calculator/src/calculator.nit
lib/android/data_store.nit
lib/android/shared_preferences/shared_preferences_api10.nit
lib/app/data_store.nit
lib/app/examples/ui_example.nit
lib/app/http_request.nit
lib/app/ui.nit
lib/core/bytes.nit
lib/core/stream.nit
lib/core/text/flat.nit
lib/dom/parser.nit
lib/gamnit/bmfont.nit
lib/gamnit/depth/depth.nit
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_lights.nit [new file with mode: 0644]
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/more_models.nit
lib/gamnit/depth/shadow.nit [new file with mode: 0644]
lib/gamnit/display.nit
lib/gamnit/display_android.nit
lib/gamnit/display_linux.nit
lib/gamnit/dynamic_resolution.nit
lib/gamnit/examples/fonts_showcase/assets/corner.png [new file with mode: 0644]
lib/gamnit/examples/fonts_showcase/src/fonts_showcase.nit
lib/gamnit/gamnit.nit
lib/gamnit/model_parsers/obj.nit
lib/gamnit/network/client.nit
lib/gamnit/network/common.nit
lib/gamnit/network/server.nit
lib/gamnit/programs.nit
lib/gamnit/textures.nit
lib/gamnit/virtual_gamepad/virtual_gamepad.nit
lib/glesv2/glesv2.nit
lib/ios/data_store.nit
lib/json/.gitattributes [deleted file]
lib/json/.gitignore [deleted file]
lib/json/Makefile [deleted file]
lib/json/error.nit
lib/json/json_lexer.nit [deleted file]
lib/json/json_parser.nit [deleted file]
lib/json/serialization_read.nit
lib/json/serialization_write.nit
lib/json/static.nit
lib/json/string_parser.nit [deleted file]
lib/linux/audio.nit
lib/linux/data_store.nit
lib/markdown/markdown.nit
lib/mongodb/mongodb.nit
lib/msgpack/ext.nit [new file with mode: 0644]
lib/msgpack/msgpack.nit [new file with mode: 0644]
lib/msgpack/msgpack_to_json.nit [new file with mode: 0644]
lib/msgpack/package.ini [new file with mode: 0644]
lib/msgpack/read.nit [new file with mode: 0644]
lib/msgpack/serialization_common.nit [new file with mode: 0644]
lib/msgpack/serialization_read.nit [new file with mode: 0644]
lib/msgpack/serialization_write.nit [new file with mode: 0644]
lib/msgpack/write.nit [new file with mode: 0644]
lib/poset.nit
lib/sdl2/mixer.nit
lib/serialization/caching.nit
lib/serialization/engine_tools.nit
lib/serialization/inspect.nit [new file with mode: 0644]
lib/serialization/safe.nit [new file with mode: 0644]
lib/serialization/serialization.nit
lib/serialization/serialization_core.nit [new file with mode: 0644]
share/nitweb/directives/entity/card.html
share/nitweb/directives/entity/tag.html
share/nitweb/javascripts/entities.js
share/nitweb/views/doc/defs.html
share/nitweb/views/doc/doc.html
share/nitweb/views/doc/entity.html
share/nitweb/views/doc/graph.html
src/frontend/serialization_code_gen_phase.nit
src/frontend/serialization_model_phase.nit
src/highlight.nit
src/loader.nit
src/metrics/detect_covariance.nit
src/metrics/static_types_metrics.nit
src/model/model_json.nit
src/model/test_model_json.nit
src/neo.nit
src/web/api_model.nit
src/web/web_base.nit
tests/sav/nitce/test_inspect_serialization.res [new file with mode: 0644]
tests/sav/nitce/test_json_deserialization_alt1.res
tests/sav/nitce/test_json_deserialization_alt2.res [new file with mode: 0644]
tests/sav/nitce/test_json_deserialization_alt3.res
tests/sav/nitce/test_json_deserialization_alt4.res [new file with mode: 0644]
tests/sav/nitce/test_msgpack_deserialization_alt2.res [new file with mode: 0644]
tests/sav/nitce/test_msgpack_deserialization_alt3.res [new file with mode: 0644]
tests/sav/nitce/test_msgpack_deserialization_alt4.res [new file with mode: 0644]
tests/sav/niti/fixme/test_json_deserialization_alt2.res [new file with mode: 0644]
tests/sav/niti/fixme/test_json_deserialization_alt4.res [new file with mode: 0644]
tests/sav/niti/fixme/test_msgpack_deserialization_alt2.res [new file with mode: 0644]
tests/sav/nitserial_args1.res
tests/sav/nitwebcrawl.res
tests/sav/test_binary_deserialization_alt1.res
tests/sav/test_inspect_serialization.res [new file with mode: 0644]
tests/sav/test_json_deserialization_alt1.res
tests/sav/test_json_deserialization_alt2.res
tests/sav/test_json_deserialization_alt3.res
tests/sav/test_json_deserialization_alt4.res
tests/sav/test_json_static.res
tests/sav/test_msgpack_deserialization.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt1.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt2.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt3.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt4.res [new file with mode: 0644]
tests/test_adhoc_json_parse.nit
tests/test_deserialization.nit
tests/test_inspect_serialization.nit [new file with mode: 0644]
tests/test_json_deserialization.nit
tests/test_msgpack_deserialization.nit [new file with mode: 0644]

index e326f39..aec0cbc 100644 (file)
@@ -1,9 +1,8 @@
-c_src                  -diff
-parser.nit             -diff
-parser_prod.nit                -diff
-lexer.nit              -diff
-tables_nit.c           -diff
-c_src/**               -diff
+/src/parser/parser.nit      -diff
+/src/parser/parser_prod.nit -diff
+/src/parser/lexer.nit       -diff
+/src/parser/tables_nit.c    -diff
+/c_src/**                   -diff
 
 tests/sav/**/*.res     -whitespace
 *.res                  -whitespace
index bd8bfa5..5cae0d4 100644 (file)
@@ -25,8 +25,10 @@ redef class App
                super
 
                var gamepad = new VirtualGamepad
-               gamepad.add_dpad
-               gamepad.controls.first.as(DPad).show_down = false
+
+               var dpad = gamepad.add_dpad
+               if dpad != null then dpad.show_down = false
+
                gamepad.add_button("space", gamepad_spritesheet.fire)
                gamepad.visible = true
                self.gamepad = gamepad
index 402c173..460e1be 100644 (file)
@@ -19,24 +19,9 @@ import beer_views
 import social_views
 import user_views
 
-redef class App
-       redef fun on_create
-       do
-               if debug then print "App::on_create"
-
-               # Create the main window
-               show_home
-               super
-       end
-
-       # Show the home/main windows
-       fun show_home
-       do
-               var window = new HomeWindow
-               window.refresh
-               push_window window
-       end
+redef fun root_window do return new HomeWindow
 
+redef class App
        redef fun on_log_in
        do
                super
index 4014c75..7d49919 100644 (file)
@@ -153,7 +153,7 @@ class CodeGenerator
                # Write the model to file next to the Nit module
                var model_path = file_name.strip_extension + ".jwrapper.bin"
                var model_stream = model_path.to_path.open_wo
-               var serializer = new BinarySerializer(model_stream)
+               var serializer = new MsgPackSerializer(model_stream)
                serializer.serialize model
                model_stream.close
        end
index e2c8ac8..b4d437a 100644 (file)
@@ -21,7 +21,7 @@ module model is serialize
 import more_collections
 import opts
 import poset
-import binary::serialization
+import msgpack
 
 import jtype_converter
 
@@ -107,16 +107,16 @@ class JavaType
        end
 
        # Short name of the class, mangled to remove `$` (e.g. `Set`)
-       var id: String is lazy do return identifier.last.replace("$", "")
+       var id: String is lazy, noserialize do return identifier.last.replace("$", "")
 
        # Full name of this class as used in java code (e.g. `java.lang.Set`)
-       var java_full_name: String is lazy do return identifier.join(".").replace("$", ".")
+       var java_full_name: String is lazy, noserialize do return identifier.join(".").replace("$", ".")
 
        # Full name of this class as used by jni (e.g. `android.graphics.BitmapFactory$Options`)
-       var jni_full_name: String is lazy do return identifier.join(".")
+       var jni_full_name: String is lazy, noserialize do return identifier.join(".")
 
        # Name of this class for the extern declaration in Nit (e.g. `java.lang.Set[]`)
-       var extern_equivalent: String is lazy do return jni_full_name + "[]" * array_dimension
+       var extern_equivalent: String is lazy, noserialize do return jni_full_name + "[]" * array_dimension
 
        # Full name of this class with arrays and generic values (e.g. `java.lang.Set<E>[]`)
        redef fun to_s do
@@ -270,12 +270,12 @@ class JavaModel
                        end
 
                        var file = model_path.to_path.open_ro
-                       var d = new BinaryDeserializer(file)
+                       var d = new MsgPackDeserializer(file)
                        var model = d.deserialize
                        file.close
 
                        if d.errors.not_empty then
-                               print_error "Error: failed to deserialize model file '{model_path}' with: {d.errors.join(", ")}"
+                               print_error "Error: failed to deserialize model file '{model_path}' with:\n* {d.errors.join("\n* ")}"
                                continue
                        end
 
@@ -291,7 +291,7 @@ class JavaModel
        end
 
        # Does this model have access to the `java.lang.Object`?
-       var knows_the_object_class: Bool = all_classes.keys.has("java.lang.Object") is lazy
+       var knows_the_object_class: Bool = all_classes.keys.has("java.lang.Object") is lazy, noserialize
 
        # Add a class in `classes`
        fun add_class(jclass: JavaClass)
@@ -487,7 +487,7 @@ class NitModuleRef
        var path: String
 
        # Name of the module
-       var name: String is lazy do return path.basename(".nit")
+       var name: String is lazy, noserialize do return path.basename(".nit")
 
        redef fun to_s do return self.name
        redef fun ==(other) do return other isa NitModuleRef and self.path == other.path
@@ -558,7 +558,7 @@ redef class Sys
        var opt_extern_class_prefix = new OptionString("Prefix to extern classes (By default uses the full namespace)", "-p")
 
        # Prefix used to name extern classes, if `null` use the full namespace
-       var extern_class_prefix: nullable String is lazy do return opt_extern_class_prefix.value
+       var extern_class_prefix: nullable String is lazy, noserialize do return opt_extern_class_prefix.value
 
        # Libraries to search for existing wrappers
        var opt_libs = new OptionArray("Paths to libraries with wrappers of Java classes ('auto' to use the full Nit lib)", "-i")
index 12c6ffc..be54bce 100644 (file)
@@ -98,7 +98,7 @@ class GlobeMaterial
        init atmo do init(null, false, [0.0, 0.8*atmo_a, 1.0*atmo_a, atmo_a])
        private var atmo_a = 0.05
 
-       redef fun draw(actor, model)
+       redef fun draw(actor, model, camera)
        do
                var gl_error = glGetError
                assert gl_error == gl_NO_ERROR else print gl_error
@@ -108,9 +108,6 @@ class GlobeMaterial
                var program = app.globe_program
                program.use
 
-               # Set constant program values
-               program.use
-
                # Bind textures
                glActiveTexture gl_TEXTURE0
                glBindTexture(gl_TEXTURE_2D, app.texture_earth.gl_texture)
@@ -135,7 +132,7 @@ class GlobeMaterial
                # Update camera view and light
                var p = app.world_camera.position
                program.camera.uniform(p.x, p.y, p.z)
-               program.mvp.uniform app.world_camera.mvp_matrix
+               program.mvp.uniform camera.mvp_matrix
                program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
 
                # Set attributes
index 0f83aa1..aa6968c 100644 (file)
@@ -40,7 +40,7 @@ abstract class EntityDefListener
        # The current entity.
        protected fun entity: Entity is abstract
 
-       redef fun start_dox_element(local_name: String, atts: Attributes) do
+       redef fun start_dox_element(local_name, atts) do
                if ["briefdescription", "detaileddescription", "inbodydescription"].has(local_name) then
                        doc.doc = entity.doc
                        doc.listen_until(dox_uri, local_name)
@@ -57,8 +57,8 @@ abstract class EntityDefListener
        end
 
        # Parse the attributes of a `location` element.
-       protected fun get_location(atts: Attributes): Location do
-               var location = new Location
+       protected fun get_location(atts: Attributes): neo_doxygen::Location do
+               var location = new neo_doxygen::Location
 
                location.path = atts.value_ns("", "bodyfile") or else atts.value_ns("", "file")
                # Doxygen may indicate `[generated]`.
@@ -100,7 +100,7 @@ abstract class ParamListener[T: Parameter]
        # Create a new parameter.
        protected fun create_parameter: T is abstract
 
-       redef fun start_dox_element(local_name: String, atts: Attributes) do
+       redef fun start_dox_element(local_name, atts) do
                if "declname" == local_name then
                        text.listen_until(dox_uri, local_name)
                else if "type" == local_name then
@@ -110,7 +110,7 @@ abstract class ParamListener[T: Parameter]
                end
        end
 
-       redef fun end_dox_element(local_name: String) do
+       redef fun end_dox_element(local_name) do
                if "declname" == local_name then
                        parameter.name = text.to_s
                else if "type" == local_name then
index aca58cb..b0f2096 100644 (file)
@@ -44,13 +44,13 @@ class ClassCompound
        # Return the number of type parameters.
        fun arity: Int do return class_type.arity
 
-       redef fun name=(name: String) do
+       redef fun name=(name) do
                super
                class_type.name = name
                class_def.name = name
        end
 
-       redef fun location=(location: nullable Location) do
+       redef fun location=(location) do
                super
                class_def.location = location
        end
@@ -60,11 +60,11 @@ class ClassCompound
                class_def["mdoc"] = doc
        end
 
-       redef fun declare_super(id: String, full_name: String, prot: String, virt: String) do
+       redef fun declare_super(id, full_name, prot, virt) do
                class_def.declare_super(id, full_name, prot, virt)
        end
 
-       redef fun declare_member(member: Member) do
+       redef fun declare_member(member) do
                class_def.declare_member(member)
        end
 
index 26c8e66..9df491a 100644 (file)
@@ -167,13 +167,13 @@ abstract class Entity
        fun ns_separator: String do return "::"
 
        # Set the location of the entity in the source code.
-       fun location=(location: nullable Location) do
+       fun location=(location: nullable neo_doxygen::Location) do
                self["location"] = location
        end
 
        # Get the location of the entity in the source code.
-       fun location: nullable Location do
-               return self["location"].as(nullable Location)
+       fun location: nullable neo_doxygen::Location do
+               return self["location"].as(nullable neo_doxygen::Location)
        end
 
        # Put the entity in the graph.
@@ -202,12 +202,12 @@ abstract class CodeBlock
        super Entity
 
        init do
-               self["location"] = new Location
+               self["location"] = new neo_doxygen::Location
        end
 
-       redef fun location=(location: nullable Location) do
+       redef fun location=(location) do
                if location == null then
-                       super(new Location)
+                       super(new neo_doxygen::Location)
                else
                        super
                end
index 576a087..4e58af0 100644 (file)
@@ -15,7 +15,6 @@
 # This module is used to model locations in source files.
 module location
 
-import json::static
 import json
 
 # A location inside a source file.
index 188d4c1..aca8dfc 100644 (file)
@@ -46,12 +46,12 @@ class FileCompound
                super
        end
 
-       redef fun location=(location: nullable Location) do
+       redef fun location=(location) do
                super
                for m in inner_namespaces do m.location = location
        end
 
-       redef fun name=(name: String) do
+       redef fun name=(name) do
                # Example: `MyClass.java`
                super
                var match = name.search_last(".")
@@ -65,7 +65,7 @@ class FileCompound
                for m in inner_namespaces do m.update_name
        end
 
-       redef fun declare_namespace(id: String, full_name: String) do
+       redef fun declare_namespace(id, full_name) do
                var m: Module
 
                assert not full_name.is_empty or id.is_empty else
index 3b1f6a4..a9dae43 100644 (file)
@@ -26,11 +26,11 @@ var c_ns = new Namespace(graph)
 var d_ns = new Namespace(graph)
 var buffer = new Buffer
 var root_ns = graph.by_id[""].as(Namespace)
-var location: Location
+var location
 
 file.name = "Bar.java"
 file.model_id = "_Bar_8java"
-location = new Location
+location = new neo_doxygen::Location
 location.path = "a/b/Bar.java"
 file.location = location
 file.declare_class("classa_b_bar", "a::b::Bar", "package")
@@ -41,7 +41,7 @@ file.put_in_graph
 
 file_2.name = "Bar.java"
 file_2.model_id = "_Bar_8java_2"
-location = new Location
+location = new neo_doxygen::Location
 location.path = "Bar.java"
 file_2.location = location
 file_2.declare_namespace("namespacec", "c")
@@ -50,7 +50,7 @@ file_2.put_in_graph
 
 bar_class.model_id = "classa_b_bar"
 bar_class.name = "Bar"
-location = new Location
+location = new neo_doxygen::Location
 location.path = "a/b/Bar.class"
 location.line_start = 5
 location.column_start = 1
@@ -61,7 +61,7 @@ bar_class.put_in_graph
 
 baz_class.model_id = "classbaz"
 baz_class.name = "Baz"
-location = new Location
+location = new neo_doxygen::Location
 location.path = "Baz.jar"
 baz_class.location = location
 baz_class.put_in_graph
index 8218004..760af3d 100644 (file)
@@ -38,18 +38,7 @@ import model
 # Delay in seconds before the next request after an error
 fun request_delay_on_error: Float do return 60.0
 
-redef class App
-       redef fun on_create
-       do
-               # Create the main window
-               push_window new TnitterWindow
-               super
-       end
-end
-
-# Main window
-class TnitterWindow
-       super Window
+redef class Window
 
        private var layout = new VerticalLayout(parent=self)
        private var list_posts = new ListLayout(parent=layout)
@@ -98,7 +87,7 @@ fun tnitter_server_uri: String do return "http://localhost:8080"
 abstract class AsyncTnitterRequest
        super AsyncHttpRequest
 
-       private var window: TnitterWindow
+       private var window: Window
 
        redef fun uri_root do return tnitter_server_uri
 
index 187bb2d..3358c6c 100644 (file)
@@ -13,8 +13,8 @@
    height="512"
    id="svg2"
    version="1.1"
-   inkscape:version="0.48.5 r10040"
-   sodipodi:docname="icon_sci.svg">
+   inkscape:version="0.92.1 r15371"
+   sodipodi:docname="icon-sci.svg">
   <defs
      id="defs4" />
   <sodipodi:namedview
@@ -25,7 +25,7 @@
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
      inkscape:zoom="1.4"
-     inkscape:cx="164.13268"
+     inkscape:cx="165.56125"
      inkscape:cy="278.8294"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
          id="path2999"
          style="" />
     </g>
-    <text
-       xml:space="preserve"
-       style="font-size:190.84666443px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
-       x="318.1875"
-       y="744.69647"
-       id="text2994"
-       sodipodi:linespacing="125%"><tspan
-         sodipodi:role="line"
-         id="tspan2996"
-         x="318.1875"
-         y="744.69647">√</tspan></text>
-    <text
-       xml:space="preserve"
-       style="font-size:190.84666443px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
-       x="72.978729"
-       y="725.84735"
-       id="text2998"
-       sodipodi:linespacing="125%"><tspan
-         sodipodi:role="line"
-         id="tspan3000"
-         x="72.978729"
-         y="725.84735">π</tspan></text>
+    <path
+       inkscape:connector-curvature="0"
+       id="path4504"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+       d="M 376.61565,748.14397 H 364.2218 L 338.40905,675.6446 h -16.77364 v -13.32572 h 27.30375 l 21.52616,62.80794 47.71167,-136.14598 h 13.60528 z" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path4507"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+       d="m 181.35503,716.1555 q 4.93891,0 8.38682,-1.86373 v 13.13934 q -4.56616,2.42286 -12.58022,2.42286 -21.15342,0 -21.15342,-24.41495 V 639.6491 h -43.33188 v 88.34113 H 96.089068 V 639.6491 H 75.3084 v -7.26857 l 13.698467,-6.70945 H 192.63064 v 13.97802 h -20.03517 v 64.67168 q 0,11.83472 8.75956,11.83472 z" />
     <flowRoot
        xml:space="preserve"
        id="flowRoot3002"
            height="282.85715"
            x="-225"
            y="-62.285713" /></flowRegion><flowPara
-         id="flowPara3008"></flowPara></flowRoot>    <text
-       xml:space="preserve"
-       style="font-size:190.84666443px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
-       x="303.01755"
-       y="973.79059"
-       id="text3014"
-       sodipodi:linespacing="125%"><tspan
-         sodipodi:role="line"
-         id="tspan3016"
-         x="303.01755"
-         y="973.79059">x²</tspan></text>
+         id="flowPara3008" /></flowRoot>    <path
+       inkscape:connector-curvature="0"
+       id="path4499"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+       d="m 335.60349,924.36988 -35.31781,-50.04133 h 19.1033 l 26.37188,38.57935 26.09232,-38.57935 h 18.91693 l -35.31782,50.04133 37.27474,52.27783 h -19.1033 l -27.86287,-40.81584 -28.23562,40.81584 h -18.91693 z" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path4501"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+       d="m 454.6031,922.04021 h -53.862 v -10.25055 l 21.99209,-21.43298 q 11.83473,-11.46198 15.09627,-16.68044 3.35473,-5.31165 3.35473,-11.74154 0,-6.05715 -3.5411,-9.13232 -3.44791,-3.07516 -9.2255,-3.07516 -5.77758,0 -10.43693,2.23648 -4.65934,2.23649 -9.59824,5.96396 l -6.70945,-8.75956 q 12.39385,-10.53012 26.931,-10.53012 12.30066,0 19.19649,6.15034 6.98901,6.05714 6.98901,16.40088 0,4.2866 -1.21143,8.01407 -1.11824,3.63429 -3.5411,7.45495 -2.42286,3.72747 -6.33671,8.01407 -3.82066,4.28659 -26.37187,25.53319 h 37.27474 z" />
   </g>
 </svg>
index 6cfb1aa..32799ca 100644 (file)
@@ -32,16 +32,7 @@ import calculator_logic
 # Show debug output?
 fun debug: Bool do return false
 
-redef class App
-       redef fun on_create
-       do
-               if debug then print "App::on_create"
-
-               # Create the main window
-               push_window new CalculatorWindow
-               super
-       end
-end
+redef fun root_window do return new CalculatorWindow
 
 # The main (and only) window of this calculator
 class CalculatorWindow
index 989b64c..cbef585 100644 (file)
 module data_store
 
 import app::data_store
-private import shared_preferences
+import shared_preferences
 
-redef class App
-       redef var data_store = new SharedPreferenceView
-end
-
-private class SharedPreferenceView
-       super DataStore
+redef class DataStore
 
        # The `SharedPreferences` used to implement the `DataStore`
        var shared_preferences = new SharedPreferences.privately(app, "data_store") is lazy
index f17661f..0464b67 100644 (file)
@@ -397,7 +397,16 @@ class SharedPreferences
                if serialized_string == "" then return null
 
                var deserializer = new JsonDeserializer(serialized_string)
-               return deserializer.deserialize
+               var deserialized = deserializer.deserialize
+
+               var errors = deserializer.errors
+               if errors.not_empty then
+                       # An update may have broken the versioning compatibility
+                       print_error "{class_name} error at deserialization: {errors.join(", ")}"
+                       return null # Let's be safe
+               end
+
+               return deserialized
        end
 end
 
index ebebe83..cb5ce2a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Simple data storage services
+# Key/value storage services
 #
-# The implementation varies per platform.
+# The main services is `App::data_store`, a `DataStore` holding any
+# serializable Nit object.
 module data_store
 
 import app_base
@@ -28,12 +29,61 @@ import android::data_store is conditional(android)
 import ios::data_store is conditional(ios)
 
 redef class App
+
        # Services to store and load data
-       fun data_store: DataStore is abstract
+       #
+       # ~~~
+       # import app::ui
+       # import app::data_store
+       #
+       # class MyWindow
+       #     super Window
+       #
+       #     var state = "Simple string or any serializable class"
+       #
+       #     redef fun on_save_state do app.data_store["state"] = state
+       #
+       #     redef fun on_restore_state
+       #     do
+       #         var state = app.data_store["state"]
+       #         if state isa String then self.state = state
+       #     end
+       # end
+       # ~~~
+       var data_store = new DataStore is lazy
 end
 
 # Simple data storage facility
-interface DataStore
+#
+# Write values with `[]=` and read with `[]`.
+# ~~~
+# import linux::data_store # Needed for testing only
+#
+# class A
+#     serialize
+#
+#     var b = true
+#     var f = 1.234
+# end
+#
+# var data_store = new DataStore
+# data_store["one"] = 1
+# data_store["str"] = "Some string"
+# data_store["a"] = new A
+#
+# assert data_store["one"] == 1
+# assert data_store["str"] == "Some string"
+# assert data_store["a"].as(A).b
+# assert data_store["a"].as(A).f == 1.234
+# assert data_store["other"] == null
+# ~~~
+#
+# Set to `null` to clear a value.
+# ~~~
+# data_store["one"] = null
+# assert data_store["one"] == null
+# ~~~
+class DataStore
 
        # Get the object stored at `key`, or null if nothing is available
        fun [](key: String): nullable Object is abstract
index 43628af..5d01592 100644 (file)
@@ -16,7 +16,9 @@
 module ui_example is
        app_name "app.nit UI"
        app_namespace "org.nitlanguage.ui_example"
-       android_api_target 15
+       android_api_min 21
+       android_api_target 21
+       android_manifest_activity "android:theme=\"@android:style/Theme.Material\""
 end
 
 import app::ui
@@ -31,7 +33,7 @@ class UiExampleWindow
        var layout = new ListLayout(parent=self)
 
        # Some label
-       var some_label = new Label(parent=layout, text="This Window uses a ListLayout.")
+       var some_label = new Label(parent=layout, text="Sample Window using a ListLayout.")
 
        # A checkbox
        var checkbox = new CheckBox(parent=layout, text="A CheckBox")
@@ -82,11 +84,4 @@ class SecondWindow
        var another_label = new Label(parent=layout, text="Close it by tapping the back button.")
 end
 
-redef class App
-       redef fun on_create
-       do
-               # Create the main window
-               push_window new UiExampleWindow
-               super
-       end
-end
+redef fun root_window do return new UiExampleWindow
index 8ab0f1d..07d4bf3 100644 (file)
 # limitations under the License.
 
 # HTTP request services: `AsyncHttpRequest` and `Text::http_get`
+#
+# ~~~nitish
+# import app::http_request
+#
+# class MyHttpRequest
+#     super AsyncHttpRequest
+#
+#     redef fun uri do return "http://example.com/"
+#
+#     redef fun on_load(data, status) do print "Received: {data or else "null"}"
+#
+#     redef fun on_fail(error) do print "Connection error: {error}"
+# end
+#
+# var req = new MyHttpRequest
+# req.start
+# ~~~
 module http_request
 
 import app_base
@@ -122,9 +139,14 @@ abstract class AsyncHttpRequest
        fun after do end
 end
 
-# Minimal implementation of `AsyncHttpRequest` where `uri` is an attribute
+# Simple `AsyncHttpRequest` where `uri` is an attribute
+#
+# Prints on communication errors and when the remote server returns an
+# HTTP status code not in the 200s.
 #
-# Prints on communication errors and when the server returns an HTTP status code not in the 200s.
+# This class can be instantiated to execute a request where the response is
+# ignored by the application. Alternatively, it can be subclassed to implement
+# `on_load`.
 #
 # ~~~nitish
 # var request = new SimpleAsyncHttpRequest("http://example.com")
index 1fdf801..42e36ce 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Portable UI API for the _app.nit_ framework
+# Portable UI controls for mobiles apps
+#
+# ~~~
+# import app::ui
+#
+# class MyWindow
+#     super Window
+#
+#     var layout = new ListLayout(parent=self)
+#     var lbl = new Label(parent=layout, text="Hello world", align=0.5)
+#     var but = new Button(parent=layout, text="Press here")
+#
+#     redef fun on_event(event) do lbl.text = "Pressed!"
+# end
+#
+# redef fun root_window do return new MyWindow
+# ~~~
 module ui
 
 import app_base
@@ -30,14 +46,14 @@ redef class App
        # This attribute is set by `push_window`.
        var window: Window is noinit
 
-       # Make visible and push `window` on the top of `pop_window`
+       # Make `window` visible and push it on the top of the `window_stack`
        #
-       # This method must be called at least once within `App::on_create`.
-       # It can be called at any times while the app is active.
+       # This method can be called at any times while the app is active.
        fun push_window(window: Window)
        do
                window_stack.add window
                self.window = window
+               window.on_create
        end
 
        # Pop the current `window` from the stack and show the previous one
@@ -54,7 +70,11 @@ redef class App
        # Stack of active windows
        var window_stack = new Array[Window]
 
-       redef fun on_create do window.on_create
+       redef fun on_create
+       do
+               var window = root_window
+               push_window window
+       end
 
        redef fun on_resume do window.on_resume
 
@@ -67,6 +87,13 @@ redef class App
        redef fun on_save_state do window.on_save_state
 end
 
+# Hook to create the first window shown to the user
+#
+# By default, a `Window` is created, which can be refined to customize it.
+# However, most apps should refine this method to return a different window,
+# this way the app can have more than one window.
+fun root_window: Window do return new Window
+
 # An event created by an `AppComponent` and sent to `AppObserver`s
 interface AppEvent
 end
@@ -173,6 +200,9 @@ class CompositeControl
 end
 
 # A window, root of the `Control` tree
+#
+# Each window should hold a single control, usually a `CompositeControl`,
+# which in turn holds all the displayed controls.
 class Window
        super CompositeControl
 
@@ -183,7 +213,7 @@ class Window
        fun on_back_button do app.pop_window
 end
 
-# A viewable `Control`
+# A visible `Control`
 abstract class View
        super Control
 
@@ -193,7 +223,9 @@ abstract class View
        var enabled: nullable Bool is writable, abstract, autoinit
 end
 
-# A control with some `text`
+# A control displaying some `text`
+#
+# For a text-only control, see `Label`.
 abstract class TextView
        super View
 
@@ -233,17 +265,17 @@ class TextInput
        var is_password: nullable Bool is writable
 end
 
-# A pushable button, raises `ButtonPressEvent`
+# A pressable button, raises `ButtonPressEvent`
 class Button
        super TextView
 end
 
-# A text label
+# A simple text label
 class Label
        super TextView
 end
 
-# Toggle control with two states and a label
+# Toggle control between two states, also displays a label
 class CheckBox
        super TextView
 
index 5b5fceb..84276ac 100644 (file)
@@ -214,7 +214,7 @@ class Bytes
                return slice(st, ed - st + 1)
        end
 
-       # Returns a subset of the content of `self` starting at `from` and of length `count`
+       # Copy a subset of `self` starting at `from` and of `count` bytes
        #
        #     var b = "abcd".to_bytes
        #     assert b.slice(1, 2).hexdigest == "6263"
@@ -239,7 +239,7 @@ class Bytes
                return ret
        end
 
-       # Returns a copy of `self` starting at `from`
+       # Copy of `self` starting at `from`
        #
        #     var b = "abcd".to_bytes
        #     assert b.slice_from(1).hexdigest  == "626364"
@@ -328,35 +328,48 @@ class Bytes
                return new FlatString.full(ns, elen, 0, elen)
        end
 
-       # Interprets `self` as a big-endian positive integer.
+       # Interprets `self` as a big-endian integer (unsigned by default)
        #
        # ~~~
        # var b = "0102".hexdigest_to_bytes
        # assert b.to_i == 258
+       #
+       # assert   "01".hexdigest_to_bytes.to_i == 1
+       # assert   "FF".hexdigest_to_bytes.to_i == 255
+       # assert "0000".hexdigest_to_bytes.to_i == 0
        # ~~~
        #
-       # Nul bytes on the left are trimmed.
-       # 0 is returned for an empty Bytes object.
+       # If `self.is_empty`, 0 is returned.
        #
        # ~~~
-       # assert "01".hexdigest_to_bytes.to_i == 1
-       # assert "0001".hexdigest_to_bytes.to_i == 1
-       #
-       # assert "0000".hexdigest_to_bytes.to_i == 0
-       # assert "00".hexdigest_to_bytes.to_i == 0
        # assert "".hexdigest_to_bytes.to_i == 0
        # ~~~
        #
+       # If `signed == true`, the bytes are read as a signed integer.
+       # As usual, the sign bit is the left most bit, no matter the
+       # `length` of `self`.
+       #
+       # ~~~
+       # assert     "01".hexdigest_to_bytes.to_i(true) ==      1
+       # assert     "FF".hexdigest_to_bytes.to_i(true) ==     -1
+       # assert   "00FF".hexdigest_to_bytes.to_i(true) ==    255
+       # assert     "E0".hexdigest_to_bytes.to_i(true) ==    -32
+       # assert   "FE00".hexdigest_to_bytes.to_i(true) ==   -512
+       # assert "FEFEFE".hexdigest_to_bytes.to_i(true) == -65794
+       # ~~~
+       #
        # `Int::to_bytes` is a loosely reverse method.
        #
        # ~~~
        # assert b.to_i.to_bytes == b
        # assert (b.to_i + 1).to_bytes.hexdigest == "0103"
        # assert "0001".hexdigest_to_bytes.to_i.to_bytes.hexdigest == "01"
+       #
+       # assert (-32).to_bytes.to_i(true) == -32
        # ~~~
        #
        # Warning: `Int` might overflow for bytes with more than 60 bits.
-       fun to_i: Int do
+       fun to_i(signed: nullable Bool): Int do
                var res = 0
                var i = 0
                while i < length do
@@ -364,6 +377,18 @@ class Bytes
                        res += self[i].to_i
                        i += 1
                end
+
+               # Two's complement is `signed`
+               if signed == true and not_empty and first > 0x80u8 then
+                       var ff = 0
+                       for j in [0..length[ do
+                               ff *= 0x100
+                               ff += 0xFF
+                       end
+
+                       res = -((res ^ ff) + 1)
+               end
+
                return res
        end
 
@@ -453,18 +478,15 @@ class Bytes
                length += ln
        end
 
-       # Appends the bytes of `s` to `selftextextt`
-       fun append_text(s: Text) do
-               for i in s.substrings do
-                       append_ns(i.fast_cstring, i.byte_length)
-               end
-       end
+       # Appends the bytes of `str` to `self`
+       fun append_text(str: Text) do str.append_to_bytes self
 
        redef fun append_to(b) do b.append self
 
        redef fun enlarge(sz) do
                if capacity >= sz then return
                persisted = false
+               if capacity < 16 then capacity = 16
                while capacity < sz do capacity = capacity * 2 + 2
                var ns = new CString(capacity)
                items.copy_to(ns, length, 0, 0)
@@ -654,7 +676,7 @@ private class BytesIterator
 end
 
 redef class Int
-       # A big-endian representation of self.
+       # A signed big-endian representation of `self`
        #
        # ~~~
        # assert     1.to_bytes.hexdigest ==     "01"
@@ -664,43 +686,86 @@ redef class Int
        # assert 65536.to_bytes.hexdigest == "010000"
        # ~~~
        #
+       # Negative values are converted to their two's complement.
+       # Be careful as the result can be ambiguous.
+       #
+       # ~~~
+       # assert     (-1).to_bytes.hexdigest ==     "FF"
+       # assert    (-32).to_bytes.hexdigest ==     "E0"
+       # assert   (-512).to_bytes.hexdigest ==   "FE00"
+       # assert (-65794).to_bytes.hexdigest == "FEFEFE"
+       # ~~~
+       #
+       # Optionally, set `n_bytes` to the desired number of bytes in the output.
+       # This setting can disambiguate the result between positive and negative
+       # integers. Be careful with this parameter as the result may overflow.
+       #
+       # ~~~
+       # assert        1.to_bytes(2).hexdigest ==     "0001"
+       # assert    65535.to_bytes(2).hexdigest ==     "FFFF"
+       # assert     (-1).to_bytes(2).hexdigest ==     "FFFF"
+       # assert   (-512).to_bytes(4).hexdigest == "FFFFFE00"
+       # assert 0x123456.to_bytes(2).hexdigest ==     "3456"
+       # ~~~
+       #
        # For 0, a Bytes object with single nul byte is returned (instead of an empty Bytes object).
        #
        # ~~~
        # assert 0.to_bytes.hexdigest == "00"
        # ~~~
        #
-       # `Bytes::to_i` can be used to do the reverse operation.
+       # For positive integers, `Bytes::to_i` can reverse the operation.
        #
        # ~~~
        # assert 1234.to_bytes.to_i == 1234
        # ~~~
        #
        # Require self >= 0
-       fun to_bytes: Bytes do
-               if self == 0 then return "\0".to_bytes
-               assert self > 0
+       fun to_bytes(n_bytes: nullable Int): Bytes do
+
+               # If 0, force using at least one byte
+               if self == 0 and n_bytes == null then n_bytes = 1
 
                # Compute the len (log256)
                var len = 1
                var max = 256
-               while self >= max do
+               var s = self.abs
+               while s >= max do
                        len += 1
                        max *= 256
                end
 
+               # Two's complement
+               s = self
+               if self < 0 then
+                       var ff = 0
+                       for j in [0..len[ do
+                               ff *= 0x100
+                               ff += 0xFF
+                       end
+
+                       s = ((-self) ^ ff) + 1
+               end
+
+               # Cut long values
+               if n_bytes != null and len > n_bytes then len = n_bytes
+
                # Allocate the buffer
-               var res = new Bytes.with_capacity(len)
-               for i in [0..len[ do res[i] = 0u8
+               var cap = n_bytes or else len
+               var res = new Bytes.with_capacity(cap)
+
+               var filler = if self < 0 then 0xFFu8 else 0u8
+               for i in [0..cap[ do res[i] = filler
 
                # Fill it starting with the end
-               var i = len
-               var sum = self
-               while i > 0 do
+               var i = cap
+               var sum = s
+               while i > cap - len do
                        i -= 1
                        res[i] = (sum % 256).to_b
                        sum /= 256
                end
+
                return res
        end
 end
@@ -931,7 +996,7 @@ end
 redef class FlatText
        redef fun append_to_bytes(b) do
                var from = if self isa FlatString then first_byte else 0
-               b.append_ns_from(items, byte_length, from)
+               if isset _items then b.append_ns_from(items, byte_length, from)
        end
 end
 
index 80b96f0..b006398 100644 (file)
@@ -662,81 +662,172 @@ abstract class Duplex
        super Writer
 end
 
-# `Stream` that can be used to write to a `String`
+# Write to `bytes` in memory
 #
-# Mainly used for compatibility with Writer type and tests.
-class StringWriter
+# ~~~
+# var writer = new BytesWriter
+#
+# writer.write "Strings "
+# writer.write_char '&'
+# writer.write_byte 0x20u8
+# writer.write_bytes "bytes".to_bytes
+#
+# assert writer.to_s == "\\x53\\x74\\x72\\x69\\x6E\\x67\\x73\\x20\\x26\\x20\\x62\\x79\\x74\\x65\\x73"
+# assert writer.bytes.to_s == "Strings & bytes"
+# ~~~
+#
+# As with any binary data, UTF-8 code points encoded on two bytes or more
+# can be constructed byte by byte.
+#
+# ~~~
+# writer = new BytesWriter
+#
+# # Write just the character first half
+# writer.write_byte 0xC2u8
+# assert writer.to_s == "\\xC2"
+# assert writer.bytes.to_s == "�"
+#
+# # Complete the character
+# writer.write_byte 0xA2u8
+# assert writer.to_s == "\\xC2\\xA2"
+# assert writer.bytes.to_s == "¢"
+# ~~~
+class BytesWriter
        super Writer
 
-       private var content = new Buffer
-       redef fun to_s do return content.to_s
-       redef fun is_writable do return not closed
+       # Written memory
+       var bytes = new Bytes.empty
 
-       redef fun write_bytes(b) do
-               content.append(b.to_s)
-       end
+       redef fun to_s do return bytes.chexdigest
 
        redef fun write(str)
        do
-               assert not closed
-               content.append(str)
+               if closed then return
+               str.append_to_bytes bytes
        end
 
        redef fun write_char(c)
        do
-               assert not closed
-               content.add(c)
+               if closed then return
+               bytes.add_char c
+       end
+
+       redef fun write_byte(value)
+       do
+               if closed then return
+               bytes.add value
+       end
+
+       redef fun write_bytes(b)
+       do
+               if closed then return
+               bytes.append b
        end
 
        # Is the stream closed?
        protected var closed = false
 
        redef fun close do closed = true
+       redef fun is_writable do return not closed
 end
 
-# `Stream` used to read from a `String`
+# `Stream` writing to a `String`
 #
-# Mainly used for compatibility with Reader type and tests.
-class StringReader
+# This class has the same behavior as `BytesWriter`
+# except for `to_s` which decodes `bytes` to a string.
+#
+# ~~~
+# var writer = new StringWriter
+#
+# writer.write "Strings "
+# writer.write_char '&'
+# writer.write_byte 0x20u8
+# writer.write_bytes "bytes".to_bytes
+#
+# assert writer.to_s == "Strings & bytes"
+# ~~~
+class StringWriter
+       super BytesWriter
+
+       redef fun to_s do return bytes.to_s
+end
+
+# Read from `bytes` in memory
+#
+# ~~~
+# var reader = new BytesReader(b"a…b")
+# assert reader.read_char == 'a'
+# assert reader.read_byte == 0xE2u8 # 1st byte of '…'
+# assert reader.read_byte == 0x80u8 # 2nd byte of '…'
+# assert reader.read_char == '�' # Reads the last byte as an invalid char
+# assert reader.read_all_bytes == b"b"
+# ~~~
+class BytesReader
        super Reader
 
-       # The string to read from.
-       var source: String
+       # Source data to read
+       var bytes: Bytes
 
-       # The current position in the string (bytewise).
-       private var cursor: Int = 0
+       # The current position in `bytes`
+       private var cursor = 0
 
-       redef fun read_char do
-               if cursor < source.length then
-                       # Fix when supporting UTF-8
-                       var c = source[cursor]
-                       cursor += 1
-                       return c
-               else
-                       return null
-               end
-       end
+       redef fun read_char
+       do
+               if cursor >= bytes.length then return null
 
-       redef fun read_byte do
-               if cursor < source.length then
-                       var c = source.bytes[cursor]
-                       cursor += 1
-                       return c
-               else
-                       return null
-               end
+               var len = bytes.items.length_of_char_at(cursor)
+               var char = bytes.items.char_at(cursor)
+               cursor += len
+               return char
        end
 
-       redef fun close do
-               source = ""
+       redef fun read_byte
+       do
+               if cursor >= bytes.length then return null
+
+               var c = bytes[cursor]
+               cursor += 1
+               return c
        end
 
-       redef fun read_all_bytes do
-               var nslen = source.length - cursor
-               var nns = new CString(nslen)
-               source.copy_to_native(nns, nslen, cursor, 0)
-               return new Bytes(nns, nslen, nslen)
+       redef fun close do bytes = new Bytes.empty
+
+       redef fun read_all_bytes
+       do
+               var res = bytes.slice_from(cursor)
+               cursor = bytes.length
+               return res
        end
 
-       redef fun eof do return cursor >= source.byte_length
+       redef fun eof do return cursor >= bytes.length
+end
+
+# `Stream` reading from a `String` source
+#
+# This class has the same behavior as `BytesReader`
+# except for its constructor accepting a `String`.
+#
+# ~~~
+# var reader = new StringReader("a…b")
+# assert reader.read_char == 'a'
+# assert reader.read_byte == 0xE2u8 # 1st byte of '…'
+# assert reader.read_byte == 0x80u8 # 2nd byte of '…'
+# assert reader.read_char == '�' # Reads the last byte as an invalid char
+# assert reader.read_all == "b"
+# ~~~
+class StringReader
+       super BytesReader
+
+       autoinit source
+
+       # Source data to read
+       var source: String
+
+       init do bytes = source.to_bytes
+
+       redef fun close
+       do
+               source = ""
+               super
+       end
 end
index 06cdcfe..b728e42 100644 (file)
@@ -1205,7 +1205,7 @@ private class FlatBufferByteIterator
 
        var curr_pos: Int
 
-       init do target_items = target._items
+       init do if isset target._items then target_items = target._items
 
        redef fun index do return curr_pos
 
index fe95094..7ebe6b0 100644 (file)
@@ -82,6 +82,7 @@ class XMLProcessor
                var c = src[pos]
                if not c == '<' then return new XMLError(st_loc, "Expected start of tag, got `{c}`")
                pos += 1
+               if pos >= src.length then return new XMLError(st_loc, "Malformed tag")
                c = src[pos]
                if c == '!' then
                        # Special tag
@@ -236,11 +237,11 @@ class XMLProcessor
        # Parses an xml tag name
        private fun parse_tag_name(delims: Array[Char]): String do
                var idst = pos
-               var c = src[pos]
                var srclen = src.length
-               while pos < srclen and not c.is_whitespace and not delims.has(c) do
+               while pos < srclen do
+                       var c = src[pos]
+                       if c.is_whitespace or delims.has(c) then break
                        pos += 1
-                       c = src[pos]
                end
                return src.substring(idst, pos - idst).trim
        end
index c2c8be5..cdeef2d 100644 (file)
@@ -367,6 +367,10 @@ class BMFontAsset
                var dy = 0.0
                var text_width = 0.0
                var line_sprites = new Array[Sprite]
+               var height = 0.0
+
+               # Has the current line height been added to `height`?
+               var line_height_counted = false
 
                # TextSprite customization
                var max_width = text_sprites.max_width
@@ -397,14 +401,21 @@ class BMFontAsset
                                dy -= line_height
                                if max_height != null and max_height < -dy + line_height then break
                                dx = 0.0
+                               if not line_height_counted then
+                                       # Force to account for empty lines
+                                       height += line_height
+                               end
+                               line_height_counted = false
                                prev_char = null
                                continue
                        else if c == pld then
                                dy -= partial_line_skip
+                               height += partial_line_skip
                                word_break = true
                                continue
                        else if c == plu then
                                dy += partial_line_skip
+                               height -= partial_line_skip # We could keep two heights and return the max
                                word_break = true
                                continue
                        else if c.is_whitespace then
@@ -477,24 +488,27 @@ class BMFontAsset
                                                var w = text[wi]
 
                                                if w == '\n' or w == pld or w == plu or w.is_whitespace or (in_link and w == ']') then break
+
+                                               if not desc.chars.keys.has(w) then
+                                                       var rc = replacement_char
+                                                       if rc == null then continue
+                                                       w = rc
+                                               end
+
                                                word_len += advance(prev_w, w) * scale
                                                prev_w = w
                                        end
 
                                        # Would the line be too long?
                                        if dx + word_len > max_width then
-                                               if text_sprites.wrap then
-                                                       # Wrap
-                                                       justify(line_sprites, text_sprites.align, dx)
-                                                       dy -= line_height
-                                                       if max_height != null and max_height < -dy + line_height then break
-                                                       dx = 0.0
-                                               else
-                                                       # Cut short
-                                                       justify(line_sprites, text_sprites.align, dx)
-                                                       dy -= line_height
-                                                       if max_height != null and max_height < -dy + line_height then break
-                                                       dx = 0.0
+                                               justify(line_sprites, text_sprites.align, dx)
+                                               dy -= line_height
+                                               if max_height != null and max_height < -dy + line_height then break
+                                               dx = 0.0
+                                               line_height_counted = false
+
+                                               if not text_sprites.wrap then
+                                                       # Cut short, skip everything until the next new line
                                                        while c != '\n' and i < text.length - 1 do
                                                                i += 1
                                                                c = text[i]
@@ -531,6 +545,12 @@ class BMFontAsset
                        prev_char = c
 
                        text_width = text_width.max(dx)
+
+                       if not line_height_counted then
+                               # Increase `height` only once per line iff there's a caracter
+                               line_height_counted = true
+                               height += line_height
+                       end
                end
 
                justify(line_sprites, text_sprites.align, dx)
@@ -542,7 +562,7 @@ class BMFontAsset
                end
 
                text_sprites.width = text_width.max(dx)
-               text_sprites.height = dy + line_height
+               text_sprites.height = height
        end
 
        # Character replacing other characters missing from the font
index 4139949..1230c4a 100644 (file)
@@ -21,6 +21,7 @@ import more_models
 import model_dimensions
 import particles
 import selection
+import shadow
 
 redef class App
 
@@ -32,6 +33,9 @@ redef class App
                world_camera.reset_height(10.0)
                world_camera.near = 0.1
 
+               # Cull the invisible triangles in the back of the geometries
+               glCullFace gl_BACK
+
                # Prepare programs
                var programs = [versatile_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram]
                for program in programs do
@@ -46,20 +50,21 @@ redef class App
        # Draw all elements of `actors` and then call `frame_core_flat`
        protected fun frame_core_depth(display: GamnitDisplay)
        do
-               glViewport(0, 0, display.width, display.height)
-               frame_core_dynamic_resolution_before display
+               frame_core_depth_clock.lapse
 
-               # Update cameras on both our programs
-               versatile_program.use
-               versatile_program.mvp.uniform world_camera.mvp_matrix
+               # Compute shadows
+               if light isa LightCastingShadows then
+                       frame_core_shadow_prep display
+                       perfs["gamnit depth shadows"].add frame_core_depth_clock.lapse
+               end
 
-               normals_program.use
-               normals_program.mvp.uniform app.world_camera.mvp_matrix
+               glViewport(0, 0, display.width, display.height)
+               frame_core_dynamic_resolution_before display
+               perfs["gamnit depth dynres"].add frame_core_depth_clock.lapse
 
-               frame_core_depth_clock.lapse
                for actor in actors do
                        for leaf in actor.model.leaves do
-                               leaf.material.draw(actor, leaf)
+                               leaf.material.draw(actor, leaf, app.world_camera)
                        end
                end
                perfs["gamnit depth actors"].add frame_core_depth_clock.lapse
@@ -77,6 +82,9 @@ redef class App
                perfs["gamnit depth ui_sprites"].add frame_core_depth_clock.lapse
 
                frame_core_dynamic_resolution_after display
+
+               # Debug, show the light point of view
+               #frame_core_shadow_debug display
        end
 
        private var frame_core_depth_clock = new Clock
index c1c1619..588b321 100644 (file)
@@ -113,6 +113,9 @@ abstract class Model
        # Usually, there is one `LeafModel` per material.
        # At each frame, each material is asked to draw all the live `LeafModel` instaces.
        fun leaves: Array[LeafModel] is abstract
+
+       # Sub-models with names, usually declared in the asset file
+       var named_parts = new Map[Text, Model]
 end
 
 # Model composed of one or many other `LeafModel`
@@ -165,7 +168,7 @@ abstract class Material
        # This method is called on many materials for many `actor` and `model` at each frame.
        # It is expected to use a `GLProgram` and call an equivalent to `glDrawArrays`.
        # However, it should not call `glClear` nor `GamnitDisplay::flip`.
-       fun draw(actor: Actor, model: LeafModel) do end
+       fun draw(actor: Actor, model: LeafModel, camera: Camera) do end
 end
 
 # Mesh with all geometry data
@@ -203,23 +206,32 @@ class Mesh
 end
 
 # Source of light
-#
-# Instances of this class define a light source position and type.
-class Light
-
-       # TODO point light, spotlight, directional light, etc.
+abstract class Light
 
        # Center of this light source in world coordinates
        var position = new Point3d[Float](0.0, 1000.0, 0.0)
 end
 
+# Basic light projected from a single point
+class PointLight
+       super Light
+end
+
+# Source of light casting shadows
+abstract class LightCastingShadows
+       super Light
+
+       # View from the camera, used for shadow mapping, implemented by sub-classes
+       fun camera: Camera is abstract
+end
+
 redef class App
 
        # Live actors to be drawn on screen
        var actors = new Array[Actor]
 
        # Single light of the scene
-       var light = new Light
+       var light: Light = new PointLight is writable
 
        # TODO move `actors & light` to a scene object
        # TODO support more than 1 light
diff --git a/lib/gamnit/depth/more_lights.nit b/lib/gamnit/depth/more_lights.nit
new file mode 100644 (file)
index 0000000..3512f27
--- /dev/null
@@ -0,0 +1,110 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# More implementations of `Light`
+module more_lights
+
+import depth_core
+intrude import cameras_cache
+
+# TODO
+#class PointLight
+#class Spotlight
+
+# Sun-like light projecting parallel rays
+class ParallelLight
+       super LightCastingShadows
+
+       # Angle to the light source, around the X axis
+       var pitch = 0.0 is writable
+
+       # Angle to the light source, around the Y axis
+       var yaw = 0.0 is writable
+
+       # Depth texture width, in world coordinates
+       var width = 100.0 is writable
+
+       # Depth texture height, in world coordinates
+       var height = 100.0 is writable
+
+       # Viewport depth, centered on `app.world_camera`
+       var depth = 500.0 is writable
+
+       redef var camera = new ParallelLightCamera(app.display.as(not null), self) is lazy
+end
+
+private class ParallelLightCamera
+       super Camera
+
+       var light: ParallelLight
+
+       # Rotation matrix produced by the current rotation of the camera
+       fun rotation_matrix: Matrix
+       do
+               var view = new Matrix.identity(4)
+               view.rotate(light.yaw,   0.0, 1.0, 0.0)
+               view.rotate(light.pitch, 1.0, 0.0, 0.0)
+               return view
+       end
+
+       private fun create_mvp_matrix: Matrix
+       do
+               var near = -light.depth/2.0
+               var far = light.depth/2.0
+
+               var view = new Matrix.identity(4)
+               view.translate(-position.x, -position.y, -position.z)
+               view = view * rotation_matrix
+               var projection = new Matrix.orthogonal(-light.width/2.0, light.width/2.0,
+                                                      -light.height/2.0, light.height/2.0,
+                                                      near, far)
+               return view * projection
+       end
+
+       redef fun mvp_matrix
+       do
+               var m = mvp_matrix_cache
+               if m == null or check_position_changed then
+                       m = create_mvp_matrix
+                       mvp_matrix_cache = m
+               end
+               return m
+       end
+
+       private var pitch_cache = 0.0
+       private var yaw_cache = 0.0
+       private var width_cache = 0.0
+       private var height_cache = 0.0
+       private var depth_cache = 0.0
+
+       redef fun check_position_changed
+       do
+               if super then return true
+
+               if light.pitch != pitch_cache or
+                  light.yaw != yaw_cache or
+                  light.width != width_cache or
+                  light.height != height_cache or
+                  light.depth != depth_cache then
+                       pitch_cache = light.pitch
+                       yaw_cache = light.yaw
+                       width_cache = light.width
+                       height_cache = light.height
+                       depth_cache = light.depth
+                       return true
+               end
+
+               return false
+       end
+end
index 3b320f9..2546da3 100644 (file)
@@ -17,6 +17,8 @@ module more_materials
 
 intrude import depth_core
 intrude import flat
+intrude import shadow
+import more_lights
 
 redef class Material
        # Get the default blueish material
@@ -45,10 +47,11 @@ class SmoothMaterial
        # The RGB values should be premultiplied by the alpha value.
        var specular_color: Array[Float] is writable
 
-       redef fun draw(actor, model)
+       redef fun draw(actor, model, camera)
        do
                var program = app.versatile_program
                program.use
+               program.mvp.uniform camera.mvp_matrix
 
                var mesh = model.mesh
 
@@ -70,11 +73,8 @@ class SmoothMaterial
                program.use_map_specular.uniform false
                program.tex_coord.array_enabled = false
 
-               # Lights
-               program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
-
                # Camera
-               program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+               program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
 
                # Colors from the material
                var a = actor.alpha
@@ -85,6 +85,8 @@ class SmoothMaterial
                program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
                                               specular_color[2]*a, specular_color[3]*a)
 
+               setup_lights(actor, model, camera, program)
+
                # Execute draw
                if mesh.indices.is_empty then
                        glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
@@ -92,6 +94,46 @@ class SmoothMaterial
                        glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
        end
+
+       private fun setup_lights(actor: Actor, model: LeafModel, camera: Camera, program: BlinnPhongProgram)
+       do
+               # TODO use a list of lights
+
+               # Light, for Lambert and Blinn-Phong
+               var light = app.light
+               if light isa ParallelLight then
+                       program.light_kind.uniform 1
+
+                       # Vector parallel to the light source
+                       program.light_center.uniform(
+                               -light.pitch.sin * light.yaw.sin,
+                               light.pitch.cos,
+                               -light.yaw.cos)
+               else if light isa PointLight then
+                       program.light_kind.uniform 2
+
+                       # Position of the light source
+                       program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
+               else
+                       program.light_kind.uniform 0
+               end
+
+               # Draw projected shadows?
+               if not light isa LightCastingShadows or not app.shadow_depth_texture_available then
+                       program.use_shadows.uniform false
+                       return
+               else program.use_shadows.uniform true
+
+               # Light point of view
+               program.light_mvp.uniform light.camera.mvp_matrix
+
+               # Depth texture
+               glActiveTexture gl_TEXTURE4
+               glBindTexture(gl_TEXTURE_2D, app.shadow_context.depth_texture)
+               program.depth_texture.uniform 4
+               program.depth_texture_size.uniform app.shadow_resolution.to_f
+               program.depth_texture_taps.uniform 2 # TODO make configurable
+       end
 end
 
 # Material with potential `diffuse_texture` and `specular_texture`
@@ -110,7 +152,7 @@ class TexturedMaterial
        # Bump map TODO
        private var normals_texture: nullable Texture = null is writable
 
-       redef fun draw(actor, model)
+       redef fun draw(actor, model, camera)
        do
                var mesh = model.mesh
 
@@ -164,6 +206,7 @@ class TexturedMaterial
                        program.use_map_bump.uniform false
                end
 
+               program.mvp.uniform camera.mvp_matrix
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
 
@@ -180,8 +223,6 @@ class TexturedMaterial
                                var xd = sample_used_texture.offset_right - xa
                                var ya = sample_used_texture.offset_top
                                var yd = sample_used_texture.offset_bottom - ya
-                               xd *= 0.999
-                               yd *= 0.999
 
                                var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
                                for i in [0..mesh.texture_coords.length/2[ do
@@ -209,8 +250,11 @@ class TexturedMaterial
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
 
-               program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
-               program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+               # Light
+               setup_lights(actor, model, camera, program)
+
+               # Camera
+               program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
 
                if mesh.indices.is_empty then
                        glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
@@ -227,11 +271,11 @@ end
 class NormalsMaterial
        super Material
 
-       redef fun draw(actor, model)
+       redef fun draw(actor, model, camera)
        do
                var program = app.normals_program
                program.use
-               program.mvp.uniform app.world_camera.mvp_matrix
+               program.mvp.uniform camera.mvp_matrix
 
                var mesh = model.mesh
 
@@ -279,13 +323,15 @@ class BlinnPhongProgram
                // Vertex normal
                attribute vec3 normal;
 
-               // Model view projection matrix
+               // Camera model view projection matrix
                uniform mat4 mvp;
 
                uniform mat4 rotation;
 
                // Lights config
+               uniform lowp int light_kind;
                uniform vec3 light_center;
+               uniform mat4 light_mvp;
 
                // Coordinates of the camera
                uniform vec3 camera;
@@ -295,17 +341,28 @@ class BlinnPhongProgram
                varying vec3 v_normal;
                varying vec4 v_to_light;
                varying vec4 v_to_camera;
+               varying vec4 v_depth_pos;
 
                void main()
                {
                        vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
                        gl_Position = pos * mvp;
+                       v_depth_pos = (pos * light_mvp) * 0.5 + 0.5;
 
                        // Pass varyings to the fragment shader
                        v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
                        v_normal = normalize(vec4(normal, 0.0) * rotation).xyz;
-                       v_to_light = normalize(vec4(light_center, 1.0) - pos);
                        v_to_camera = normalize(vec4(camera, 1.0) - pos);
+
+                       if (light_kind == 0) {
+                               // No light
+                       } else if (light_kind == 1) {
+                               // Parallel
+                               v_to_light = normalize(vec4(light_center, 1.0));
+                       } else {
+                               // Point light (and others?)
+                               v_to_light = normalize(vec4(light_center, 1.0) - pos);
+                       }
                }
                """ @ glsl_vertex_shader
 
@@ -317,6 +374,7 @@ class BlinnPhongProgram
                varying vec3 v_normal;
                varying vec4 v_to_light;
                varying vec4 v_to_camera;
+               varying vec4 v_depth_pos;
 
                // Colors
                uniform vec4 ambient_color;
@@ -343,6 +401,60 @@ class BlinnPhongProgram
                uniform bool use_map_normal;
                uniform sampler2D map_normal;
 
+               // Shadow
+               uniform lowp int light_kind;
+               uniform bool use_shadows;
+               uniform sampler2D depth_texture;
+               uniform float depth_texture_size;
+               uniform int depth_texture_taps;
+
+               // Shadow effect on the diffuse colors of the fragment at offset `x, y`
+               float shadow_lookup(vec2 depth_coord, float x, float y) {
+                       float tap_width = 1.0;
+                       float pixel_size = tap_width/depth_texture_size;
+
+                       vec2 offset = vec2(x * pixel_size * v_depth_pos.w,
+                                          y * pixel_size * v_depth_pos.w);
+                       depth_coord += offset;
+
+                       float depth = v_depth_pos.z/v_depth_pos.w;
+                       //vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
+                       if (depth_coord.x < 0.0 || depth_coord.x > 1.0 || depth_coord.y < 0.0 || depth_coord.y > 1.0) {
+                               // Out of the shadow map texture
+                               //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // debug, red out of the light view
+                               return 1.0;
+                       }
+
+                       float shadow_depth = texture2D(depth_texture, depth_coord).r;
+                       float bias = 0.0001;
+                       if (shadow_depth == 1.0) {
+                               // Too far to be in depth texture
+                               return 1.0;
+                       } else if (shadow_depth <= depth - bias) {
+                               // In a shadow
+                               //gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); // debug, blue shadows
+                               return 0.2; // TODO replace with a configurable ambient light
+                       }
+
+                       //gl_FragColor = vec4(0.0, 1.0-(shadow_depth-depth), 0.0, 1.0); // debug, green lit surfaces
+                       return 1.0;
+               }
+
+               // Shadow effect on the diffuse colors of the fragment
+               float shadow() {
+                       if (!use_shadows) return 1.0;
+
+                       vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
+
+                       float taps = float(depth_texture_taps);
+                       float tap_step = 2.00/taps;
+                       float sum = 0.0;
+                       for (float x = -1.0; x <= 0.99; x += tap_step)
+                               for (float y = -1.0; y <= 0.99; y += tap_step)
+                                       sum += shadow_lookup(depth_coord, x, y);
+                       return sum / taps / taps;
+               }
+
                void main()
                {
                        // Normal
@@ -356,28 +468,44 @@ class BlinnPhongProgram
                        vec4 ambient = ambient_color;
                        if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
 
-                       // Diffuse Lambert light
-                       vec3 to_light = v_to_light.xyz;
-                       float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
+                       if (light_kind == 0) {
+                               // No light, show diffuse and ambient
 
-                       vec4 diffuse = lambert * diffuse_color;
-                       if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
+                               vec4 diffuse = diffuse_color;
+                               if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
 
-                       // Specular Phong light
-                       float s = 0.0;
-                       if (lambert > 0.0) {
-                               vec3 l = reflect(-to_light, normal);
-                               s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
-                               s = pow(s, 8.0); // TODO make this `shininess` a material attribute
-                       }
+                               gl_FragColor = ambient + diffuse;
+                       } else {
+                               // Parallel light or point light (1 or 2)
+
+                               // Diffuse Lambert light
+                               vec3 to_light = v_to_light.xyz;
+                               float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
+
+                               vec4 diffuse = lambert * diffuse_color;
+                               if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
+
+                               // Specular Phong light
+                               float s = 0.0;
+                               if (lambert > 0.0) {
+                                       // In light
+                                       vec3 l = reflect(-to_light, normal);
+                                       s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
+                                       s = pow(s, 8.0); // TODO make this `shininess` a material attribute
+
+                                       // Shadows
+                                       diffuse *= shadow();
+                               }
 
-                       vec4 specular = s * specular_color;
-                       if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
+                               vec4 specular = s * specular_color;
+                               if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
+
+                               gl_FragColor = ambient + diffuse + specular;
+                       }
 
-                       gl_FragColor = ambient + diffuse + specular;
                        if (gl_FragColor.a < 0.01) discard;
 
-                       //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug
+                       //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug normals
                }
                """ @ glsl_fragment_shader
 
@@ -393,7 +521,7 @@ class BlinnPhongProgram
        # Should this program use the texture `map_diffuse`?
        var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
 
-       # Diffuser texture unit
+       # Diffuse texture unit
        var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
 
        # Should this program use the texture `map_specular`?
@@ -423,9 +551,27 @@ class BlinnPhongProgram
        # Specular color
        var specular_color = uniforms["specular_color"].as(UniformVec4) is lazy
 
-       # Center position of the light
+       # Kind of lights: 0 -> no light, 1 -> parallel, 2 -> point
+       var light_kind = uniforms["light_kind"].as(UniformInt) is lazy
+
+       # Center position of the light *or* vector to parallel light source
        var light_center = uniforms["light_center"].as(UniformVec3) is lazy
 
+       # Light model view projection matrix
+       var light_mvp = uniforms["light_mvp"].as(UniformMat4) is lazy
+
+       # Should shadow be drawn? Would use `depth_texture` and `light_mvp`.
+       var use_shadows = uniforms["use_shadows"].as(UniformBool) is lazy
+
+       # Diffuse texture unit
+       var depth_texture = uniforms["depth_texture"].as(UniformSampler2D) is lazy
+
+       # Size, in pixels, of `depth_texture`
+       var depth_texture_size = uniforms["depth_texture_size"].as(UniformFloat) is lazy
+
+       # Times to tap the `depth_texture`, square root (set to 3 for a total of 9 taps)
+       var depth_texture_taps = uniforms["depth_texture_taps"].as(UniformInt) is lazy
+
        # Camera position
        var camera = uniforms["camera"].as(UniformVec3) is lazy
 
@@ -438,7 +584,7 @@ class BlinnPhongProgram
        # Scaling per vertex
        var scale = uniforms["scale"].as(UniformFloat) is lazy
 
-       # Model view projection matrix
+       # Camera model view projection matrix
        var mvp = uniforms["mvp"].as(UniformMat4) is lazy
 end
 
index eb148c9..9ec6d2e 100644 (file)
@@ -43,6 +43,8 @@ class ModelAsset
 
        redef fun load
        do
+               if loaded then return
+
                var ext = path.file_extension
                if ext == "obj" then
                        load_obj_file
@@ -59,6 +61,22 @@ class ModelAsset
                loaded = true
        end
 
+       private fun lazy_load
+       do
+               if loaded then return
+
+               # Lazy load
+               load
+
+               # Print errors when lazy loading only
+               if errors.length == 1 then
+                       print_error errors.first
+               else if errors.length > 1 then
+                       print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
+                       print_error errors.join("\n* ")
+               end
+       end
+
        private fun load_obj_file
        do
                # Read .obj description from assets
@@ -83,37 +101,33 @@ class ModelAsset
                if debug_gamnit then assert obj_def.is_coherent
 
                # Build models
-               var converter = new ModelFromObj(path, obj_def)
-               converter.models leaves_cache
+               var converter = new BuildModelFromObj(path, obj_def)
+               converter.fill_leaves self
                errors.add_all converter.errors
        end
 
        redef fun leaves
        do
-               if not loaded then
-                       # Lazy load
-                       load
-
-                       # Print errors when lazy loading only
-                       if errors.length == 1 then
-                               print_error errors.first
-                       else if errors.length > 1 then
-                               print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
-                               print_error errors.join("\n* ")
-                       end
-               end
-
+               lazy_load
                return leaves_cache
        end
 
        private var leaves_cache = new Array[LeafModel]
+
+       redef fun named_parts
+       do
+               lazy_load
+               return named_leaves_cache
+       end
+
+       private var named_leaves_cache = new Map[String, Model]
 end
 
-# Short-lived service to convert an `ObjDef` to `models`
+# Short-lived service to convert an `ObjDef` to `fill_leaves`
 #
 # Limitations: This service only support faces with 3 or 4 vertices.
 # Faces with more vertices should be triangulated by the modeling tool.
-private class ModelFromObj
+private class BuildModelFromObj
 
        # Path to the .obj file in the assets folder, used to find .mtl files
        var path: String
@@ -121,22 +135,28 @@ private class ModelFromObj
        # Parsed .obj definition
        var obj_def: ObjDef
 
-       # Errors raised by calls to `models`
+       # Errors raised by calls to `fill_leaves`
        var errors = new Array[Error]
 
-       # Fill `leaves` with models described in `obj_def`
-       fun models(leaves: Array[LeafModel])
+       # Fill `leaves` with objects described in `obj_def`
+       fun fill_leaves(target_model: ModelAsset)
        do
-               # Sort faces by material
-               var mtl_to_faces = new MultiHashMap[String, ObjFace]
-               for face in obj_def.faces do
-                       var mtl_lib_name = face.material_lib
-                       var mtl_name = face.material_name
-
-                       var full_name = ""
-                       if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+               var leaves = target_model.leaves_cache
 
-                       mtl_to_faces[full_name].add face
+               # Sort faces by material
+               var obj_mtl_to_faces = new Map[ObjObj, MultiHashMap[String, ObjFace]]
+               for obj in obj_def.objects do
+                       var mtl_to_faces = new MultiHashMap[String, ObjFace]
+                       obj_mtl_to_faces[obj] = mtl_to_faces
+                       for face in obj.faces do
+                               var mtl_lib_name = face.material_lib
+                               var mtl_name = face.material_name
+
+                               var full_name = ""
+                               if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+
+                               mtl_to_faces[full_name].add face
+                       end
                end
 
                # Load material libs
@@ -158,38 +178,43 @@ private class ModelFromObj
                        mtl_libs[asset_path] = mtl_lib
                end
 
-               # Create 1 mesh per material, and prepare materials
+               # Create 1 mesh per material per object, and prepare materials
                var mesh_to_mtl = new Map[Mesh, nullable MtlDef]
+               var mesh_to_name = new Map[Mesh, String]
                var texture_names = new Set[String]
-               for full_name, faces in mtl_to_faces do
-
-                       # Create mesh
-                       var mesh = new Mesh
-                       mesh.vertices = vertices(faces)
-                       mesh.normals = normals(faces)
-                       mesh.texture_coords = texture_coords(faces)
-
-                       # Material
-                       var mtl_def = null
-
-                       var mtl_lib_name = faces.first.material_lib
-                       var mtl_name = faces.first.material_name
-                       if mtl_lib_name != null and mtl_name != null then
-                               var asset_path = self.path.dirname / mtl_lib_name
-                               var mtl_lib = mtl_libs[asset_path]
-                               var mtl = mtl_lib.get_or_null(mtl_name)
-                               if mtl != null then
-                                       mtl_def = mtl
-
-                                       for e in mtl.maps do
-                                               texture_names.add self.path.dirname / e
+               for obj in obj_def.objects do
+                       var mtl_to_faces = obj_mtl_to_faces[obj]
+                       for mtl_path, faces in mtl_to_faces do
+
+                               # Create mesh
+                               var mesh = new Mesh
+                               mesh.vertices = vertices(faces)
+                               mesh.normals = normals(faces)
+                               mesh.texture_coords = texture_coords(faces)
+
+                               # Material
+                               var mtl_def = null
+
+                               var mtl_lib_name = faces.first.material_lib
+                               var mtl_name = faces.first.material_name
+                               if mtl_lib_name != null and mtl_name != null then
+                                       var asset_path = self.path.dirname / mtl_lib_name
+                                       var mtl_lib = mtl_libs[asset_path]
+                                       var mtl = mtl_lib.get_or_null(mtl_name)
+                                       if mtl != null then
+                                               mtl_def = mtl
+
+                                               for e in mtl.maps do
+                                                       texture_names.add self.path.dirname / e
+                                               end
+                                       else
+                                               errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                        end
-                               else
-                                       errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                end
-                       end
 
-                       mesh_to_mtl[mesh] = mtl_def
+                               mesh_to_mtl[mesh] = mtl_def
+                               mesh_to_name[mesh] = obj.name
+                       end
                end
 
                # Load textures need for these materials
@@ -241,12 +266,27 @@ private class ModelFromObj
                end
 
                # Create models and store them
+               var name_to_leaves = new MultiHashMap[String, LeafModel]
                for mesh, mtl_def in mesh_to_mtl do
+
                        var material = materials.get_or_null(mtl_def)
                        if material == null then material = new Material
 
                        var model = new LeafModel(mesh, material)
                        leaves.add model
+
+                       name_to_leaves[mesh_to_name[mesh]].add model
+               end
+
+               # Collect objects with a name
+               for name, models in name_to_leaves do
+                       if models.length == 1 then
+                               target_model.named_leaves_cache[name] = models.first
+                       else
+                               var named_model = new CompositeModel
+                               named_model.leaves.add_all models
+                               target_model.named_leaves_cache[name] = named_model
+                       end
                end
        end
 
diff --git a/lib/gamnit/depth/shadow.nit b/lib/gamnit/depth/shadow.nit
new file mode 100644 (file)
index 0000000..213b461
--- /dev/null
@@ -0,0 +1,477 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Shadow mapping using a depth texture
+#
+# The default light does not cast any shadows. It can be changed to a
+# `ParallelLight` in client games to cast sun-like shadows:
+#
+# ~~~
+# import more_lights
+#
+# var sun = new ParallelLight
+# sun.pitch = 0.25*pi
+# sun.yaw = 0.25*pi
+# app.light = sun
+# ~~~
+module shadow
+
+intrude import gamnit::depth_core
+
+redef class App
+
+       # Resolution of the shadow texture, defaults to 4096 pixels
+       #
+       # TODO make configurable / ask the hardware for gl_MAX_TEXTURE_SIZE
+       var shadow_resolution = 4096
+
+       # Are shadows supported by the current hardware configuration?
+       #
+       # The implementation may change in the future, but it currently relies on
+       # the GL extension `GL_EOS_depth_texture`.
+       var supports_shadows: Bool is lazy do
+               return display.as(not null).gl_extensions.has("GL_OES_depth_texture")
+       end
+
+       # Is `shadow_context.depth_texture` ready to be used?
+       fun shadow_depth_texture_available: Bool
+       do return supports_shadows and shadow_context.depth_texture != -1
+
+       private var shadow_depth_program = new ShadowDepthProgram
+
+       private var perf_clock_shadow = new Clock is lazy
+
+       redef fun on_create
+       do
+               super
+
+               var program = shadow_depth_program
+               program.compile_and_link
+               var error = program.error
+               assert error == null else print_error error
+       end
+
+       private var shadow_context: ShadowContext = create_shadow_context is lazy
+
+       private fun create_shadow_context: ShadowContext
+       do
+               var display = display
+               assert display != null
+
+               var context = new ShadowContext
+               context.prepare_once(display, shadow_resolution)
+               return context
+       end
+
+       # Update the depth texture from the light point of view
+       #
+       # This method updates `shadow_context.depth_texture`.
+       protected fun frame_core_shadow_prep(display: GamnitDisplay)
+       do
+               if not supports_shadows then return
+
+               var light = app.light
+               if not light isa LightCastingShadows then return
+
+               perf_clock_shadow.lapse
+
+               # Make sure there's no errors pending
+               assert glGetError == gl_NO_ERROR
+
+               # Bind the framebuffer and make sure it is OK
+               glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.light_view_framebuffer)
+               assert glGetError == gl_NO_ERROR
+               assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
+
+               # Draw to fill the depth texture and only the depth
+               glViewport(0, 0, shadow_resolution, shadow_resolution)
+               glColorMask(false, false, false, false)
+               glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
+               assert glGetError == gl_NO_ERROR
+
+               # Update light position
+               var camera = light.camera
+               camera.position.x = app.world_camera.position.x
+               camera.position.y = app.world_camera.position.y
+               camera.position.z = app.world_camera.position.z
+
+               # Draw all actors
+               for actor in actors do
+                       for leaf in actor.model.leaves do
+                               leaf.material.draw_depth(actor, leaf, camera)
+                       end
+               end
+
+               # Take down, bring back default values
+               glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
+               glColorMask(true, true, true, true)
+
+               perfs["gamnit shadows prep"].add perf_clock_shadow.lapse
+       end
+
+       # ---
+       # Debug: show light view in the bottom left of the screen
+
+       # Lazy load the debugging program
+       private var shadow_debug_program: LightPointOfViewProgram is lazy do
+               var program = new LightPointOfViewProgram
+               program.compile_and_link
+               var error = program.error
+               assert error == null else print_error error
+               return program
+       end
+
+       # Draw the light view in the bottom left of the screen, for debugging only
+       #
+       # The shadow depth texture is a square that can be deformed by this projection.
+       protected fun frame_core_shadow_debug(display: GamnitDisplay)
+       do
+               if not supports_shadows then
+                       print_error "Error: Shadows are not supported by the current hardware configuration"
+                       return
+               end
+
+               perf_clock_shadow.lapse
+
+               var program = shadow_debug_program
+
+               glBindBuffer(gl_ARRAY_BUFFER, shadow_context.buffer_array)
+               glViewport(0, 0, display.width/3, display.height/3)
+               glClear gl_DEPTH_BUFFER_BIT
+               program.use
+
+               # Uniforms
+               glActiveTexture gl_TEXTURE0
+               glBindTexture(gl_TEXTURE_2D, shadow_context.depth_texture)
+               program.texture.uniform 0
+
+               # Attributes
+               var sizeof_gl_float = 4
+               var n_floats = 3
+               glEnableVertexAttribArray program.coord.location
+               glVertexAttribPointeri(program.coord.location, n_floats, gl_FLOAT, false, 0, 0)
+               var offset = 4 * n_floats * sizeof_gl_float
+
+               n_floats = 2
+               glEnableVertexAttribArray program.tex_coord.location
+               glVertexAttribPointeri(program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset)
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               # Draw
+               glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               # Take down
+               glBindBuffer(gl_ARRAY_BUFFER, 0)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               sys.perfs["gamnit shadow debug"].add app.perf_clock_shadow.lapse
+       end
+end
+
+# Handles to reused GL buffers and texture
+private class ShadowContext
+
+       # Real screen framebuffer
+       var screen_framebuffer: Int = -1
+
+       # Framebuffer for the light point of view
+       var light_view_framebuffer: Int = -1
+
+       # Depth attached to `light_view_framebuffer`
+       var depth_texture: Int = -1
+
+       # Buffer name for vertex data
+       var buffer_array: Int = -1
+
+       # Prepare all attributes once per resolution change
+       fun prepare_once(display: GamnitDisplay, shadow_resolution: Int)
+       do
+               assert display.gl_extensions.has("GL_OES_depth_texture")
+
+               # Set aside the real screen framebuffer name
+               var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
+               self.screen_framebuffer = screen_framebuffer
+
+               # Framebuffer
+               var framebuffer = glGenFramebuffers(1).first
+               glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
+               assert glIsFramebuffer(framebuffer)
+               self.light_view_framebuffer = framebuffer
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               # Depth & texture/color
+               var textures = glGenTextures(1)
+               self.depth_texture = textures[0]
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               resize(display, shadow_resolution)
+               assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
+
+               # Array buffer
+               buffer_array = glGenBuffers(1).first
+               glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
+               assert glIsBuffer(buffer_array)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               ## coord
+               var data = new Array[Float]
+               data.add_all([-1.0, -1.0, 0.0,
+                          1.0, -1.0, 0.0,
+                         -1.0,  1.0, 0.0,
+                          1.0,  1.0, 0.0])
+               ## tex_coord
+               data.add_all([0.0, 0.0,
+                             1.0, 0.0,
+                             0.0, 1.0,
+                             1.0, 1.0])
+               var c_data = new GLfloatArray.from(data)
+               glBufferData(gl_ARRAY_BUFFER, data.length*4, c_data.native_array, gl_STATIC_DRAW)
+
+               glBindBuffer(gl_ARRAY_BUFFER, 0)
+
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+       end
+
+       # Init size or resize `depth_texture`
+       fun resize(display: GamnitDisplay, shadow_resolution: Int)
+       do
+               glBindFramebuffer(gl_FRAMEBUFFER, light_view_framebuffer)
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               # Depth texture
+               var depth_texture = self.depth_texture
+               glActiveTexture gl_TEXTURE0
+               glBindTexture(gl_TEXTURE_2D, depth_texture)
+               glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
+               glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
+               glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
+               glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               # TODO support hardware shadows with GL ES 3.0 or GL_EXT_shadow_samplers
+               #glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_COMPARE_MODE, ...)
+
+               glTexImage2D(gl_TEXTURE_2D, 0, gl_DEPTH_COMPONENT,
+                            shadow_resolution, shadow_resolution,
+                            0, gl_DEPTH_COMPONENT, gl_UNSIGNED_SHORT, new Pointer.nul)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               glFramebufferTexture2D(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_TEXTURE_2D, depth_texture, 0)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+
+               # Check if the framebuffer is complete and valid
+               assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
+
+               # Take down
+               glBindTexture(gl_TEXTURE_2D, 0)
+               glBindFramebuffer(gl_FRAMEBUFFER, 0)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+       end
+
+       var destroyed = false
+
+       fun destroy
+       do
+               if destroyed then return
+               destroyed = true
+
+               # Free the buffer
+               glDeleteBuffers([buffer_array])
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+               buffer_array = -1
+
+               # Free the array and framebuffer plus its attachments
+               glDeleteBuffers([buffer_array])
+               glDeleteFramebuffers([light_view_framebuffer])
+               glDeleteTextures([depth_texture])
+       end
+end
+
+redef class Material
+       # Optimized draw of `model`, a part of `actor`, from the view of `camera`
+       #
+       # This drawing should only produce usable depth data. The default behavior,
+       # uses `shadow_depth_program`.
+       protected fun draw_depth(actor: Actor, model: LeafModel, camera: Camera)
+       do
+               var program = app.shadow_depth_program
+               program.use
+               program.mvp.uniform camera.mvp_matrix
+
+               var mesh = model.mesh
+
+               program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
+               program.scale.uniform actor.scale
+               program.use_map_diffuse.uniform false
+
+               program.tex_coord.array_enabled = true
+               program.tex_coord.array(mesh.texture_coords, 2)
+
+               program.coord.array_enabled = true
+               program.coord.array(mesh.vertices, 3)
+
+               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+
+               if mesh.indices.is_empty then
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
+               else
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+               end
+       end
+
+end
+
+# Efficiently draw actors from the light view
+class ShadowDepthProgram
+       super GamnitProgramFromSource
+
+       redef var vertex_shader_source = """
+               // Vertex coordinates
+               attribute vec4 coord;
+
+               // Vertex translation
+               uniform vec4 translation;
+
+               // Vertex scaling
+               uniform float scale;
+
+               // Vertex coordinates on textures
+               attribute vec2 tex_coord;
+
+               // Vertex normal
+               attribute vec3 normal;
+
+               // Model view projection matrix
+               uniform mat4 mvp;
+
+               // Rotation matrix
+               uniform mat4 rotation;
+
+               // Output for the fragment shader
+               varying vec2 v_tex_coord;
+
+               void main()
+               {
+                       vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
+                       gl_Position = pos * mvp;
+
+                       // Pass varyings to the fragment shader
+                       v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
+               }
+               """ @ glsl_vertex_shader
+
+       redef var fragment_shader_source = """
+               precision mediump float;
+
+               // Diffuse map
+               uniform bool use_map_diffuse;
+               uniform sampler2D map_diffuse;
+
+               varying vec2 v_tex_coord;
+
+               void main()
+               {
+                       if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a <= 0.01) {
+                               discard;
+                       }
+               }
+               """ @ glsl_fragment_shader
+
+       # Vertices coordinates
+       var coord = attributes["coord"].as(AttributeVec4) is lazy
+
+       # Should this program use the texture `map_diffuse`?
+       var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
+
+       # Diffuse texture unit
+       var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
+
+       # Coordinates on the textures, per vertex
+       var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
+
+       # Diffuse color
+       var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
+
+       # Translation applied to each vertex
+       var translation = uniforms["translation"].as(UniformVec4) is lazy
+
+       # Rotation matrix
+       var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+
+       # Scaling per vertex
+       var scale = uniforms["scale"].as(UniformFloat) is lazy
+
+       # Model view projection matrix
+       var mvp = uniforms["mvp"].as(UniformMat4) is lazy
+end
+
+# Draw the camera point of view on screen
+private class LightPointOfViewProgram
+       super GamnitProgramFromSource
+
+       redef var vertex_shader_source = """
+               // Vertex coordinates
+               attribute vec3 coord;
+
+               // Vertex coordinates on textures
+               attribute vec2 tex_coord;
+
+               // Output to the fragment shader
+               varying vec2 v_coord;
+
+               void main()
+               {
+                       gl_Position = vec4(coord, 1.0);
+                       v_coord = tex_coord;
+               }
+               """ @ glsl_vertex_shader
+
+       redef var fragment_shader_source = """
+               precision mediump float;
+
+               // Virtual screen texture / color attachment
+               uniform sampler2D texture0;
+
+               // Input from the vertex shader
+               varying vec2 v_coord;
+
+               void main()
+               {
+                       gl_FragColor = texture2D(texture0, v_coord);
+               }
+               """ @ glsl_fragment_shader
+
+       # Vertices coordinates
+       var coord = attributes["coord"].as(AttributeVec3) is lazy
+
+       # Coordinates on the textures, per vertex
+       var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
+
+       # Visible texture
+       var texture = uniforms["texture0"].as(UniformSampler2D) is lazy
+end
index 2b694c6..868b359 100644 (file)
@@ -72,4 +72,7 @@ class GamnitDisplay
        #
        # The implementation varies per platform.
        fun feed_events do end
+
+       # Extensions to OpenGL ES 2.0 supported by the current configuration
+       var gl_extensions: Array[String] is lazy do return glGetString(gl_EXTENSIONS).split(' ')
 end
index 8e5566c..b14cb33 100644 (file)
@@ -78,3 +78,8 @@ redef class TextureAsset
                jni_env.pop_local_frame
        end
 end
+
+redef class Pointer
+       # Disable out premultiply as we use only the one from Android
+       redef fun premultiply_alpha(width, height) do end
+end
index e3ab243..dcfd963 100644 (file)
@@ -89,12 +89,12 @@ redef class GamnitDisplay
 
                # Audio support
                var inited = mix.initialize(mix_init_flags)
-               assert inited != mix_init_flags else
+               if inited != mix_init_flags then
                        print_error "Failed to load SDL2 mixer format supports: {mix.error}"
                end
 
-               var opened = mix.open_audio(44100, mix.default_format, 2, 1024)
-               assert opened else
+               var open = mix.open_audio(44100, mix.default_format, 2, 1024)
+               if not open then
                        print_error "Failed to initialize SDL2 mixer: {mix.error}"
                end
 
@@ -108,8 +108,8 @@ redef class GamnitDisplay
 
        # SDL2 mixer initialization flags
        #
-       # Defaults to all available formats.
-       var mix_init_flags: MixInitFlags = mix.flac | mix.mod | mix.mp3 | mix.ogg is lazy, writable
+       # Defaults to FLAC, MP3 and OGG.
+       var mix_init_flags: MixInitFlags = mix.flac | mix.mp3 | mix.ogg is lazy, writable
 
        # Close the SDL display
        fun close_sdl
index 97efba6..fb78730 100644 (file)
@@ -136,7 +136,11 @@ redef class App
 
                # Draw
                glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
+               gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
 
+               # Take down
+               glBindBuffer(gl_ARRAY_BUFFER, 0)
                gl_error = glGetError
                assert gl_error == gl_NO_ERROR else print_error gl_error
 
@@ -159,7 +163,7 @@ redef class App
        end
 end
 
-# Program drawing the dynamic screen to the real screen
+# Handles to reused GL buffers and texture
 private class DynamicContext
 
        # Real screen framebuffer
@@ -177,8 +181,6 @@ private class DynamicContext
        # Buffer name for vertex data
        var buffer_array: Int = -1
 
-       var float_per_vertex: Int is lazy do return 4 + 4 + 3
-
        # Prepare all attributes once per resolution change
        fun prepare_once(display: GamnitDisplay, max_dynamic_resolution_ratio: Float)
        do
@@ -248,7 +250,7 @@ private class DynamicContext
                # Depth
                glBindRenderbuffer(gl_RENDERBUFFER, depthbuffer)
                assert glIsRenderbuffer(depthbuffer)
-               glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPNENT16, width, height)
+               glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPONENT16, width, height)
                glFramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_RENDERBUFFER, depthbuffer)
                var gl_error = glGetError
                assert gl_error == gl_NO_ERROR else print_error gl_error
diff --git a/lib/gamnit/examples/fonts_showcase/assets/corner.png b/lib/gamnit/examples/fonts_showcase/assets/corner.png
new file mode 100644 (file)
index 0000000..12f7986
Binary files /dev/null and b/lib/gamnit/examples/fonts_showcase/assets/corner.png differ
index d64c304..33a342c 100644 (file)
@@ -22,9 +22,12 @@ redef class App
        # Asset font used to display text
        var font = new BMFontAsset("Josefin_Sans/font.fnt")
 
-       # Anchor texture used to identify the anchor coordinate of each `TextSprites`
+       # Anchor texture identifying the anchor coordinates of each `TextSprites`
        var anchor = new Texture("anchor.png")
 
+       # Bottom right corner
+       var corner = new Texture("corner.png")
+
        redef fun on_create
        do
                super
@@ -170,6 +173,12 @@ redef class App
 
                # Add the anchor effects to all TextSprites
                for t in texts do ui_sprites.add new Sprite(anchor, t.anchor)
+
+               for t in texts do
+                       # Bottom right
+                       var br = t.anchor.offset(t.width*(1.0-t.align), -t.height*(1.0-t.valign), 1.0)
+                       ui_sprites.add new Sprite(corner, br)
+               end
        end
 
        redef fun accept_event(event)
index fccf2ae..bd26cbd 100644 (file)
@@ -36,6 +36,13 @@ redef class App
                var display = new GamnitDisplay
                display.setup
                self.display = display
+
+               # Print the current GL configuration, for debugging
+               print "GL vendor: {glGetString(gl_VENDOR)}"
+               print "GL renderer: {glGetString(gl_RENDERER)}"
+               print "GL version: {glGetString(gl_VERSION)}"
+               print "GLSL version: {glGetString(gl_SHADING_LANGUAGE_VERSION)}"
+               print "GL extensions: {glGetString(gl_EXTENSIONS)}"
        end
 
        # Core of the frame logic, executed only when the display is visible
index e9f8499..069c653 100644 (file)
@@ -47,6 +47,7 @@ import model_parser_base
 # var parser = new ObjFileParser(obj_src)
 # var parsed_obj = parser.parse
 # assert parsed_obj.is_coherent
+# assert parsed_obj.objects.first.name == "Cube"
 # ~~~
 class ObjFileParser
        super StringProcessor
@@ -60,6 +61,7 @@ class ObjFileParser
        # Execute parsing of `src` to extract an `ObjDef`
        fun parse: nullable ObjDef
        do
+               var obj_obj = null
                while not eof do
                        var token = read_token
                        if token.is_empty or token == "#" then
@@ -78,7 +80,7 @@ class ObjFileParser
                                geometry.params.add vec
                        else if token == "f" then # Faces
                                var face = read_face
-                               geometry.faces.add face
+                               if obj_obj != null then obj_obj.faces.add face
                        else if token == "mtllib" then
                                current_material_lib = read_until_eol_or_comment
                        else if token == "usemtl" then
@@ -87,6 +89,8 @@ class ObjFileParser
                        # TODO other line type headers
                        else if token == "s" then
                        else if token == "o" then
+                               obj_obj = new ObjObj(read_until_eol_or_comment)
+                               geometry.objects.add obj_obj
                        else if token == "g" then
                        end
                        skip_eol
@@ -152,15 +156,17 @@ class ObjDef
        # Surface parameters
        var params = new Array[Vec3]
 
-       # Faces
-       var faces = new Array[ObjFace]
+       # Sub-objects
+       var objects = new Array[ObjObj]
 
        # Relative paths to referenced material libraries
        fun material_libs: Set[String] do
                var libs = new Set[String]
-               for face in faces do
-                       var lib = face.material_lib
-                       if lib != null then libs.add lib
+               for obj in objects do
+                       for face in obj.faces do
+                               var lib = face.material_lib
+                               if lib != null then libs.add lib
+                       end
                end
                return libs
        end
@@ -173,10 +179,12 @@ class ObjDef
        # be executed at each execution of a game.
        fun is_coherent: Bool
        do
-               for f in faces do
-                       if f.vertices.length < 3 then return error("ObjFace with less than 3 vertices")
+               for obj in objects do
+                       for f in obj.faces do
+                               if f.vertices.length < 3 then return error("Face with less than 3 vertices")
+                       end
 
-                       for v in f.vertices do
+                       for f in obj.faces do for v in f.vertices do
                                var i = v.vertex_point_index
                                if i < 1 then return error("Vertex point index < 1")
                                if i > vertex_points.length then return error("Vertex point index > than length")
@@ -205,9 +213,19 @@ class ObjDef
        end
 end
 
+# Sub-object within an `ObjDef`
+class ObjObj
+
+       # Sub-object name as declared in the source file
+       var name: String
+
+       # Sub-object faces
+       var faces = new Array[ObjFace]
+end
+
 # Flat surface of an `ObjDef`
 class ObjFace
-       # Vertex composing this surface, thene should be 3 or more
+       # Vertex composing this surface, there should be 3 or more
        var vertices = new Array[ObjVertex]
 
        # Relative path to the .mtl material lib
index 95afff8..dc4fc7b 100644 (file)
@@ -62,13 +62,17 @@ class RemoteServer
        var socket: nullable TCPStream = null
 
        # Is this connection connected?
-       fun connected: Bool do return socket != null and socket.connected
+       fun connected: Bool
+       do
+               var socket = socket
+               return socket != null and socket.connected
+       end
 
-       # `BinarySerializer` used to send data to this client through `socket`
-       var writer: BinarySerializer is noinit
+       # `MsgPackSerializer` used to send data to this client through `socket`
+       var writer: MsgPackSerializer is noinit
 
-       # `BinaryDeserializer` used to receive data from this client through `socket`
-       var reader: BinaryDeserializer is noinit
+       # `MsgPackDeserializer` used to receive data from this client through `socket`
+       var reader: MsgPackDeserializer is noinit
 
        # Attempt connection with the remote server
        fun connect: Bool
@@ -83,9 +87,9 @@ class RemoteServer
                end
 
                # Setup serialization
-               writer = new BinarySerializer(socket)
+               writer = new MsgPackSerializer(socket)
                writer.cache = new AsyncCache(false)
-               reader = new BinaryDeserializer(socket)
+               reader = new MsgPackDeserializer(socket)
                writer.link reader
 
                return true
@@ -104,21 +108,21 @@ class RemoteServer
 
                # App name
                var app_name = sys.handshake_app_name
-               socket.write_string app_name
+               socket.serialize_msgpack app_name
 
-               var server_app = socket.read_string
+               var server_app = socket.deserialize_msgpack("String")
                if server_app != app_name then
-                       print_error "Handshake Error: server app name is '{server_app}'"
+                       print_error "Handshake Error: server app name is '{server_app or else "<invalid>"}'"
                        socket.close
                        return false
                end
 
                # App version
-               socket.write_string sys.handshake_app_version
+               socket.serialize_msgpack sys.handshake_app_version
 
-               var server_version = socket.read_string
+               var server_version = socket.deserialize_msgpack("String")
                if server_version != sys.handshake_app_version then
-                       print_error "Handshake Error: server version is different '{server_version}'"
+                       print_error "Handshake Error: server version is different '{server_version or else "<invalid>"}'"
                        socket.close
                        return false
                end
index 579145b..339aa9b 100644 (file)
@@ -16,7 +16,7 @@
 module common
 
 import socket
-import binary::serialization
+import msgpack
 
 # Unique name of the application to use in the handshake
 #
index 70cf002..0a78d43 100644 (file)
@@ -160,18 +160,18 @@ class RemoteClient
        # Is this client connected?
        fun connected: Bool do return socket.connected
 
-       # `BinarySerializer` used to send data to this client through `socket`
-       var writer: BinarySerializer is noinit
+       # `MsgPackSerializer` used to send data to this client through `socket`
+       var writer: MsgPackSerializer is noinit
 
-       # `BinaryDeserializer` used to receive data from this client through `socket`
-       var reader: BinaryDeserializer is noinit
+       # `MsgPackDeserializer` used to receive data from this client through `socket`
+       var reader: MsgPackDeserializer is noinit
 
        init
        do
                # Setup serialization
-               writer = new BinarySerializer(socket)
+               writer = new MsgPackSerializer(socket)
                writer.cache = new AsyncCache(true)
-               reader = new BinaryDeserializer(socket)
+               reader = new MsgPackDeserializer(socket)
                writer.link reader
        end
 
@@ -182,29 +182,29 @@ class RemoteClient
 
                # Make sure it is the same app
                var server_app = sys.handshake_app_name
-               var client_app = socket.read_string
+               var client_app = socket.deserialize_msgpack
                if server_app != client_app then
-                       print_error "Server Error: Client app name is '{client_app}'"
+                       print_error "Server Error: Client app name is '{client_app or else "<invalid>"}'"
 
                        # Send an empty string so the client read it and give up
-                       socket.write_string ""
+                       socket.serialize_msgpack ""
                        socket.close
                        return false
                end
 
-               socket.write_string server_app
+               socket.serialize_msgpack server_app
 
                # App version
                var app_version = sys.handshake_app_version
-               var client_version = socket.read_string
+               var client_version = socket.deserialize_msgpack
                if client_version != app_version then
-                       print_error "Handshake Error: client version is different '{client_version}'"
-                       socket.write_string ""
+                       print_error "Handshake Error: client version is different '{client_version or else "<invalid>"}'"
+                       socket.serialize_msgpack ""
                        socket.close
                        return false
                end
 
-               socket.write_string app_version
+               socket.serialize_msgpack app_version
 
                return true
        end
index 6859c76..fc02530 100644 (file)
@@ -162,6 +162,14 @@ class UniformBool
        fun uniform(val: Bool) do uniform_1i(location, if val then 1 else 0)
 end
 
+# Shader uniform of GLSL type `int`
+class UniformInt
+       super Uniform
+
+       # Set this uniform value
+       fun uniform(val: Int) do uniform_1i(location, val)
+end
+
 # Shader uniform of GLSL type `vec4`
 class UniformFloat
        super Uniform
@@ -230,6 +238,7 @@ end
 class InactiveUniform
        super InactiveVariable
        super UniformBool
+       super UniformInt
        super UniformFloat
        super UniformSampler2D
        super UniformVec2
@@ -408,6 +417,8 @@ abstract class GamnitProgram
                        var uniform
                        if typ == gl_BOOL then
                                uniform = new UniformBool(gl_program, name, location, size)
+                       else if typ == gl_INT then
+                               uniform = new UniformInt(gl_program, name, location, size)
                        else if typ == gl_SAMPLER_2D then
                                uniform = new UniformSampler2D(gl_program, name, location, size)
                        else if typ == gl_FLOAT then
index 4fb98ac..2aa405d 100644 (file)
@@ -168,13 +168,13 @@ class CustomTexture
                for i in [0..4[ do cpixels[offset+i] = bytes[i]
        end
 
-       # Overwrite all pixels with `color`
+       # Overwrite all pixels with `color`, return `self`
        #
        # The argument `color` should be an array of up to 4 floats (RGBA).
        # If `color` has less than 4 items, the missing items are replaced by 1.0.
        #
        # Require: `not loaded`
-       fun fill(color: Array[Float])
+       fun fill(color: Array[Float]): SELF
        do
                assert not loaded else print_error "{class_name}::fill already loaded"
 
@@ -189,6 +189,8 @@ class CustomTexture
                                i += 4
                        end
                end
+
+               return self
        end
 
        redef fun load(force)
@@ -236,8 +238,11 @@ class RootTexture
        private fun load_from_pixels(pixels: Pointer, width, height: Int, format: GLPixelFormat)
        do
                var max_texture_size = glGetIntegerv(gl_MAX_TEXTURE_SIZE, 0)
-               if width > max_texture_size or height > max_texture_size then
-                       error = new Error("Texture {self} width or height is over the GL_MAX_TEXTURE_SIZE of {max_texture_size}")
+               if width > max_texture_size then
+                       error = new Error("Texture width larger than gl_MAX_TEXTURE_SIZE ({max_texture_size}) in {self} at {width}")
+                       return
+               else if height > max_texture_size then
+                       error = new Error("Texture height larger than gl_MAX_TEXTURE_SIZE ({max_texture_size}) in {self} at {height}")
                        return
                end
 
index 2ffb84e..f5b6bec 100644 (file)
@@ -58,7 +58,7 @@ import virtual_gamepad_spritesheet
 redef class App
 
        # Current touch gamepad, still may be invisible
-       var gamepad: nullable VirtualGamepad = null
+       var gamepad: nullable VirtualGamepad = null is writable
 
        # Textures used for `DPad` and available to clients
        var gamepad_spritesheet = new VirtualGamepadSpritesheet
@@ -86,7 +86,7 @@ class VirtualGamepad
        # and `add_button`.
        var controls = new Array[RoundControl]
 
-       # Add a directional pad (`DPad`) to a default location
+       # Add and return a directional pad (`DPad`) to a default location
        #
        # The 4 buttons fire events with the corresponding name in `names`.
        # Items in `names` should be in order of top, left, down and right.
@@ -101,17 +101,22 @@ class VirtualGamepad
        # added by `add_button`.
        #
        # Require: `names == null or names.length == 4`
-       fun add_dpad(names: nullable Array[String])
+       fun add_dpad(names: nullable Array[String]): nullable DPad
        do
                if names == null then names = ["w","a","s","d"]
                assert names.length == 4
 
                if n_dpads == 0 then
-                       controls.add new DPad(app.ui_camera.bottom_left.offset(200.0, 100.0, 0.0), names)
+                       var dpad = new DPad(app.ui_camera.bottom_left.offset(200.0, 100.0, 0.0), names)
+                       controls.add dpad
+                       return dpad
                else if n_dpads == 1 then
-                       controls.add new DPad(app.ui_camera.bottom_right.offset(-200.0, 100.0, 0.0), names)
+                       var dpad = new DPad(app.ui_camera.bottom_right.offset(-200.0, 100.0, 0.0), names)
+                       controls.add dpad
+                       return dpad
                else
                        print_error "Too many DPad ({n_dpads}) in {self}"
+                       return null
                end
        end
 
@@ -132,7 +137,7 @@ class VirtualGamepad
                new Point[Float](-350.0, 350.0),
                new Point[Float](-350.0, 550.0))
 
-       # Add a round button to a default location
+       # Add and return a round button to a default location
        #
        # Fired events use `name`, it should usually correspond to a
        # keyboard key like "space" or "a".
@@ -144,7 +149,7 @@ class VirtualGamepad
        #
        # A maximum of 6 buttons may be added using this method when
        # there is less than 2 `DPad`. Otherwise, only 2 buttons can be added.
-       fun add_button(name: String, texture: Texture)
+       fun add_button(name: String, texture: Texture): nullable RoundButton
        do
                if n_dpads == 2 and button_positions.length == 6 then
                        # Drop the bottom 4 buttons
@@ -156,8 +161,10 @@ class VirtualGamepad
 
                assert button_positions.not_empty else print_error "Too many buttons in {self}"
                var pos = button_positions.shift
-               controls.add new RoundButton(
+               var but = new RoundButton(
                        app.ui_camera.bottom_right.offset(pos.x, pos.y, 0.0), name, texture)
+               controls.add but
+               return but
        end
 
        private fun prepare
index b2d9418..c4ac331 100644 (file)
@@ -31,6 +31,7 @@
 # http://www.khronos.org/opengles/sdk/docs/man/
 module glesv2 is
        pkgconfig
+       no_warning "missing-doc"
        new_annotation glsl_vertex_shader
        new_annotation glsl_fragment_shader
        ldflags("-lGLESv2")@android
@@ -755,7 +756,7 @@ fun gl_RGB565: GLRenderbufferFormat `{ return GL_RGB565; `}
 fun gl_RGB5_A1: GLRenderbufferFormat `{ return GL_RGB5_A1; `}
 
 # 16 depth bits format
-fun gl_DEPTH_COMPNENT16: GLRenderbufferFormat `{ return GL_DEPTH_COMPONENT16; `}
+fun gl_DEPTH_COMPONENT16: GLRenderbufferFormat `{ return GL_DEPTH_COMPONENT16; `}
 
 # 8 stencil bits format
 fun gl_STENCIL_INDEX8: GLRenderbufferFormat `{ return GL_STENCIL_INDEX8; `}
@@ -1206,6 +1207,7 @@ end
 fun gl_ALPHA: GLPixelFormat `{ return GL_ALPHA; `}
 fun gl_RGB: GLPixelFormat `{ return GL_RGB; `}
 fun gl_RGBA: GLPixelFormat `{ return GL_RGBA; `}
+fun gl_DEPTH_COMPONENT: GLPixelFormat `{ return GL_DEPTH_COMPONENT; `}
 
 # Set of buffers as a bitwise OR mask
 extern class GLBuffer `{ GLbitfield `}
@@ -1308,3 +1310,22 @@ fun gl_TEXTURE_BINDING_CUBE_MAP: GLGetParameterName `{ return GL_TEXTURE_BINDING
 fun gl_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: GLGetParameterName `{ return GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING; `}
 fun gl_FRAMEBUFFER_BINDING: GLGetParameterName `{ return GL_FRAMEBUFFER_BINDING; `}
 fun gl_RENDERBUFFER_BINDING: GLGetParameterName `{ return GL_RENDERBUFFER_BINDING; `}
+
+# Return a string describing the current GL configuration
+fun glGetString(name: GLEnum): String do return glGetString_native(name).to_s
+private fun glGetString_native(name: GLEnum): CString `{ return (char*)glGetString(name); `}
+
+# Company responsible for this GL implementation
+fun gl_VENDOR: GLEnum `{ return GL_VENDOR; `}
+
+# Name of the renderer, typically specific to a particular configuration of the hardware platform
+fun gl_RENDERER: GLEnum `{ return GL_RENDERER; `}
+
+# Version or release number
+fun gl_VERSION: GLEnum `{ return GL_VERSION; `}
+
+# Version or release number for the shading language of the form
+fun gl_SHADING_LANGUAGE_VERSION: GLEnum `{ return GL_SHADING_LANGUAGE_VERSION; `}
+
+# Space-separated list of supported extensions to GL
+fun gl_EXTENSIONS: GLEnum `{ return GL_EXTENSIONS; `}
index a06586c..e4316ea 100644 (file)
@@ -19,12 +19,7 @@ import app::data_store
 import cocoa::foundation
 private import json
 
-redef class App
-       redef var data_store = new UserDefaultView
-end
-
-private class UserDefaultView
-       super DataStore
+redef class DataStore
 
        # The `NSUserDefaults` used to implement `DataStore`
        var user_defaults = new NSUserDefaults.standard_user_defaults is lazy
@@ -37,7 +32,16 @@ private class UserDefaultView
 
                # TODO report errors
                var deserializer = new JsonDeserializer(nsstr.to_s)
-               return deserializer.deserialize
+               var deserialized = deserializer.deserialize
+
+               var errors = deserializer.errors
+               if errors.not_empty then
+                       # An update may have broken the versioning compatibility
+                       print_error "{class_name} error at deserialization: {errors.join(", ")}"
+                       return null # Let's be safe
+               end
+
+               return deserialized
        end
 
        redef fun []=(key, value)
diff --git a/lib/json/.gitattributes b/lib/json/.gitattributes
deleted file mode 100644 (file)
index 041d62a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-json_lexer.nit         -diff
-json_parser.nit                -diff
diff --git a/lib/json/.gitignore b/lib/json/.gitignore
deleted file mode 100644 (file)
index 0f08b2b..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-json.concrete_grammar.txt
-json.dfa.dot
-json.gram.dot
-json.lr.dot
-json.lr.txt
-json.nfa.dot
-json_test_parser.nit
diff --git a/lib/json/Makefile b/lib/json/Makefile
deleted file mode 100644 (file)
index a8a5eb9..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-NITCCDIR=../../contrib/nitcc/
-
-pre-build: $(NITCCDIR)src/nitcc
-       $(NITCCDIR)src/nitcc $(NITCCDIR)examples/json.sablecc
-
-$(NITCCDIR)src/nitcc:
-       make -C $(NITCCDIR)
index 26a9952..f13d2fc 100644 (file)
@@ -8,25 +8,16 @@
 # You are allowed to redistribute it and sell it, alone or is a part of
 # another product.
 
-# Errors related to JSON parsing.
-module json::error
+# Intro `JsonParseError` which is exposed by all JSON reading APIs
+module error
 
-import nitcc_runtime
+import parser_base
 
-# Ill-formed JSON.
+# JSON format error at parsing
 class JsonParseError
        super Error
        serialize
 
-       # The location of the error in the original text.
-       var position: nullable Position
-
-       redef fun to_s do
-               var p = position
-               if p isa Position then
-                       return "Error Parsing JSON: [{p}] {super}"
-               else
-                       return super
-               end
-       end
+       # Location of the error in source
+       var location: nullable Location = null
 end
diff --git a/lib/json/json_lexer.nit b/lib/json/json_lexer.nit
deleted file mode 100644 (file)
index 32a82b2..0000000
+++ /dev/null
@@ -1,470 +0,0 @@
-# Lexer generated by nitcc for the grammar json
-module json_lexer is generated, no_warning "missing-doc"
-import nitcc_runtime
-import json_parser
-class Lexer_json
-       super Lexer
-       redef fun start_state do return dfastate_0
-end
-private fun dfastate_0: DFAState0 do return once new DFAState0
-private fun dfastate_1: DFAState1 do return once new DFAState1
-private fun dfastate_2: DFAState2 do return once new DFAState2
-private fun dfastate_3: DFAState3 do return once new DFAState3
-private fun dfastate_4: DFAState4 do return once new DFAState4
-private fun dfastate_5: DFAState5 do return once new DFAState5
-private fun dfastate_6: DFAState6 do return once new DFAState6
-private fun dfastate_7: DFAState7 do return once new DFAState7
-private fun dfastate_8: DFAState8 do return once new DFAState8
-private fun dfastate_9: DFAState9 do return once new DFAState9
-private fun dfastate_10: DFAState10 do return once new DFAState10
-private fun dfastate_11: DFAState11 do return once new DFAState11
-private fun dfastate_12: DFAState12 do return once new DFAState12
-private fun dfastate_13: DFAState13 do return once new DFAState13
-private fun dfastate_14: DFAState14 do return once new DFAState14
-private fun dfastate_15: DFAState15 do return once new DFAState15
-private fun dfastate_16: DFAState16 do return once new DFAState16
-private fun dfastate_17: DFAState17 do return once new DFAState17
-private fun dfastate_18: DFAState18 do return once new DFAState18
-private fun dfastate_19: DFAState19 do return once new DFAState19
-private fun dfastate_20: DFAState20 do return once new DFAState20
-private fun dfastate_21: DFAState21 do return once new DFAState21
-private fun dfastate_22: DFAState22 do return once new DFAState22
-private fun dfastate_23: DFAState23 do return once new DFAState23
-private fun dfastate_24: DFAState24 do return once new DFAState24
-private fun dfastate_25: DFAState25 do return once new DFAState25
-private fun dfastate_26: DFAState26 do return once new DFAState26
-private fun dfastate_27: DFAState27 do return once new DFAState27
-private fun dfastate_28: DFAState28 do return once new DFAState28
-private fun dfastate_29: DFAState29 do return once new DFAState29
-private fun dfastate_30: DFAState30 do return once new DFAState30
-private fun dfastate_31: DFAState31 do return once new DFAState31
-private fun dfastate_32: DFAState32 do return once new DFAState32
-private fun dfastate_33: DFAState33 do return once new DFAState33
-private fun dfastate_34: DFAState34 do return once new DFAState34
-class MyNToken
-       super NToken
-end
-private class DFAState0
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 8 then return null
-               if c <= 10 then return dfastate_1
-               if c <= 31 then return null
-               if c <= 32 then return dfastate_1
-               if c <= 33 then return null
-               if c <= 34 then return dfastate_2
-               if c <= 43 then return null
-               if c <= 44 then return dfastate_3
-               if c <= 45 then return dfastate_4
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_5
-               if c <= 58 then return dfastate_6
-               if c <= 90 then return null
-               if c <= 91 then return dfastate_7
-               if c <= 92 then return null
-               if c <= 93 then return dfastate_8
-               if c <= 101 then return null
-               if c <= 102 then return dfastate_9
-               if c <= 109 then return null
-               if c <= 110 then return dfastate_10
-               if c <= 115 then return null
-               if c <= 116 then return dfastate_11
-               if c <= 122 then return null
-               if c <= 123 then return dfastate_12
-               if c <= 124 then return null
-               if c <= 125 then return dfastate_13
-               return null
-       end
-end
-private class DFAState1
-       super DFAState
-       redef fun is_accept do return true
-       redef fun is_ignored do return true
-       redef fun make_token(position, source) do
-               return null
-       end
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 8 then return null
-               if c <= 10 then return dfastate_1
-               if c <= 31 then return null
-               if c <= 32 then return dfastate_1
-               return null
-       end
-end
-private class DFAState2
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c > 92 then return dfastate_2
-               if c <= 33 then return dfastate_2
-               if c <= 34 then return dfastate_29
-               if c <= 91 then return dfastate_2
-               return dfastate_30
-       end
-end
-private class DFAState3
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39d_44d_39d
-               t.text = ","
-               t.position = position
-               return t
-       end
-end
-private class DFAState4
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_5
-               return null
-       end
-end
-private class DFAState5
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new Nnumber
-               t.text = position.extract(source)
-               t.position = position
-               return t
-       end
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 45 then return null
-               if c <= 46 then return dfastate_24
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_5
-               if c <= 68 then return null
-               if c <= 69 then return dfastate_25
-               if c <= 100 then return null
-               if c <= 101 then return dfastate_25
-               return null
-       end
-end
-private class DFAState6
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39d_58d_39d
-               t.text = ":"
-               t.position = position
-               return t
-       end
-end
-private class DFAState7
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39d_91d_39d
-               t.text = "["
-               t.position = position
-               return t
-       end
-end
-private class DFAState8
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39d_93d_39d
-               t.text = "]"
-               t.position = position
-               return t
-       end
-end
-private class DFAState9
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 96 then return null
-               if c <= 97 then return dfastate_20
-               return null
-       end
-end
-private class DFAState10
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 116 then return null
-               if c <= 117 then return dfastate_17
-               return null
-       end
-end
-private class DFAState11
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 113 then return null
-               if c <= 114 then return dfastate_14
-               return null
-       end
-end
-private class DFAState12
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39d_123d_39d
-               t.text = "\{"
-               t.position = position
-               return t
-       end
-end
-private class DFAState13
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39d_125d_39d
-               t.text = "\}"
-               t.position = position
-               return t
-       end
-end
-private class DFAState14
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 116 then return null
-               if c <= 117 then return dfastate_15
-               return null
-       end
-end
-private class DFAState15
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 100 then return null
-               if c <= 101 then return dfastate_16
-               return null
-       end
-end
-private class DFAState16
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39dtrue_39d
-               t.text = "true"
-               t.position = position
-               return t
-       end
-end
-private class DFAState17
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 107 then return null
-               if c <= 108 then return dfastate_18
-               return null
-       end
-end
-private class DFAState18
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 107 then return null
-               if c <= 108 then return dfastate_19
-               return null
-       end
-end
-private class DFAState19
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39dnull_39d
-               t.text = "null"
-               t.position = position
-               return t
-       end
-end
-private class DFAState20
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 107 then return null
-               if c <= 108 then return dfastate_21
-               return null
-       end
-end
-private class DFAState21
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 114 then return null
-               if c <= 115 then return dfastate_22
-               return null
-       end
-end
-private class DFAState22
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 100 then return null
-               if c <= 101 then return dfastate_23
-               return null
-       end
-end
-private class DFAState23
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new N_39dfalse_39d
-               t.text = "false"
-               t.position = position
-               return t
-       end
-end
-private class DFAState24
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_28
-               return null
-       end
-end
-private class DFAState25
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 42 then return null
-               if c <= 43 then return dfastate_26
-               if c <= 44 then return null
-               if c <= 45 then return dfastate_26
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_27
-               return null
-       end
-end
-private class DFAState26
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_27
-               return null
-       end
-end
-private class DFAState27
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new Nnumber
-               t.text = position.extract(source)
-               t.position = position
-               return t
-       end
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_27
-               return null
-       end
-end
-private class DFAState28
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new Nnumber
-               t.text = position.extract(source)
-               t.position = position
-               return t
-       end
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_28
-               if c <= 68 then return null
-               if c <= 69 then return dfastate_25
-               if c <= 100 then return null
-               if c <= 101 then return dfastate_25
-               return null
-       end
-end
-private class DFAState29
-       super DFAState
-       redef fun is_accept do return true
-       redef fun make_token(position, source) do
-               var t = new Nstring
-               t.text = position.extract(source)
-               t.position = position
-               return t
-       end
-end
-private class DFAState30
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 33 then return null
-               if c <= 34 then return dfastate_2
-               if c <= 46 then return null
-               if c <= 47 then return dfastate_2
-               if c <= 91 then return null
-               if c <= 92 then return dfastate_2
-               if c <= 97 then return null
-               if c <= 98 then return dfastate_2
-               if c <= 101 then return null
-               if c <= 102 then return dfastate_2
-               if c <= 109 then return null
-               if c <= 110 then return dfastate_2
-               if c <= 113 then return null
-               if c <= 114 then return dfastate_2
-               if c <= 115 then return null
-               if c <= 116 then return dfastate_2
-               if c <= 117 then return dfastate_31
-               return null
-       end
-end
-private class DFAState31
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_32
-               if c <= 64 then return null
-               if c <= 90 then return dfastate_32
-               if c <= 96 then return null
-               if c <= 122 then return dfastate_32
-               return null
-       end
-end
-private class DFAState32
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_33
-               if c <= 64 then return null
-               if c <= 90 then return dfastate_33
-               if c <= 96 then return null
-               if c <= 122 then return dfastate_33
-               return null
-       end
-end
-private class DFAState33
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_34
-               if c <= 64 then return null
-               if c <= 90 then return dfastate_34
-               if c <= 96 then return null
-               if c <= 122 then return dfastate_34
-               return null
-       end
-end
-private class DFAState34
-       super DFAState
-       redef fun trans(char) do
-               var c = char.code_point
-               if c <= 47 then return null
-               if c <= 57 then return dfastate_2
-               if c <= 64 then return null
-               if c <= 90 then return dfastate_2
-               if c <= 96 then return null
-               if c <= 122 then return dfastate_2
-               return null
-       end
-end
diff --git a/lib/json/json_parser.nit b/lib/json/json_parser.nit
deleted file mode 100644 (file)
index 28094c0..0000000
+++ /dev/null
@@ -1,876 +0,0 @@
-# Parser generated by nitcc for the grammar json
-module json_parser is generated, no_warning("missing-doc","old-init")
-import nitcc_runtime
-class Parser_json
-       super Parser
-       redef fun start_state do return state_Start
-end
-private fun state_Start: LRStateStart do return once new LRStateStart
-private fun state_value: LRStatevalue do return once new LRStatevalue
-private fun state_number: LRStatenumber do return once new LRStatenumber
-private fun state_string: LRStatestring do return once new LRStatestring
-private fun state__39dtrue_39d: LRState_39dtrue_39d do return once new LRState_39dtrue_39d
-private fun state__39dfalse_39d: LRState_39dfalse_39d do return once new LRState_39dfalse_39d
-private fun state__39dnull_39d: LRState_39dnull_39d do return once new LRState_39dnull_39d
-private fun state__39d_123d_39d: LRState_39d_123d_39d do return once new LRState_39d_123d_39d
-private fun state__39d_91d_39d: LRState_39d_91d_39d do return once new LRState_39d_91d_39d
-private fun state_value_32dEof: LRStatevalue_32dEof do return once new LRStatevalue_32dEof
-private fun state__39d_123d_39d_32dmembers: LRState_39d_123d_39d_32dmembers do return once new LRState_39d_123d_39d_32dmembers
-private fun state__39d_123d_39d_32d_39d_125d_39d: LRState_39d_123d_39d_32d_39d_125d_39d do return once new LRState_39d_123d_39d_32d_39d_125d_39d
-private fun state__39d_123d_39d_32dpair: LRState_39d_123d_39d_32dpair do return once new LRState_39d_123d_39d_32dpair
-private fun state__39d_123d_39d_32dstring: LRState_39d_123d_39d_32dstring do return once new LRState_39d_123d_39d_32dstring
-private fun state__39d_91d_39d_32delements: LRState_39d_91d_39d_32delements do return once new LRState_39d_91d_39d_32delements
-private fun state__39d_91d_39d_32d_39d_93d_39d: LRState_39d_91d_39d_32d_39d_93d_39d do return once new LRState_39d_91d_39d_32d_39d_93d_39d
-private fun state__39d_91d_39d_32dvalue: LRState_39d_91d_39d_32dvalue do return once new LRState_39d_91d_39d_32dvalue
-private fun state__39d_123d_39d_32dmembers_32d_39d_125d_39d: LRState_39d_123d_39d_32dmembers_32d_39d_125d_39d do return once new LRState_39d_123d_39d_32dmembers_32d_39d_125d_39d
-private fun state__39d_123d_39d_32dmembers_32d_39d_44d_39d: LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d do return once new LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d
-private fun state__39d_123d_39d_32dstring_32d_39d_58d_39d: LRState_39d_123d_39d_32dstring_32d_39d_58d_39d do return once new LRState_39d_123d_39d_32dstring_32d_39d_58d_39d
-private fun state__39d_91d_39d_32delements_32d_39d_93d_39d: LRState_39d_91d_39d_32delements_32d_39d_93d_39d do return once new LRState_39d_91d_39d_32delements_32d_39d_93d_39d
-private fun state__39d_91d_39d_32delements_32d_39d_44d_39d: LRState_39d_91d_39d_32delements_32d_39d_44d_39d do return once new LRState_39d_91d_39d_32delements_32d_39d_44d_39d
-private fun state__39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair: LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair do return once new LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair
-private fun state__39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue: LRState_39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue do return once new LRState_39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue
-private fun state__39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue: LRState_39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue do return once new LRState_39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue
-private fun goto_Nvalue: Goto_Nvalue do return once new Goto_Nvalue
-private fun reduce_Nvalue_number(parser: Parser) do
-               # REDUCE value::value_number=number
-               var n0 = parser.pop.as(Nnumber)
-               var p1 = new Nvalue_number(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_string(parser: Parser) do
-               # REDUCE value::value_string=string
-               var n0 = parser.pop.as(Nstring)
-               var p1 = new Nvalue_string(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_true(parser: Parser) do
-               # REDUCE value::value_true='true'
-               var n0 = parser.pop.as(N_39dtrue_39d)
-               var p1 = new Nvalue_true(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_false(parser: Parser) do
-               # REDUCE value::value_false='false'
-               var n0 = parser.pop.as(N_39dfalse_39d)
-               var p1 = new Nvalue_false(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_null(parser: Parser) do
-               # REDUCE value::value_null='null'
-               var n0 = parser.pop.as(N_39dnull_39d)
-               var p1 = new Nvalue_null(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_object_95d0(parser: Parser) do
-               # REDUCE value::value_object_0='{' members '}'
-               var n2 = parser.pop.as(N_39d_125d_39d)
-               var n1 = parser.pop.as(Nmembers)
-               var n0 = parser.pop.as(N_39d_123d_39d)
-               var p1 = new Nvalue_object(n0, n1, n2)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_object_95d1(parser: Parser) do
-               # REDUCE value::value_object_1='{' '}'
-               var n1 = parser.pop.as(N_39d_125d_39d)
-               var n0 = parser.pop.as(N_39d_123d_39d)
-               var p1 = new Nvalue_object(n0, null, n1)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_array_95d0(parser: Parser) do
-               # REDUCE value::value_array_0='[' elements ']'
-               var n2 = parser.pop.as(N_39d_93d_39d)
-               var n1 = parser.pop.as(Nelements)
-               var n0 = parser.pop.as(N_39d_91d_39d)
-               var p1 = new Nvalue_array(n0, n1, n2)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_array_95d1(parser: Parser) do
-               # REDUCE value::value_array_1='[' ']'
-               var n1 = parser.pop.as(N_39d_93d_39d)
-               var n0 = parser.pop.as(N_39d_91d_39d)
-               var p1 = new Nvalue_array(n0, null, n1)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nvalue)
-end
-private fun goto_Nmembers: Goto_Nmembers do return once new Goto_Nmembers
-private fun reduce_Nmembers_tail(parser: Parser) do
-               # REDUCE members::members_tail=members ',' pair
-               var n2 = parser.pop.as(Npair)
-               var n1 = parser.pop.as(N_39d_44d_39d)
-               var n0 = parser.pop.as(Nmembers)
-               var p1 = new Nmembers_tail(n0, n1, n2)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nmembers)
-end
-private fun reduce_Nmembers_head(parser: Parser) do
-               # REDUCE members::members_head=pair
-               var n0 = parser.pop.as(Npair)
-               var p1 = new Nmembers_head(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nmembers)
-end
-private fun goto_Npair: Goto_Npair do return once new Goto_Npair
-private fun reduce_Npair(parser: Parser) do
-               # REDUCE pair::pair=string ':' value
-               var n2 = parser.pop.as(Nvalue)
-               var n1 = parser.pop.as(N_39d_58d_39d)
-               var n0 = parser.pop.as(Nstring)
-               var p1 = new Npair(n0, n1, n2)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Npair)
-end
-private fun goto_Nelements: Goto_Nelements do return once new Goto_Nelements
-private fun reduce_Nelements_tail(parser: Parser) do
-               # REDUCE elements::elements_tail=elements ',' value
-               var n2 = parser.pop.as(Nvalue)
-               var n1 = parser.pop.as(N_39d_44d_39d)
-               var n0 = parser.pop.as(Nelements)
-               var p1 = new Nelements_tail(n0, n1, n2)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nelements)
-end
-private fun reduce_Nelements_head(parser: Parser) do
-               # REDUCE elements::elements_head=value
-               var n0 = parser.pop.as(Nvalue)
-               var p1 = new Nelements_head(n0)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.goto(goto_Nelements)
-end
-private fun goto_N_start: Goto_N_start do return once new Goto_N_start
-private fun reduce_NStart(parser: Parser) do
-               # REDUCE _start::Start=value Eof
-               var n1 = parser.pop.as(NEof)
-               var n0 = parser.pop.as(Nvalue)
-               var p1 = new NStart(n0, n1)
-               var prod = p1
-               parser.node_stack.push prod
-               parser.stop = true
-end
-redef class NToken
-       # guarded action for state Start
-       # 7 shift(s) and 0 reduce(s)
-       private fun action_sStart(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state value
-       # 1 shift(s) and 0 reduce(s)
-       private fun action_svalue(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '{'
-       # 2 shift(s) and 0 reduce(s)
-       private fun action_s_39d_123d_39d(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '['
-       # 8 shift(s) and 0 reduce(s)
-       private fun action_s_39d_91d_39d(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '{' members
-       # 2 shift(s) and 0 reduce(s)
-       private fun action_s_39d_123d_39d_32dmembers(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '{' string
-       # 1 shift(s) and 0 reduce(s)
-       private fun action_s_39d_123d_39d_32dstring(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '[' elements
-       # 2 shift(s) and 0 reduce(s)
-       private fun action_s_39d_91d_39d_32delements(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '{' members ','
-       # 1 shift(s) and 0 reduce(s)
-       private fun action_s_39d_123d_39d_32dmembers_32d_39d_44d_39d(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '{' string ':'
-       # 7 shift(s) and 0 reduce(s)
-       private fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser: Parser) do
-               parser.parse_error
-       end
-       # guarded action for state '[' elements ','
-       # 7 shift(s) and 0 reduce(s)
-       private fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser: Parser) do
-               parser.parse_error
-       end
-end
-class N_39d_123d_39d
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state__39d_123d_39d)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state__39d_123d_39d)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state__39d_123d_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state__39d_123d_39d)
-       end
-       redef fun node_name do return "\'\{\'"
-end
-class N_39d_125d_39d
-       super NToken
-       redef fun action_s_39d_123d_39d(parser) do
-               parser.shift(state__39d_123d_39d_32d_39d_125d_39d)
-       end
-       redef fun action_s_39d_123d_39d_32dmembers(parser) do
-               parser.shift(state__39d_123d_39d_32dmembers_32d_39d_125d_39d)
-       end
-       redef fun node_name do return "\'\}\'"
-end
-class N_39d_91d_39d
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state__39d_91d_39d)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state__39d_91d_39d)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state__39d_91d_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state__39d_91d_39d)
-       end
-       redef fun node_name do return "\'[\'"
-end
-class N_39d_93d_39d
-       super NToken
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state__39d_91d_39d_32d_39d_93d_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements(parser) do
-               parser.shift(state__39d_91d_39d_32delements_32d_39d_93d_39d)
-       end
-       redef fun node_name do return "\']\'"
-end
-class Nnumber
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state_number)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state_number)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state_number)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state_number)
-       end
-       redef fun node_name do return "number"
-end
-class Nstring
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state_string)
-       end
-       redef fun action_s_39d_123d_39d(parser) do
-               parser.shift(state__39d_123d_39d_32dstring)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state_string)
-       end
-       redef fun action_s_39d_123d_39d_32dmembers_32d_39d_44d_39d(parser) do
-               parser.shift(state__39d_123d_39d_32dstring)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state_string)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state_string)
-       end
-       redef fun node_name do return "string"
-end
-class N_39dtrue_39d
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state__39dtrue_39d)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state__39dtrue_39d)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state__39dtrue_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state__39dtrue_39d)
-       end
-       redef fun node_name do return "\'true\'"
-end
-class N_39dfalse_39d
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state__39dfalse_39d)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state__39dfalse_39d)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state__39dfalse_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state__39dfalse_39d)
-       end
-       redef fun node_name do return "\'false\'"
-end
-class N_39dnull_39d
-       super NToken
-       redef fun action_sStart(parser) do
-               parser.shift(state__39dnull_39d)
-       end
-       redef fun action_s_39d_91d_39d(parser) do
-               parser.shift(state__39dnull_39d)
-       end
-       redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
-               parser.shift(state__39dnull_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
-               parser.shift(state__39dnull_39d)
-       end
-       redef fun node_name do return "\'null\'"
-end
-class N_39d_44d_39d
-       super NToken
-       redef fun action_s_39d_123d_39d_32dmembers(parser) do
-               parser.shift(state__39d_123d_39d_32dmembers_32d_39d_44d_39d)
-       end
-       redef fun action_s_39d_91d_39d_32delements(parser) do
-               parser.shift(state__39d_91d_39d_32delements_32d_39d_44d_39d)
-       end
-       redef fun node_name do return "\',\'"
-end
-class N_39d_58d_39d
-       super NToken
-       redef fun action_s_39d_123d_39d_32dstring(parser) do
-               parser.shift(state__39d_123d_39d_32dstring_32d_39d_58d_39d)
-       end
-       redef fun node_name do return "\':\'"
-end
-redef class NEof
-       super NToken
-       redef fun action_svalue(parser) do
-               parser.shift(state_value_32dEof)
-       end
-       redef fun node_name do return "Eof"
-end
-redef class LRGoto
-       private fun goto_s_39d_123d_39d(parser: Parser) do abort
-       private fun goto_s_39d_91d_39d(parser: Parser) do abort
-end
-class Goto_Nvalue
-       super LRGoto
-       redef fun goto_s_39d_91d_39d(parser) do
-               parser.push(state__39d_91d_39d_32dvalue)
-       end
-end
-class Goto_Nmembers
-       super LRGoto
-       redef fun goto_s_39d_123d_39d(parser) do
-               parser.push(state__39d_123d_39d_32dmembers)
-       end
-end
-class Goto_Npair
-       super LRGoto
-       redef fun goto_s_39d_123d_39d(parser) do
-               parser.push(state__39d_123d_39d_32dpair)
-       end
-end
-class Goto_Nelements
-       super LRGoto
-       redef fun goto_s_39d_91d_39d(parser) do
-               parser.push(state__39d_91d_39d_32delements)
-       end
-end
-class Goto_N_start
-       super LRGoto
-end
-class Nvalue
-       super NProd
-       redef fun node_name do return "value"
-end
-class Nvalue_number
-       super Nvalue
-       redef fun node_name do return "value_number"
-       var n_number: Nnumber
-       init(n_number: Nnumber) do
-               self.n_number = n_number
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_number
-               abort
-       end
-end
-class Nvalue_string
-       super Nvalue
-       redef fun node_name do return "value_string"
-       var n_string: Nstring
-       init(n_string: Nstring) do
-               self.n_string = n_string
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_string
-               abort
-       end
-end
-class Nvalue_true
-       super Nvalue
-       redef fun node_name do return "value_true"
-       var n_0: N_39dtrue_39d
-       init(n_0: N_39dtrue_39d) do
-               self.n_0 = n_0
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_0
-               abort
-       end
-end
-class Nvalue_false
-       super Nvalue
-       redef fun node_name do return "value_false"
-       var n_0: N_39dfalse_39d
-       init(n_0: N_39dfalse_39d) do
-               self.n_0 = n_0
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_0
-               abort
-       end
-end
-class Nvalue_null
-       super Nvalue
-       redef fun node_name do return "value_null"
-       var n_0: N_39dnull_39d
-       init(n_0: N_39dnull_39d) do
-               self.n_0 = n_0
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_0
-               abort
-       end
-end
-class Nvalue_object
-       super Nvalue
-       redef fun node_name do return "value_object"
-       var n_0: N_39d_123d_39d
-       var n_members: nullable Nmembers
-       var n_2: N_39d_125d_39d
-       init(n_0: N_39d_123d_39d, n_members: nullable Nmembers, n_2: N_39d_125d_39d) do
-               self.n_0 = n_0
-               self.n_members = n_members
-               self.n_2 = n_2
-       end
-       redef fun number_of_children do return 3
-       redef fun child(i) do
-               if i == 0 then return n_0
-               if i == 1 then return n_members
-               if i == 2 then return n_2
-               abort
-       end
-end
-class Nvalue_array
-       super Nvalue
-       redef fun node_name do return "value_array"
-       var n_0: N_39d_91d_39d
-       var n_elements: nullable Nelements
-       var n_2: N_39d_93d_39d
-       init(n_0: N_39d_91d_39d, n_elements: nullable Nelements, n_2: N_39d_93d_39d) do
-               self.n_0 = n_0
-               self.n_elements = n_elements
-               self.n_2 = n_2
-       end
-       redef fun number_of_children do return 3
-       redef fun child(i) do
-               if i == 0 then return n_0
-               if i == 1 then return n_elements
-               if i == 2 then return n_2
-               abort
-       end
-end
-class Nmembers
-       super NProd
-       redef fun node_name do return "members"
-end
-class Nmembers_tail
-       super Nmembers
-       redef fun node_name do return "members_tail"
-       var n_members: Nmembers
-       var n_1: N_39d_44d_39d
-       var n_pair: Npair
-       init(n_members: Nmembers, n_1: N_39d_44d_39d, n_pair: Npair) do
-               self.n_members = n_members
-               self.n_1 = n_1
-               self.n_pair = n_pair
-       end
-       redef fun number_of_children do return 3
-       redef fun child(i) do
-               if i == 0 then return n_members
-               if i == 1 then return n_1
-               if i == 2 then return n_pair
-               abort
-       end
-end
-class Nmembers_head
-       super Nmembers
-       redef fun node_name do return "members_head"
-       var n_pair: Npair
-       init(n_pair: Npair) do
-               self.n_pair = n_pair
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_pair
-               abort
-       end
-end
-class Npair
-       super NProd
-       redef fun node_name do return "pair"
-       var n_string: Nstring
-       var n_1: N_39d_58d_39d
-       var n_value: Nvalue
-       init(n_string: Nstring, n_1: N_39d_58d_39d, n_value: Nvalue) do
-               self.n_string = n_string
-               self.n_1 = n_1
-               self.n_value = n_value
-       end
-       redef fun number_of_children do return 3
-       redef fun child(i) do
-               if i == 0 then return n_string
-               if i == 1 then return n_1
-               if i == 2 then return n_value
-               abort
-       end
-end
-class Nelements
-       super NProd
-       redef fun node_name do return "elements"
-end
-class Nelements_tail
-       super Nelements
-       redef fun node_name do return "elements_tail"
-       var n_elements: Nelements
-       var n_1: N_39d_44d_39d
-       var n_value: Nvalue
-       init(n_elements: Nelements, n_1: N_39d_44d_39d, n_value: Nvalue) do
-               self.n_elements = n_elements
-               self.n_1 = n_1
-               self.n_value = n_value
-       end
-       redef fun number_of_children do return 3
-       redef fun child(i) do
-               if i == 0 then return n_elements
-               if i == 1 then return n_1
-               if i == 2 then return n_value
-               abort
-       end
-end
-class Nelements_head
-       super Nelements
-       redef fun node_name do return "elements_head"
-       var n_value: Nvalue
-       init(n_value: Nvalue) do
-               self.n_value = n_value
-       end
-       redef fun number_of_children do return 1
-       redef fun child(i) do
-               if i == 0 then return n_value
-               abort
-       end
-end
-class N_start
-       super NProd
-       redef fun node_name do return "_start"
-end
-class NStart
-       super N_start
-       redef fun node_name do return "Start"
-       var n_0: Nvalue
-       var n_1: NEof
-       init(n_0: Nvalue, n_1: NEof) do
-               self.n_0 = n_0
-               self.n_1 = n_1
-       end
-       redef fun number_of_children do return 2
-       redef fun child(i) do
-               if i == 0 then return n_0
-               if i == 1 then return n_1
-               abort
-       end
-end
-# State Start
-private class LRStateStart
-       super LRState
-       redef fun to_s do return "Start"
-       redef fun error_msg do return "value"
-       redef fun action(parser) do
-               parser.peek_token.action_sStart(parser)
-       end
-       redef fun goto(parser, goto) do
-               parser.push(state_value)
-       end
-end
-# State value
-private class LRStatevalue
-       super LRState
-       redef fun to_s do return "value"
-       redef fun error_msg do return "Eof"
-       redef fun action(parser) do
-               parser.peek_token.action_svalue(parser)
-       end
-end
-# State number
-private class LRStatenumber
-       super LRState
-       redef fun to_s do return "number"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_number(parser)
-       end
-end
-# State string
-private class LRStatestring
-       super LRState
-       redef fun to_s do return "string"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_string(parser)
-       end
-end
-# State 'true'
-private class LRState_39dtrue_39d
-       super LRState
-       redef fun to_s do return "\'true\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_true(parser)
-       end
-end
-# State 'false'
-private class LRState_39dfalse_39d
-       super LRState
-       redef fun to_s do return "\'false\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_false(parser)
-       end
-end
-# State 'null'
-private class LRState_39dnull_39d
-       super LRState
-       redef fun to_s do return "\'null\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_null(parser)
-       end
-end
-# State '{'
-private class LRState_39d_123d_39d
-       super LRState
-       redef fun to_s do return "\'\{\'"
-       redef fun error_msg do return "members, pair"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_123d_39d(parser)
-       end
-       redef fun goto(parser, goto) do
-               goto.goto_s_39d_123d_39d(parser)
-       end
-end
-# State '['
-private class LRState_39d_91d_39d
-       super LRState
-       redef fun to_s do return "\'[\'"
-       redef fun error_msg do return "elements, value"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_91d_39d(parser)
-       end
-       redef fun goto(parser, goto) do
-               goto.goto_s_39d_91d_39d(parser)
-       end
-end
-# State value Eof
-private class LRStatevalue_32dEof
-       super LRState
-       redef fun to_s do return "value Eof"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_NStart(parser)
-       end
-end
-# State '{' members
-private class LRState_39d_123d_39d_32dmembers
-       super LRState
-       redef fun to_s do return "\'\{\' members"
-       redef fun error_msg do return "\'\}\', \',\'"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_123d_39d_32dmembers(parser)
-       end
-end
-# State '{' '}'
-private class LRState_39d_123d_39d_32d_39d_125d_39d
-       super LRState
-       redef fun to_s do return "\'\{\' \'\}\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_object_95d1(parser)
-       end
-end
-# State '{' pair
-private class LRState_39d_123d_39d_32dpair
-       super LRState
-       redef fun to_s do return "\'\{\' pair"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nmembers_head(parser)
-       end
-end
-# State '{' string
-private class LRState_39d_123d_39d_32dstring
-       super LRState
-       redef fun to_s do return "\'\{\' string"
-       redef fun error_msg do return "\':\'"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_123d_39d_32dstring(parser)
-       end
-end
-# State '[' elements
-private class LRState_39d_91d_39d_32delements
-       super LRState
-       redef fun to_s do return "\'[\' elements"
-       redef fun error_msg do return "\']\', \',\'"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_91d_39d_32delements(parser)
-       end
-end
-# State '[' ']'
-private class LRState_39d_91d_39d_32d_39d_93d_39d
-       super LRState
-       redef fun to_s do return "\'[\' \']\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_array_95d1(parser)
-       end
-end
-# State '[' value
-private class LRState_39d_91d_39d_32dvalue
-       super LRState
-       redef fun to_s do return "\'[\' value"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nelements_head(parser)
-       end
-end
-# State '{' members '}'
-private class LRState_39d_123d_39d_32dmembers_32d_39d_125d_39d
-       super LRState
-       redef fun to_s do return "\'\{\' members \'\}\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_object_95d0(parser)
-       end
-end
-# State '{' members ','
-private class LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d
-       super LRState
-       redef fun to_s do return "\'\{\' members \',\'"
-       redef fun error_msg do return "pair"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_123d_39d_32dmembers_32d_39d_44d_39d(parser)
-       end
-       redef fun goto(parser, goto) do
-               parser.push(state__39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair)
-       end
-end
-# State '{' string ':'
-private class LRState_39d_123d_39d_32dstring_32d_39d_58d_39d
-       super LRState
-       redef fun to_s do return "\'\{\' string \':\'"
-       redef fun error_msg do return "value"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser)
-       end
-       redef fun goto(parser, goto) do
-               parser.push(state__39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue)
-       end
-end
-# State '[' elements ']'
-private class LRState_39d_91d_39d_32delements_32d_39d_93d_39d
-       super LRState
-       redef fun to_s do return "\'[\' elements \']\'"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nvalue_array_95d0(parser)
-       end
-end
-# State '[' elements ','
-private class LRState_39d_91d_39d_32delements_32d_39d_44d_39d
-       super LRState
-       redef fun to_s do return "\'[\' elements \',\'"
-       redef fun error_msg do return "value"
-       redef fun action(parser) do
-               parser.peek_token.action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser)
-       end
-       redef fun goto(parser, goto) do
-               parser.push(state__39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue)
-       end
-end
-# State '{' members ',' pair
-private class LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair
-       super LRState
-       redef fun to_s do return "\'\{\' members \',\' pair"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nmembers_tail(parser)
-       end
-end
-# State '{' string ':' value
-private class LRState_39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue
-       super LRState
-       redef fun to_s do return "\'\{\' string \':\' value"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Npair(parser)
-       end
-end
-# State '[' elements ',' value
-private class LRState_39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue
-       super LRState
-       redef fun to_s do return "\'[\' elements \',\' value"
-       redef fun error_msg do return ""
-       redef fun action(parser) do
-               reduce_Nelements_tail(parser)
-       end
-end
index 0ac2bea..8dd73f7 100644 (file)
 # Services to read JSON: `from_json_string` and `JsonDeserializer`
 module serialization_read
 
-import ::serialization::caching
-private import ::serialization::engine_tools
+import serialization::caching
+private import serialization::engine_tools
+import serialization::safe
+
 private import static
-private import string_parser
 import poset
 
 # Deserializer from a Json string.
 class JsonDeserializer
        super CachingDeserializer
+       super SafeDeserializer
 
        # Json text to deserialize from.
        private var text: Text
 
-       # Accepted parameterized classes to deserialize
-       #
-       # If `whitelist.empty`, all types are accepted.
-       #
-       # ~~~nitish
-       # import json::serialization
-       #
-       # class MyClass
-       #     serialize
-       # end
-       #
-       # var json_string = """
-       # {"__class" = "MyClass"}
-       # """
-       #
-       # var deserializer = new JsonDeserializer(json_string)
-       # var obj = deserializer.deserialize
-       # assert deserializer.errors.is_empty
-       # assert obj isa MyClass
-       #
-       # deserializer = new JsonDeserializer(json_string)
-       # deserializer.whitelist.add "Array[String]"
-       # deserializer.whitelist.add "AnotherAcceptedClass"
-       # obj = deserializer.deserialize
-       # assert deserializer.errors.length == 1
-       # assert obj == null
-       # ~~~
-       var whitelist = new Array[Text]
-
-       # Should objects be checked if they a subtype of the static type before deserialization?
-       #
-       # Defaults to `true`, as it should always be activated.
-       # It can be turned off to implement the subtype check itself.
-       var check_subtypes = true is writable
-
        # Root json object parsed from input text.
        private var root: nullable Object is noinit
 
@@ -180,10 +147,7 @@ class JsonDeserializer
 
                                        if class_name == null and static_type != null then
                                                # Fallack to the static type, strip the `nullable` prefix
-                                               var prefix = "nullable "
-                                               if static_type.has_prefix(prefix) then
-                                                       class_name = static_type.substring_from(prefix.length)
-                                               else class_name = static_type
+                                               class_name = static_type.strip_nullable
                                        end
                                end
 
@@ -197,19 +161,7 @@ class JsonDeserializer
                                        return object
                                end
 
-                               if whitelist.not_empty and not whitelist.has(class_name) then
-                                       errors.add new Error("Deserialization Error: '{class_name}' not in whitelist")
-                                       return null
-                               end
-
-                               if static_type != null and check_subtypes then
-                                       var static_class = static_type.strip_nullable_and_params
-                                       var dynamic_class = class_name.strip_nullable_and_params
-                                       if not class_inheritance_metamodel.has_edge(dynamic_class, static_class) then
-                                               errors.add new Error("Deserialization Error: `{class_name}` is not a subtype of the static type `{static_type}`")
-                                               return null
-                                       end
-                               end
+                               if not accept(class_name, static_type) then return null
 
                                # advance on path
                                path.push object
@@ -241,6 +193,17 @@ class JsonDeserializer
                                return val.chars.first
                        end
 
+                       # byte?
+                       if kind == "byte" then
+                               var val = object.get_or_null("__val")
+                               if not val isa Int then
+                                       errors.add new Error("Serialization Error: JSON `byte` object does not declare an integer `__val`.")
+                                       return object
+                               end
+
+                               return val.to_b
+                       end
+
                        errors.add new Error("Deserialization Error: JSON object has an unknown `__kind`.")
                        return object
                end
@@ -249,13 +212,8 @@ class JsonDeserializer
                if object isa Array[nullable Object] then
                        # Can we use the static type?
                        if static_type != null then
-                               var prefix = "nullable "
-                               var class_name = if static_type.has(prefix) then
-                                               static_type.substring_from(prefix.length)
-                                       else static_type
-
                                opened_array = object
-                               var value = deserialize_class(class_name)
+                               var value = deserialize_class(static_type.strip_nullable)
                                opened_array = null
                                return value
                        end
@@ -315,6 +273,16 @@ class JsonDeserializer
                        return array
                end
 
+               if object isa String and object.length == 1 and static_type == "Char" then
+                       # Char serialized as a JSON string
+                       return object.chars.first
+               end
+
+               if object isa Int and static_type == "Byte" then
+                       # Byte serialized as an integer
+                       return object.to_b
+               end
+
                return object
        end
 
@@ -410,25 +378,6 @@ redef class Text
                end
                return res
        end
-
-       # Strip the `nullable` prefix and the params from the class name `self`
-       #
-       # ~~~nitish
-       # assert "String".strip_nullable_and_params == "String"
-       # assert "Array[Int]".strip_nullable_and_params == "Array"
-       # assert "Map[Set[String], Set[Int]]".strip_nullable_and_params == "Map"
-       # ~~~
-       private fun strip_nullable_and_params: String
-       do
-               var class_name = to_s
-
-               var prefix = "nullable "
-               if class_name.has_prefix(prefix) then class_name = class_name.substring_from(prefix.length)
-
-               var bracket_index = class_name.index_of('[')
-               if bracket_index == -1 then return class_name
-               return class_name.substring(0, bracket_index)
-       end
 end
 
 redef class SimpleCollection[E]
@@ -558,36 +507,21 @@ end
 private fun class_inheritance_metamodel_json: CString is intern
 
 redef class Sys
-       # Class inheritance graph
-       #
-       # ~~~
-       # var hierarchy = class_inheritance_metamodel
-       # assert hierarchy.has_edge("String", "Object")
-       # assert not hierarchy.has_edge("Object", "String")
-       # ~~~
-       var class_inheritance_metamodel: POSet[String] is lazy do
+       redef var class_inheritance_metamodel is lazy do
                var engine = new JsonDeserializer(class_inheritance_metamodel_json.to_s)
                engine.check_subtypes = false
                engine.whitelist.add_all(
-                       ["String", "POSet[String]", "POSetElement[String]", "HashSet[String]", "HashMap[String, POSetElement[String]]"])
+                       ["String", "POSet[String]", "POSetElement[String]",
+                        "HashSet[String]", "HashMap[String, POSetElement[String]]"])
+
                var poset = engine.deserialize
                if engine.errors.not_empty then
-                       print_error engine.errors.join("\n")
+                       print_error "Deserialization errors in class_inheritance_metamodel:"
+                       print_error engine.errors.join("\n* ")
                        return new POSet[String]
                end
+
                if poset isa POSet[String] then return poset
                return new POSet[String]
        end
 end
-
-redef class Deserializer
-       redef fun deserialize_class(name)
-       do
-               if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
-               if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
-               if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
-               if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
-
-               return super
-       end
-end
index eeb30ff..009e131 100644 (file)
@@ -33,7 +33,7 @@ class JsonSerializer
        #   be deserialized to their original form using `JsonDeserializer`.
        # * Use references when an object has already been serialized so to not duplicate it.
        # * Support cycles in references.
-       # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
+       # * Preserve the Nit `Char` and `Byte` types as special objects.
        # * The generated JSON is standard and can be read by non-Nit programs.
        #   However, some Nit types are not represented by the simplest possible JSON representation.
        #   With the added metadata, it can be complex to read.
@@ -242,10 +242,10 @@ redef class Serializable
        # which is used by all the serialization engines, not just JSON.
        protected fun accept_json_serializer(v: JsonSerializer)
        do
-               var id = v.cache.new_id_for(self)
                v.stream.write "\{"
                v.indent_level += 1
                if not v.plain_json then
+                       var id = v.cache.new_id_for(self)
                        v.new_line_and_indent
                        v.stream.write "\"__kind\": \"obj\", \"__id\": "
                        v.stream.write id.to_s
@@ -286,6 +286,19 @@ redef class Char
        end
 end
 
+redef class Byte
+       redef fun accept_json_serializer(v)
+       do
+               if v.plain_json then
+                       to_i.accept_json_serializer v
+               else
+                       v.stream.write "\{\"__kind\": \"byte\", \"__val\": "
+                       to_i.accept_json_serializer v
+                       v.stream.write "\}"
+               end
+       end
+end
+
 redef class CString
        redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
 end
@@ -317,8 +330,10 @@ end
 redef class SimpleCollection[E]
        redef fun accept_json_serializer(v)
        do
-               # Register as pseudo object
-               if not v.plain_json then
+               if v.plain_json then
+                       serialize_to_pure_json v
+               else
+                       # Register as pseudo object
                        var id = v.cache.new_id_for(self)
                        v.stream.write """{"""
                        v.indent_level += 1
@@ -329,14 +344,12 @@ redef class SimpleCollection[E]
                        v.stream.write class_name
                        v.stream.write """","""
                        v.new_line_and_indent
+
                        v.stream.write """"__items": """
                        serialize_to_pure_json v
+
                        core_serialize_to v
-               else
-                       serialize_to_pure_json v
-               end
 
-               if not v.plain_json then
                        v.indent_level -= 1
                        v.new_line_and_indent
                        v.stream.write "\}"
index 9b68f31..0d25f10 100644 (file)
@@ -22,9 +22,8 @@
 # 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
+import parser_base
+intrude import error
 
 redef class Text
 
@@ -42,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
@@ -117,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 Serializable 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
@@ -146,133 +134,347 @@ redef class FlatText
        end
 end
 
-# A map that can be translated into a JSON object.
-interface JsonMapRead[K: String, V: nullable Serializable]
-       super MapRead[K, V]
-       super Serializable
-end
-
-# A JSON Object.
-class JsonObject
-       super JsonMapRead[String, nullable Serializable]
-       super HashMap[String, nullable Serializable]
-end
-
-# 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
-
-################################################################################
-# Redef parser
-
-redef class Nvalue
-       # The represented value.
-       private fun to_nit_object: nullable Serializable 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
+       # 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
 
-redef class Npair
-       # The represented key.
-       private fun name: String do return n_string.to_nit_string
+       private var parse_str_buf = new FlatBuffer
 
-       # The represented value.
-       private fun value: nullable Serializable do return n_value.to_nit_object
-end
+       # 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
diff --git a/lib/json/string_parser.nit b/lib/json/string_parser.nit
deleted file mode 100644 (file)
index 5015754..0000000
+++ /dev/null
@@ -1,351 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Simple ad-hoc implementation of a JSON parser for String inputs
-module string_parser
-
-import parser_base
-import static
-
-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
-
-       # 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
-
-# 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
-
-redef class Text
-       redef fun parse_json do return (new JSONStringParser(self.to_s)).parse_entity
-end
-
-redef class JsonParseError
-       serialize
-
-       # Location of the error in source
-       var location: nullable Location = null
-end
index a3bfb4f..4b8fdb1 100644 (file)
@@ -56,7 +56,13 @@ redef class Sound
                self.native = native
        end
 
-       redef fun play
+       redef fun play do play_channel(-1, 0)
+
+       # Play this sound on `channel` (or any channel if -1) and return the channel
+       #
+       # Repeat the sound `loops` times, `loops == 0` plays it once,
+       # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+       fun play_channel(channel, loops: Int): Int
        do
                var native = native
 
@@ -70,12 +76,12 @@ redef class Sound
                end
 
                # If there's an error, silently skip
-               if error != null then return
+               if error != null then return -1
                native = self.native
                assert native != null
 
                # Play on any available channel
-               mix.play_channel(-1, native, 0)
+               return mix.play_channel(channel, native, loops)
        end
 end
 
index 5924822..95ddeb1 100644 (file)
@@ -19,15 +19,10 @@ module data_store
 
 import app::data_store
 private import xdg_basedir
-private import sqlite3
+import sqlite3
 private import json
 
-redef class App
-       redef var data_store = new LinuxStore
-end
-
-private class LinuxStore
-       super DataStore
+redef class DataStore
 
        # File path of the Sqlite3 DB file
        fun db_file: String do return "data_store.db"
@@ -35,7 +30,7 @@ private class LinuxStore
        # Sqlite3 table used
        fun db_table: String do return "data_store"
 
-       var db_cache: nullable Sqlite3DB = null
+       private var db_cache: nullable Sqlite3DB = null
 
        # Database to use to implement the `DataStore`
        fun db: nullable Sqlite3DB
@@ -71,6 +66,7 @@ private class LinuxStore
 
                # Prepare SELECT statement
                var stmt = db.select("* FROM {db_table} WHERE key == {key.to_sql_string}")
+               if stmt == null then return null
 
                # Execute statment
                for row in stmt do
@@ -82,6 +78,13 @@ private class LinuxStore
                        var deserializer = new JsonDeserializer(serialized)
                        var deserialized = deserializer.deserialize
 
+                       var errors = deserializer.errors
+                       if errors.not_empty then
+                               # An update may have broken the versioning compatibility
+                               print_error "{class_name} error at deserialization: {errors.join(", ")}"
+                               return null # Let's be safe
+                       end
+
                        return deserialized
                end
 
index d63f3e4..4ae99fb 100644 (file)
@@ -213,15 +213,15 @@ class MarkdownProcessor
                if not line.is_empty and line.leading < 4 and line.value[line.leading] == '[' then
                        pos = line.leading + 1
                        pos = md.read_until(id, pos, ']')
-                       if not id.is_empty and pos + 2 < line.value.length then
+                       if not id.is_empty and pos >= 0 and pos + 2 < line.value.length then
                                if line.value[pos + 1] == ':' then
                                        pos += 2
                                        pos = md.skip_spaces(pos)
-                                       if line.value[pos] == '<' then
+                                       if pos >= 0 and line.value[pos] == '<' then
                                                pos += 1
                                                pos = md.read_until(link, pos, '>')
                                                pos += 1
-                                       else
+                                       else if pos >= 0 then
                                                pos = md.read_until(link, pos, ' ', '\n')
                                        end
                                        if not link.is_empty then
index 1baf7f3..98b271d 100644 (file)
@@ -235,7 +235,7 @@ class MongoClient
        # var db_name = "test_{db_suffix}"
        # var db = client.database(db_name)
        # db.collection("test").insert(new JsonObject)
-       # assert client.database_names.has("test")
+       # assert client.database_names.has(db_name)
        # ~~~
        fun database_names: Array[String] do
                var res = new Array[String]
diff --git a/lib/msgpack/ext.nit b/lib/msgpack/ext.nit
new file mode 100644 (file)
index 0000000..327dbff
--- /dev/null
@@ -0,0 +1,33 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Application specific MessagePack extension `MsgPackExt`
+module ext
+
+import serialization
+
+# Application specific MessagePack extension
+class MsgPackExt
+       serialize
+
+       # Custom type code, in [0..127]
+       var typ: Byte
+
+       # Data bytes
+       var data: Bytes
+
+       redef fun hash do return typ.hash + data.hash*8
+       redef fun ==(o) do return o isa MsgPackExt and o.typ == typ and o.data == data
+       redef fun to_s do return "<{class_name} typ: {typ}, data: {data.chexdigest}>"
+end
diff --git a/lib/msgpack/msgpack.nit b/lib/msgpack/msgpack.nit
new file mode 100644 (file)
index 0000000..929c330
--- /dev/null
@@ -0,0 +1,164 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# MessagePack, an efficient binary serialization format
+#
+# This modules provides services at different levels:
+#
+# * Serialize Nit objects using either the quick and easy `Serializable::serialize_msgpack`
+#   and `Write::serialize_msgpack`, or the extensible `MsgPackSerializer`.
+#
+# * Deserialize MessagePack to Nit objects using the quick and easy methods
+#   `Reader|Bytes::deserialize_msgpack`, or inspect errors with the extensible
+#   `MsgPackDeserializer`.
+#
+# * Read and write MessagePack data at a low-level (for engines and the likes)
+#   by importing `msgpack::read` or `msgpack::write`. These services support
+#   only which support only core Nit types with a corresponding MessagePack type.
+#   See `Reader::read_msgpack` and services on `Writer` including `write_msgpack_ext`.
+#
+# Here we discuss the recommended serialization services supporting core
+# Nit and MessagePack types as well as full Nit objects.
+#
+# ## Primitive MessagePack types
+#
+# Most Nit core types are serialized to the smallest corresponding MessagePack type.
+#
+# ~~~
+# assert false.serialize_msgpack == b"\xC2"
+# assert b"\xC2".deserialize_msgpack == false
+#
+# assert true.serialize_msgpack  == b"\xC3"
+# assert b"\xC3".deserialize_msgpack == true
+#
+# assert 1.234.serialize_msgpack == b"\xCB\x3F\xF3\xBE\x76\xC8\xB4\x39\x58"
+# assert b"\xCB\x3F\xF3\xBE\x76\xC8\xB4\x39\x58".deserialize_msgpack == 1.234
+#
+# assert "ABC".serialize_msgpack == b"\xA3ABC"
+# assert b"\xA3ABC".deserialize_msgpack == "ABC"
+#
+# assert [0x11, 0x22, 0x33].serialize_msgpack(plain=true) == b"\x93\x11\x22\x33"
+# assert b"\x93\x11\x22\x33".deserialize_msgpack == [0x11, 0x22, 0x33]
+#
+# var map = new Map[String, nullable Object]
+# map["i"] = 1
+# map["o"] = null
+# assert map.serialize_msgpack(plain=true) == b"\x82\xA1\x69\x01\xA1\x6F\xC0"
+# ~~~
+#
+# Ints are serialized to the smallest MessagePack type, so a small integer fits
+# in a single byte and larger integers take more bytes as needed.
+#
+# ~~~
+# assert 1.serialize_msgpack            == b"\x01"
+# assert (-32).serialize_msgpack        == b"\xE0"
+# assert 0x7F.serialize_msgpack         == b"\x7F"
+# assert 0x80.serialize_msgpack         == b"\xCC\x80"
+# assert 0x1234.serialize_msgpack       == b"\xCD\x12\x34"
+# assert (-0x1234).serialize_msgpack    == b"\xD1\xED\xCC"
+# assert 0x12345678.serialize_msgpack   == b"\xCE\x12\x34\x56\x78"
+# assert 0x0123456789.serialize_msgpack == b"\xCF\x00\x00\x00\x01\x23\x45\x67\x89"
+#
+# assert b"\x01".deserialize_msgpack                 == 1
+# assert b"\xE0".deserialize_msgpack                 == -32
+# assert b"\x7F".deserialize_msgpack                 == 0x7F
+# assert b"\xCC\x80".deserialize_msgpack             == 0x80
+# assert b"\xCD\x12\x34".deserialize_msgpack         == 0x1234
+# assert b"\xD1\xED\xCC".deserialize_msgpack         == -0x1234
+# assert b"\xCE\x12\x34\x56\x78".deserialize_msgpack == 0x12345678
+# assert b"\xCF\x00\x00\x00\x01\x23\x45\x67\x89".deserialize_msgpack == 0x0123456789
+# ~~~
+#
+# ## Primitive Nit type without a MessagePack equivalent
+#
+# Chars are serialized as a string in plain mode.
+#
+# ~~~
+# assert 'A'.serialize_msgpack(plain=true)      == b"\xA1\x41"
+# assert b"\xA1\x41".deserialize_msgpack == "A" # Not a Char
+# ~~~
+#
+# Or, with metadata, chars are serialized to an ext with id 0x7C.
+#
+# ~~~
+# assert 'A'.serialize_msgpack                      == b"\xD4\x7C\x41"
+# assert b"\xD4\x7C\x41".deserialize_msgpack == 'A'
+# ~~~
+#
+# Byte instances are serialized as an integer in plain mode.
+#
+# ~~~
+# assert 0x01u8.serialize_msgpack(plain=true) == b"\x01"
+# assert b"\x01".deserialize_msgpack   == 1 # Not a Byte
+# ~~~
+#
+# Or, with metadata, byte instances are serialized to an ext with id 0x7E.
+#
+# ~~~
+# assert 0x01u8.serialize_msgpack                   == b"\xD4\x7E\x01"
+# assert b"\xD4\x7E\x01".deserialize_msgpack == 0x01u8
+# ~~~
+#
+# ## Full objects
+#
+# Objects are serialized to a map in plain mode, replacing cycles by `null` values.
+# This creates plain MessagePack easy to read for other non-Nit programs,
+# but cycles and the dynamic type information are lost.
+#
+# ~~~
+# class A
+#     serialize
+#
+#     var i = 1
+#     var o: nullable A = self
+#
+#     redef fun ==(o) do return o isa A and o.i == i # Skip the cyclic `o`
+# end
+#
+# var a = new A
+# var bytes = a.serialize_msgpack(plain=true)
+# assert bytes == b"\x82\xA1\x69\x01\xA1\x6F\xC0"
+# assert bytes.deserialize_msgpack isa Map[nullable Serializable, nullable Serializable] # Not an A
+# ~~~
+#
+# Or, with metadata, the same object is serialized with information on object
+# uniqueness (with an id and references) and its dynamic type.
+# The whole object is contained in a MessagePack array:
+#
+# * The array holds the metadata and attributes or each object,
+#   here it is a fixarray of 3 items: 0x93
+# * Define an object (ext type 0x7B) with the id 0, here a fixext1: 0xD47B00
+# * The dynamic type name, here a fixstr with the letter 'A': 0xA141
+# * The attributes as a map, here a fixmap of 2 items: 0x82
+# * First attribute name, here a fixstr for "i": 0xA169
+# * First attribute value, here a fixint for 1: 0x01
+# * Second attribute name, here a fixstr for "o": 0xA16F
+# * Second attribute value, a reference (ext type 0x7D) to object id 0,
+#   here a fixext1: 0xD47D00
+#
+# ~~~
+# bytes = a.serialize_msgpack
+# assert bytes == b"\x93\xD4\x7B\x00\xA1\x41\x82\xA1\x69\x01\xA1\x6F\xD4\x7D\x00"
+# assert bytes.deserialize_msgpack == a
+# ~~~
+#
+# ## References
+#
+# Format description and other implementations: http://msgpack.org/
+#
+# Format specification: https://github.com/msgpack/msgpack/blob/master/spec.md
+module msgpack
+
+import serialization_write
+import serialization_read
diff --git a/lib/msgpack/msgpack_to_json.nit b/lib/msgpack/msgpack_to_json.nit
new file mode 100644 (file)
index 0000000..86337cd
--- /dev/null
@@ -0,0 +1,39 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Convert MessagePack format to JSON
+module msgpack_to_json
+
+import msgpack::read
+import json
+
+if args.has("-h") then
+       print "Usage: nit msgpack::msgpack_to_json [source_file.msgpack]"
+       print "Convert MessagePack format to JSON. Read from stdin if no source_file is given."
+       exit 0
+end
+
+var reader = if args.length >= 1 then
+               new FileReader.open(args.first)
+       else stdin
+
+while reader.last_error == null and not reader.eof do
+       var deserialized = reader.read_msgpack
+
+       if deserialized != null then
+               print deserialized.serialize_to_json(plain=true, pretty=true)
+       else
+               print "null"
+       end
+end
diff --git a/lib/msgpack/package.ini b/lib/msgpack/package.ini
new file mode 100644 (file)
index 0000000..2820571
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=msgpack
+tags=format,lib
+maintainer=Alexis Laferrière <alexis.laf@xymus.net>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/msgpack/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/msgpack/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
diff --git a/lib/msgpack/read.nit b/lib/msgpack/read.nit
new file mode 100644 (file)
index 0000000..b068a81
--- /dev/null
@@ -0,0 +1,216 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Low-level read MessagePack format from `Reader` streams
+module read
+
+import serialization
+private import binary
+
+import ext
+
+redef class Reader
+
+       # Read the next MessagePack object and return it as a simple Nit object
+       #
+       # The return value is composed of:
+       # * the simple types `null`, `Bool`, `Int`, `Float`, `String` and `Bytes`,
+       # * collections of simple Nit objects `Array[nullable Serializable]`
+       #   and `Map[nullable Serializable, nullable Serializable]`,
+       # * and `MsgPackExt` for custom MessagePack *ext* data.
+       #
+       # This method reads plain MessagePack data, as written by `MsgPackSerializer`
+       # when `plain_msgpack == true`. To deserialize full Nit objects from
+       # MessagePack with metadata use `Reader::deserialize_msgpack`.
+       fun read_msgpack: nullable Serializable
+       do
+               if last_error != null then return 0
+
+               var typ = read_byte
+               if typ == null then
+                       # Error, return default `null`
+                       return null
+
+               else if typ & 0b1000_0000u8 == 0u8 or typ & 0b1110_0000u8 == 0b1110_0000u8 then
+                       # fixint
+                       var bytes = new Bytes.with_capacity(1)
+                       bytes.add typ
+                       return bytes.to_i(signed=true)
+
+               else if typ & 0b1111_0000u8 == 0b1000_0000u8 then
+                       # fixmap
+                       var len = typ & 0b0000_1111u8
+                       return read_msgpack_map_data(len.to_i)
+
+               else if typ & 0b1111_0000u8 == 0b1001_0000u8 then
+                       # fixarray
+                       var len = typ & 0b0000_1111u8
+                       return read_msgpack_array_data(len.to_i)
+
+               else if typ & 0b1110_0000u8 == 0b1010_0000u8 then
+                       # fixstr
+                       var len = typ & 0b0001_1111u8
+                       return read_bytes(len.to_i).to_s
+
+               else if typ == 0xC0u8 then
+                       return null
+               else if typ == 0xC2u8 then
+                       return false
+               else if typ == 0xC3u8 then
+                       return true
+
+               else if typ == 0xCCu8 then
+                       # uint8
+                       return (read_byte or else 0u8).to_i
+               else if typ == 0xCDu8 then
+                       # uint16
+                       return read_bytes(2).to_i
+               else if typ == 0xCEu8 then
+                       # uint32
+                       return read_bytes(4).to_i
+               else if typ == 0xCFu8 then
+                       # uint64
+                       return read_bytes(8).to_i
+               else if typ == 0xD0u8 then
+                       # int8
+                       return read_bytes(1).to_i(true)
+               else if typ == 0xD1u8 then
+                       # int16
+                       return read_bytes(2).to_i(true)
+               else if typ == 0xD2u8 then
+                       # int32
+                       return read_bytes(4).to_i(true)
+               else if typ == 0xD3u8 then
+                       # int64
+                       return read_int64
+
+               else if typ == 0xCAu8 then
+                       return read_float
+               else if typ == 0xCBu8 then
+                       return read_double
+
+               else if typ == 0xD9u8 then
+                       # str8
+                       var len = read_byte
+                       if len == null then return null
+                       return read_bytes(len.to_i).to_s
+               else if typ == 0xDAu8 then
+                       # str16
+                       var len = read_bytes(2)
+                       return read_bytes(len.to_i).to_s
+               else if typ == 0xDBu8 then
+                       # str32
+                       var len = read_bytes(4)
+                       return read_bytes(len.to_i).to_s
+
+               else if typ == 0xC4u8 then
+                       # bin8
+                       var len = read_byte
+                       if len == null then return null
+                       return read_bytes(len.to_i)
+               else if typ == 0xC5u8 then
+                       # bin16
+                       var len = read_bytes(2)
+                       return read_bytes(len.to_i)
+               else if typ == 0xC6u8 then
+                       # bin32
+                       var len = read_bytes(4)
+                       return read_bytes(len.to_i)
+
+               else if typ == 0xDCu8 then
+                       # array16
+                       var len = read_bytes(2)
+                       return read_msgpack_array_data(len.to_i)
+               else if typ == 0xDDu8 then
+                       # array32
+                       var len = read_bytes(4)
+                       return read_msgpack_array_data(len.to_i)
+
+               else if typ == 0xDEu8 then
+                       # map16
+                       var len = read_bytes(2)
+                       return read_msgpack_map_data(len.to_i)
+               else if typ == 0xDFu8 then
+                       # map32
+                       var len = read_bytes(4)
+                       return read_msgpack_map_data(len.to_i)
+
+               else if typ == 0xD4u8 then
+                       # fixext1
+                       return read_msgpack_fixext_data(1)
+               else if typ == 0xD5u8 then
+                       # fixext2
+                       return read_msgpack_fixext_data(2)
+               else if typ == 0xD6u8 then
+                       # fixext4
+                       return read_msgpack_fixext_data(4)
+               else if typ == 0xD7u8 then
+                       # fixext8
+                       return read_msgpack_fixext_data(8)
+               else if typ == 0xD8u8 then
+                       # fixext16
+                       return read_msgpack_fixext_data(16)
+
+               else if typ == 0xC7u8 then
+                       # ext1
+                       return read_msgpack_ext_data(1)
+               else if typ == 0xC8u8 then
+                       # ext2
+                       return read_msgpack_ext_data(2)
+               else if typ == 0xC9u8 then
+                       # ext4
+                       return read_msgpack_ext_data(4)
+               end
+
+               print_error "MessagePack Warning: Found no match for typ {typ} / 0b{typ.to_i.to_base(2)}"
+               return null
+       end
+
+       # Read the content of a map, `len` keys and values
+       private fun read_msgpack_map_data(len: Int): Map[nullable Serializable, nullable Serializable]
+       do
+               var map = new Map[nullable Serializable, nullable Serializable]
+               for i in [0..len.to_i[ do map[read_msgpack] = read_msgpack
+               return map
+       end
+
+       # Read the content of an array of `len` items
+       private fun read_msgpack_array_data(len: Int): Array[nullable Serializable]
+       do
+               return [for i in [0..len[ do read_msgpack]
+       end
+
+       # Read the content of a *fixext* of `len` bytes
+       #
+       # ~~~
+       # var reader = new BytesReader(b"\xC7\x03\x0A\x0B\x0C\x0D")
+       # var ext = reader.read_msgpack
+       # assert ext isa MsgPackExt
+       # assert ext.typ == 0x0Au8
+       # assert ext.data == b"\x0B\x0C\x0D"
+       # ~~~
+       private fun read_msgpack_fixext_data(len: Int): MsgPackExt
+       do
+               var exttyp = read_byte or else 0u8
+               var data = read_bytes(len)
+               return new MsgPackExt(exttyp, data)
+       end
+
+       # Read the content of a dynamic *ext* including the length on `len_len` bytes
+       private fun read_msgpack_ext_data(len_len: Int): MsgPackExt
+       do
+               var len = read_bytes(len_len).to_i
+               return read_msgpack_fixext_data(len)
+       end
+end
diff --git a/lib/msgpack/serialization_common.nit b/lib/msgpack/serialization_common.nit
new file mode 100644 (file)
index 0000000..a9e9ddf
--- /dev/null
@@ -0,0 +1,32 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Serialization services for `serialization_write` and `serialization_read`
+module serialization_common
+
+# MessagePack serialization or deserialization engine
+abstract class MsgPackEngine
+
+       # *ext type* byte for object definitions, defaults to 0x7Bu8 or '{'
+       var ext_typ_obj: Byte = 0x7Bu8 is writable
+
+       # *ext type* byte for object references, defaults to 0x7Du8 or '}'
+       var ext_typ_ref: Byte = 0x7Du8 is writable
+
+       # *ext type* byte to identify a char, defaults to 0x7Cu8 or '~'
+       var ext_typ_char: Byte = 0x7Cu8 is writable
+
+       # *ext type* byte to identify a byte, defaults to 0x7Eu8 or '|'
+       var ext_typ_byte: Byte = 0x7Eu8 is writable
+end
diff --git a/lib/msgpack/serialization_read.nit b/lib/msgpack/serialization_read.nit
new file mode 100644 (file)
index 0000000..75f3874
--- /dev/null
@@ -0,0 +1,410 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Deserialize full Nit objects from MessagePack format
+#
+# See the package `msgpack` for more details on the serialization
+# of Nit objects.
+module serialization_read
+
+import serialization::caching
+import serialization::safe
+private import json # for class_inheritance_metamodel
+private import serialization::engine_tools
+
+import serialization_common
+private import read
+import ext
+
+# ---
+# Easy services
+
+redef class Bytes
+
+       # Deserialize full Nit `nullable Object` from MessagePack formated data
+       #
+       # The dynamic type of the deserialized object can be limited to `static_type`.
+       #
+       # Warning: Deserialization errors are reported with `print_error`,
+       # the returned object may be partial or fall back on `null`.
+       # To handle the errors programmatically, use a `MsgPackDeserializer`.
+       fun deserialize_msgpack(static_type: nullable String): nullable Object
+       do
+               var stream = new BytesReader(self)
+               var res = stream.deserialize_msgpack(static_type)
+               stream.close
+               return res
+       end
+end
+
+redef class Reader
+
+       # Deserialize full Nit `nullable Object` from MessagePack formated data
+       #
+       # This method use metadata in the MessagePack source to recreate full
+       # Nit objects serialized by `Writer::serialize_msgpack` or
+       # `MsgPackSerializer`.
+       #
+       # The dynamic type of the deserialized object can be limited to `static_type`.
+       #
+       # Warning: Deserialization errors are reported with `print_error`,
+       # the returned object may be partial or fall back on `null`.
+       # To handle the errors programmatically, use a `MsgPackDeserializer`.
+       fun deserialize_msgpack(static_type: nullable String): nullable Object
+       do
+               var deserializer = new MsgPackDeserializer(self)
+               var res = deserializer.deserialize(static_type)
+
+               if deserializer.errors.length == 1 then
+                       print_error deserializer.errors.join("")
+               else if deserializer.errors.not_empty then
+                       print_error "Deserialization Errors:\n* {deserializer.errors.join("\n* ")}"
+               end
+
+               return res
+       end
+end
+
+# ---
+# Engine
+
+# Deserialize MessagePack format to full Nit objects
+class MsgPackDeserializer
+       super CachingDeserializer
+       super MsgPackEngine
+       super SafeDeserializer
+
+       # Source stream
+       var stream: Reader
+
+       # Map of attributes from the root deserialized object to the current object
+       private var path = new Array[Map[nullable Serializable, nullable Serializable]]
+
+       # Metadata arrays with from the root deserialized object to the current object
+       var path_arrays = new Array[nullable Array[nullable Object]]
+
+       # Names of the attributes from the root to the object currently being deserialized
+       var attributes_path = new Array[String]
+
+       # Last encountered object reference id.
+       #
+       # See `id_to_object`.
+       private var just_opened_id: nullable Int = null
+
+       redef fun deserialize_attribute(name, static_type)
+       do
+               if path.is_empty then
+                       # The was a parsing error or the root is not an object
+                       deserialize_attribute_missing = false
+                       return null
+               end
+
+               var current = path.last
+
+               var serialized_value = null
+               var serialized_value_found = false
+               if current.keys.has(name) then
+                       # Non-cached string
+                       serialized_value = current[name]
+                       serialized_value_found = true
+               else
+                       # It may be cached, deserialize all keys until we find it
+                       for key in current.keys.to_a do
+                               if key isa Array[nullable Serializable] or key isa MsgPackExt then
+                                       var str = convert_object(key, "String")
+                                       if str isa String then
+                                               var value = current[key]
+                                               current.keys.remove key
+                                               current[str] = value
+
+                                               if str == name then
+                                                       serialized_value = value
+                                                       serialized_value_found = true
+                                                       break
+                                               end
+                                       end
+                               end
+                       end
+               end
+
+               if not serialized_value_found then
+                       # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
+                       deserialize_attribute_missing = true
+                       return null
+               end
+
+               attributes_path.add name
+               var res = convert_object(serialized_value, static_type)
+               attributes_path.pop
+
+               deserialize_attribute_missing = false
+               return res
+       end
+
+       # This may be called multiple times by the same object from defs of a same constructor
+       redef fun notify_of_creation(new_object)
+       do
+               var id = just_opened_id
+               if id == null then return
+               cache[id] = new_object
+       end
+
+       # Convert the simple JSON `object` to a Nit object
+       private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
+       do
+               #print "convert_object {if object != null then object.class_name else "null"}"
+               if object isa Array[nullable Object] and object.length >= 1 then
+                       # Serialized object?
+                       var first = object.first
+                       if first isa MsgPackExt then
+                               if first.typ == ext_typ_obj then
+                                       # An array starts with a *ext*, it must be a serialized object
+
+                                       # New object declaration
+                                       var id = first.data.to_i
+
+                                       if cache.has_id(id) then
+                                               # FIXME use Warning
+                                               errors.add new Error("Deserialization Error: object with id {id} is deserialized twice.")
+                                               # Keep going
+                                       end
+
+                                       var type_name = null
+                                       var i = 1
+
+                                       # Read dynamic type
+                                       if object.length >= 2 then
+
+                                               # Try to get the type name as a string
+                                               var o = object[i]
+                                               if o isa String and static_type == "String" and object.length == 2 then
+                                                       cache[id] = o
+                                                       return o
+                                               else
+                                                       var typ = convert_object(object[i], "String")
+                                                       if typ isa String then
+                                                               type_name = typ
+                                                               i += 1
+                                                       end
+                                               end
+                                       end
+
+                                       if type_name == null then
+                                               # There was no dynamic type
+
+                                               # We could use a `class_name_heuristic` here...
+
+                                               # Fallback to the static type
+                                               if static_type != null then
+                                                       type_name = static_type.strip_nullable
+                                               end
+
+                                               if type_name == null then
+                                                       errors.add new Error("Deserialization Error: could not determine dynamic type of `{object}`.")
+                                                       return null
+                                               end
+                                       end
+
+                                       if not accept(type_name, static_type) then return null
+
+                                       var attributes = null
+                                       if object.length > i then attributes = object[i]
+                                       if not attributes isa Map[nullable Serializable, nullable Serializable] then
+                                               # Some other type (could be an error), or there's no attributes
+                                               attributes = new Map[nullable Serializable, nullable Serializable]
+                                       end
+
+                                       # advance on path
+                                       path.push attributes
+                                       path_arrays.push object
+
+                                       just_opened_id = id
+                                       var value = deserialize_class(type_name)
+                                       just_opened_id = null
+
+                                       # revert on path
+                                       path.pop
+                                       path_arrays.pop
+
+                                       return value
+                               else
+                                       errors.add new Error("Deserialization Error: unknown MessagePack ext '{first.typ}'.")
+                               end
+                       end
+
+                       # Plain array? Try to convert it to the desired static_type
+                       if static_type != null then
+                               return deserialize_class(static_type.strip_nullable)
+                       end
+                       return object
+               end
+
+               if object isa Map[nullable Serializable, nullable Serializable] then
+                       # Plain map
+                       # TODO parse it as an instance of `static_type`
+
+                       if static_type != null then
+                               path.push object
+                               path_arrays.push null
+
+                               just_opened_id = null
+                               var value = deserialize_class(static_type.strip_nullable)
+
+                               path.pop
+                               path_arrays.pop
+
+                               return value
+                       end
+
+                       return object
+               end
+
+               if object isa MsgPackExt then
+
+                       # First try the custom extensions
+                       var custom = deserialize_ext(object, static_type)
+                       if custom == null then
+
+                               # No custom, go for deser standard references
+                               if object.typ == ext_typ_ref then
+                                       # Reference to an object
+                                       var id = object.data.to_i
+                                       if not cache.has_id(id) then
+                                               errors.add new Error("Deserialization Error: object reference id unknown.")
+                                               return object
+                                       end
+                                       return cache.object_for(id)
+
+                               else if object.typ == ext_typ_char then
+                                       # Char
+                                       return object.data.to_s.first
+
+                               else if object.typ == ext_typ_byte then
+                                       # Byte
+                                       return object.data.first
+                               end
+                       end
+               end
+
+               if object isa String and object.length == 1 and static_type == "Char" then
+                       # Char serialized as a string
+                       return object.chars.first
+               end
+
+               if object isa Int and static_type == "Byte" then
+                       # Byte serialized as an integer
+                       return object.to_b
+               end
+
+               return object
+       end
+
+       redef fun deserialize(static_type)
+       do
+               errors.clear
+
+               var root = stream.read_msgpack
+               return convert_object(root, static_type)
+       end
+
+       # Hook to customize the deserialization of MessagePack extensions
+       #
+       # Redefine this method in subclasses to return custom Nit objects from
+       # an application specific extension.
+       #
+       # This method is invoked before dealing with the extensions used by the
+       # Nit serialization metadata [0x40..0x43]. In general, you should ignore
+       # them by returning `null`, but they can also be intercepted to comply to
+       # a format from a remote server.
+       protected fun deserialize_ext(ext: MsgPackExt, static_type: nullable String): nullable Object
+       do
+               return null
+       end
+end
+
+redef class SimpleCollection[E]
+       redef init from_deserializer(v)
+       do
+               super
+               if v isa MsgPackDeserializer then
+                       v.notify_of_creation self
+                       init
+
+                       var open_array = v.path_arrays.last
+                       var msgpack_items = null
+                       if open_array != null then msgpack_items = open_array.last
+
+                       if not msgpack_items isa Array[nullable Serializable] then
+                               v.errors.add new Error("Deserialization Error: no items in source of `{class_name}`")
+                               return
+                       end
+
+                       # Name of the dynamic name of E
+                       var items_type_name = (new GetName[E]).to_s
+
+                       # Fill array
+                       for o in msgpack_items do
+                               var obj = v.convert_object(o, items_type_name)
+                               if obj isa E then
+                                       add obj
+                               else v.errors.add new AttributeTypeError(self, "items", obj, items_type_name)
+                       end
+               end
+       end
+end
+
+redef class Map[K, V]
+       redef init from_deserializer(v)
+       do
+               super
+
+               if v isa MsgPackDeserializer then
+                       v.notify_of_creation self
+                       init
+
+                       var open_object = v.path_arrays.last
+                       var msgpack_items
+                       if open_object != null then
+                               # Metadata available
+                               msgpack_items = open_object.last
+                       else
+                               msgpack_items = v.path.last
+                       end
+
+                       if not msgpack_items isa Map[nullable Object, nullable Object] then
+                               v.errors.add new Error("Deserialization Error: no key/values in source of `{class_name}`")
+                               return
+                       end
+
+                       var keys_type_name = (new GetName[K]).to_s
+                       var values_type_name = (new GetName[V]).to_s
+
+                       for key_src, value_src in msgpack_items do
+                               var key = v.convert_object(key_src, keys_type_name)
+                               if not key isa K then
+                                       v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
+                                       continue
+                               end
+
+                               var value = v.convert_object(value_src, values_type_name)
+                               if not value isa V then
+                                       v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
+                                       continue
+                               end
+
+                               self[key] = value
+                       end
+               end
+       end
+end
diff --git a/lib/msgpack/serialization_write.nit b/lib/msgpack/serialization_write.nit
new file mode 100644 (file)
index 0000000..0b5b4f9
--- /dev/null
@@ -0,0 +1,349 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Serialize full Nit objects to MessagePack format
+#
+# There are 3 main entrypoint services:
+# * `Writer::serialize_msgpack` adds an object to any stream writer.
+# * `Serializable::serialize_msgpack` serializes the object to bytes.
+# * `MsgPackSerializer` gives full control over the serialization of
+#   Nit objets to the MessagePack format.
+module serialization_write
+
+import serialization::caching
+private import serialization::engine_tools
+
+import serialization_common
+private import write
+import ext
+
+# MessagePack deserialization engine
+class MsgPackSerializer
+       super CachingSerializer
+       super MsgPackEngine
+
+       # Target writing stream
+       var stream: Writer
+
+       # Write plain MessagePack without metadata for deserialization?
+       #
+       # If `false`, the default, serialize to support deserialization:
+       #
+       # * Each object is encapsulated in an array that contains metadata and
+       #   the actual object attributes in a map. The metadata includes the type
+       #   name and references to already serialized object. This information
+       #   supports deserializing the message, including cycles.
+       # * Preserve the Nit `Char` and `Byte` types as an object.
+       # * The generated MessagePack is standard and can be read by non-Nit programs.
+       #   However, it contains some complexity that may make it harder to use.
+       #
+       # If `true`, serialize only the real data or non-Nit programs:
+       #
+       # * Nit objects are serialized to pure and standard MessagePack so they can
+       #   be easily read by non-Nit programs.
+       # * Nit objects are serialized at every reference, so they may be duplicated.
+       #   It is easier to read but it creates a larger output and it does not support
+       #   cycles. Cyclic references are replaced by `null`.
+       # * The serialized data can only be deserialized to their expected static
+       #   types, losing the knowledge of their dynamic type.
+       var plain_msgpack = false is writable
+
+       # Should strings declaring the objects type and attributes name be cached?
+       #
+       # If `true` metadata strings are cached using `cache`.
+       # The first occurrence is written as an object declaration,
+       # successive occurrences are written as an object reference.
+       #
+       # If `false`, the default, metadata strings are written as pure MessagePack
+       # strings, without their own metadata.
+       #
+       # Using the cache may save some space by avoiding the repetition of
+       # names used by many types or attributes.
+       # However, it adds complexity to the generated message and may be less
+       # safe for versioning.
+       var cache_metadata_strings = false is writable
+
+       # List of the current open objects, the first is the main target of the serialization
+       #
+       # Used only when `plain_msgpack == true` to detect cycles in serialization.
+       private var open_objects = new Array[Object]
+
+       redef var current_object = null
+
+       redef fun serialize(object)
+       do
+               if object == null then
+                       stream.write_msgpack_null
+               else
+                       if plain_msgpack then
+                               for o in open_objects do
+                                       if object.is_same_serialized(o) then
+                                               # Cycle, can't be managed in plain_msgpack mode
+                                               warn "Cycle detected in serialized object, replacing reference with 'null'."
+                                               stream.write_msgpack_null
+                                               return
+                                       end
+                               end
+
+                               open_objects.add object
+                       end
+
+                       var last_object = current_object
+                       current_object = object
+                       object.accept_msgpack_serializer self
+                       current_object = last_object
+
+                       if plain_msgpack then open_objects.pop
+               end
+       end
+
+       redef fun serialize_attribute(name, value)
+       do
+               serialize_meta_string name
+               super
+       end
+
+       redef fun serialize_reference(object)
+       do
+               if not plain_msgpack and cache.has_object(object) then
+                       # if already serialized, add local reference
+                       var id = cache.id_for(object)
+                       stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
+               else
+                       # serialize
+                       serialize object
+               end
+       end
+
+       private fun serialize_meta_string(type_name: String)
+       do
+               if plain_msgpack or not cache_metadata_strings then
+                       # String only version
+                       stream.write_msgpack_str type_name
+                       return
+               end
+
+               if cache.has_object(type_name) then
+                       # if already serialized, add reference
+                       var id = cache.id_for(type_name)
+                       stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
+               else
+                       # serialize
+                       var id = cache.new_id_for(type_name)
+                       stream.write_msgpack_array 2 # obj+id, type_name
+                       stream.write_msgpack_ext(ext_typ_obj, id.to_bytes)
+                       stream.write_msgpack_str type_name
+               end
+       end
+end
+
+# Serialization visitor to count attribute in `Serializable` objects
+class AttributeCounter
+       super Serializer
+
+       # Number of attributes counted
+       var count = 0
+
+       redef fun serialize_attribute(name, object) do count += 1
+end
+
+# ---
+# Services and serializables
+
+redef class Writer
+       # Serialize `value` in MessagePack format
+       fun serialize_msgpack(value: nullable Serializable, plain: nullable Bool)
+       do
+               var serializer = new MsgPackSerializer(self)
+               serializer.plain_msgpack = plain or else false
+               serializer.serialize value
+       end
+end
+
+redef class Serializable
+
+       # Serialize `self` to MessagePack bytes
+       #
+       # Set `plain = true` to generate standard MessagePack, without deserialization metadata.
+       # Use this option if the generated MessagePack will be read by non-Nit programs.
+       # Use the default, `plain = false`, if the MessagePack bytes are to be deserialized by a Nit program.
+       fun serialize_msgpack(plain: nullable Bool): Bytes
+       do
+               var stream = new BytesWriter
+               stream.serialize_msgpack(self, plain)
+               stream.close
+               return stream.bytes
+       end
+
+       # Hook to customize the serialization of this class to MessagePack
+       #
+       # This method can be refined to customize the serialization by either
+       # writing pure JSON directly on the stream `v.stream` or
+       # by using other services of `MsgPackSerializer`.
+       #
+       # Most of the time, it is better to refine the method `core_serialize_to`
+       # which is used by all the serialization engines, not just MessagePack.
+       protected fun accept_msgpack_serializer(v: MsgPackSerializer)
+       do
+
+               # Count the number of attributes
+               var attribute_counter = new AttributeCounter
+               accept_msgpack_attribute_counter attribute_counter
+               var n_attributes = attribute_counter.count
+
+               if not v.plain_msgpack then
+
+                       var n_meta_items = 2
+                       if n_attributes > 0 then n_meta_items += 1
+                       n_meta_items += msgpack_extra_array_items # obj+id, class_name, attributes
+
+                       # Metadata
+                       var id = v.cache.new_id_for(self)
+                       v.stream.write_msgpack_array n_meta_items
+                       v.stream.write_msgpack_ext(v.ext_typ_obj, id.to_bytes)
+                       v.serialize_meta_string class_name
+
+                       if n_attributes > 0 then v.stream.write_msgpack_map n_attributes
+               else
+                       v.stream.write_msgpack_map n_attributes
+               end
+
+               v.serialize_core self
+       end
+
+       # Hook to customize the behavior of the `AttributeCounter`
+       #
+       # By default, this method makes `v` visits all serializable attributes.
+       protected fun accept_msgpack_attribute_counter(v: AttributeCounter)
+       do
+               v.serialize_core self
+       end
+
+       # Hook to request a larger than usual metadata array
+       #
+       # Use by `SimpleCollection` and `Map` to append the items after
+       # the metadata and attributes.
+       protected fun msgpack_extra_array_items: Int do return 0
+end
+
+redef class MsgPackExt
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_ext(typ, data)
+end
+
+redef class Text
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_str self
+end
+
+redef class Int
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_int self
+end
+
+redef class Float
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_double self
+end
+
+redef class Bool
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bool self
+end
+
+redef class Byte
+       redef fun accept_msgpack_serializer(v)
+       do
+               if v.plain_msgpack then
+                       # Write as a string
+                       v.stream.write_msgpack_int to_i
+               else
+                       # Write as ext
+                       var bytes = new Bytes.with_capacity(1)
+                       bytes.add self
+                       v.stream.write_msgpack_ext(v.ext_typ_byte, bytes)
+               end
+       end
+end
+
+redef class Char
+       redef fun accept_msgpack_serializer(v)
+       do
+               if v.plain_msgpack then
+                       # Write as a string
+                       v.stream.write_msgpack_fixstr to_s
+               else
+                       # Write as ext
+                       var bytes = to_s.to_bytes
+                       v.stream.write_msgpack_ext(v.ext_typ_char, bytes)
+               end
+       end
+end
+
+redef class Bytes
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bin self
+end
+
+redef class CString
+       redef fun accept_msgpack_serializer(v) do to_s.accept_msgpack_serializer(v)
+end
+
+redef class SimpleCollection[E]
+       redef fun accept_msgpack_serializer(v)
+       do
+               if not v.plain_msgpack then
+                       # Add metadata and other attributes
+                       super
+               end
+
+               # Header
+               v.stream.write_msgpack_array length
+
+               # Items
+               for e in self do
+                       if not v.try_to_serialize(e) then
+                               assert e != null # null would have been serialized
+                               v.warn "element of type {e.class_name} is not serializable."
+                               v.stream.write_msgpack_null
+                       end
+               end
+       end
+
+       redef fun msgpack_extra_array_items do return 1
+end
+
+redef class Map[K, V]
+       redef fun accept_msgpack_serializer(v)
+       do
+               if not v.plain_msgpack then
+                       # Add metadata and other attributes
+                       super
+               end
+
+               # Header
+               v.stream.write_msgpack_map keys.length
+
+               # Key / values, alternating
+               for key, val in self do
+                       if not v.try_to_serialize(key) then
+                               assert val != null # null would have been serialized
+                               v.warn "element of type {val.class_name} is not serializable."
+                               v.stream.write_msgpack_null
+                       end
+
+                       if not v.try_to_serialize(val) then
+                               assert val != null # null would have been serialized
+                               v.warn "element of type {val.class_name} is not serializable."
+                               v.stream.write_msgpack_null
+                       end
+               end
+       end
+
+       redef fun msgpack_extra_array_items do return 1
+end
diff --git a/lib/msgpack/write.nit b/lib/msgpack/write.nit
new file mode 100644 (file)
index 0000000..15e5985
--- /dev/null
@@ -0,0 +1,533 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Low-level write in MessagePack format to `Writer` streams
+module write
+
+import binary
+
+redef class Writer
+
+       # Write `null`, or nil, in MessagePack format
+       fun write_msgpack_null do write_byte 0xC0u8
+
+       # Write `bool` in MessagePack format
+       fun write_msgpack_bool(bool: Bool)
+       do write_byte(if bool then 0xC3u8 else 0xC2u8)
+
+       # ---
+       # Integers
+
+       # Write the integer `value` either as the shortest possible MessagePack _int_
+       fun write_msgpack_int(value: Int)
+       do
+               if value >= -0x20 and value <= 0x7F then
+                       write_msgpack_fixint value
+               else if value >= 0 then
+                       if value <= 0xFF then
+                               write_msgpack_uint8 value
+                       else if value <= 0xFFFF then
+                               write_msgpack_uint16 value
+                       else if value <= 0xFFFF_FFFF then
+                               write_msgpack_uint32 value
+                       else #if value <= 0xFFFF_FFFF_FFFF_FFFF then
+                               write_msgpack_uint64 value
+                       end
+               else if value >= -128 then
+                       write_msgpack_int8 value
+               else if value >= -32768 then
+                       write_msgpack_int16 value
+               else if value >= -2147483648 then
+                       write_msgpack_int32 value
+               else
+                       write_msgpack_int64 value
+               end
+       end
+
+       # Write `value` as a single byte with metadata
+       #
+       # Require: `value >= -0x20 and value <= 0x7F`
+       fun write_msgpack_fixint(value: Int)
+       do
+               assert value >= -0x20 and value <= 0x7F
+               write_byte value.to_b
+       end
+
+       # Write `value` over one unsigned byte, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFF`
+       fun write_msgpack_uint8(value: Int)
+       do
+               write_byte 0xCCu8
+               write_bytes value.to_bytes(n_bytes=1)
+       end
+
+       # Write `value` over two unsigned bytes, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFFFF`
+       fun write_msgpack_uint16(value: Int)
+       do
+               write_byte 0xCDu8
+               write_bytes value.to_bytes(n_bytes=2)
+       end
+
+       # Write `value` over 4 unsigned bytes, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFFFF_FFFF`
+       fun write_msgpack_uint32(value: Int)
+       do
+               write_byte 0xCEu8
+               write_bytes value.to_bytes(n_bytes=4)
+       end
+
+       # Write `value` over 8 unsigned bytes, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFFFF_FFFF_FFFF_FFFF`
+       fun write_msgpack_uint64(value: Int)
+       do
+               write_byte 0xCFu8
+               write_bytes value.to_bytes(n_bytes=8)
+       end
+
+       # Write `value` over one signed byte, following 1 metadata byte
+       #
+       # Require: `value >= -128  and value <= 127`
+       fun write_msgpack_int8(value: Int)
+       do
+               write_byte 0xD0u8
+               write_bytes value.to_bytes(n_bytes=1)
+       end
+
+       # Write `value` over two signed bytes, following 1 metadata byte
+       fun write_msgpack_int16(value: Int)
+       do
+               write_byte 0xD1u8
+               write_bytes value.to_bytes(n_bytes=2)
+       end
+
+       # Write `value` over 4 signed bytes, following 1 metadata byte
+       fun write_msgpack_int32(value: Int)
+       do
+               write_byte 0xD2u8
+               write_bytes value.to_bytes(n_bytes=4)
+       end
+
+       # Write `value` over 8 signed bytes, following 1 metadata byte
+       fun write_msgpack_int64(value: Int)
+       do
+               write_byte 0xD3u8
+               write_int64 value
+       end
+
+       # ---
+       # Floats
+
+       # Write `value` as a MessagePack float (losing precision)
+       fun write_msgpack_float(value: Float)
+       do
+               write_byte 0xCAu8
+               write_float value
+       end
+
+       # Write `value` as a MessagePack double
+       fun write_msgpack_double(value: Float)
+       do
+               write_byte 0xCBu8
+               write_double value
+       end
+
+       # ---
+       # Strings
+
+       # Write `text` in the shortest possible MessagePack format
+       #
+       # Require: `text.byte_length <= 0xFFFF_FFFF`
+       fun write_msgpack_str(text: Text)
+       do
+               var len = text.byte_length
+               if len <= 0x1F then
+                       write_msgpack_fixstr text
+               else if len <= 0xFF then
+                       write_msgpack_str8 text
+               else if len <= 0xFFFF then
+                       write_msgpack_str16 text
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_str32 text
+               else
+                       abort
+               end
+       end
+
+       # Write `text` in _fixstr_ format, max of 0x1F bytes
+       #
+       # Require: `text.byte_length <= 0x1F`
+       fun write_msgpack_fixstr(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0x1F
+
+               var b = 0b1010_0000u8 | len.to_b
+               write_byte b
+
+               write text
+       end
+
+       # Write `text` in _str8_ format, max of 0xFF bytes
+       #
+       # Require: `text.byte_length <= 0xFF`
+       fun write_msgpack_str8(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0xFF
+
+               write_byte 0xD9u8
+               write_byte len.to_b
+               write text
+       end
+
+       # Write `text` in _str16_ format, max of 0xFFFF bytes
+       #
+       # Require: `text.byte_length <= 0xFFFF`
+       fun write_msgpack_str16(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0xFFFF
+
+               write_byte 0xDAu8
+               var len_bytes = len.to_bytes
+               write_byte len_bytes[0]
+               write_byte if len_bytes.length > 1 then len_bytes[1] else 0u8
+               write text
+       end
+
+       # Write `text` in _str32_ format, max of 0xFFFF_FFFF bytes
+       #
+       # Require: `text.byte_length <= 0xFFFF_FFFF`
+       fun write_msgpack_str32(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0xFFFF_FFFF
+
+               write_byte 0xDBu8
+               var len_bytes = len.to_bytes
+               write_byte len_bytes[0]
+               for i in [1..4[ do
+                       write_byte if len_bytes.length > i then len_bytes[i] else 0u8
+               end
+               write text
+       end
+
+       # ---
+       # Binary data
+
+       # Write `data` in the shortest possible MessagePack _bin_ format
+       #
+       # Require: `data.length <= 0xFFFF_FFFF`
+       fun write_msgpack_bin(data: Bytes)
+       do
+               var len = data.length
+               if len <= 0xFF then
+                       write_msgpack_bin8 data
+               else if len <= 0xFFFF then
+                       write_msgpack_bin16 data
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_bin32 data
+               else abort
+       end
+
+       # Write `data` in _bin8_ format, max of 0xFF bytes
+       #
+       # Require: `data.length <= 0xFF`
+       fun write_msgpack_bin8(data: Bytes)
+       do
+               var len = data.length
+               assert len <= 0xFF
+
+               write_byte 0xC4u8
+               write_byte len.to_b
+               write_bytes data
+       end
+
+       # Write `data` in _bin16_ format, max of 0xFFFF bytes
+       #
+       # Require: `data.length <= 0xFFFF`
+       fun write_msgpack_bin16(data: Bytes)
+       do
+               var len = data.length
+               assert len <= 0xFFFF
+
+               write_byte 0xC5u8
+               write_bytes len.to_bytes(n_bytes=2)
+               write_bytes data
+       end
+
+       # Write `data` in _bin32_ format, max of 0xFFFF_FFFF bytes
+       #
+       # Require: `data.length <= 0xFFFF_FFFF`
+       fun write_msgpack_bin32(data: Bytes)
+       do
+               var len = data.length
+               assert len <= 0xFFFF_FFFF
+
+               write_byte 0xC6u8
+               write_bytes len.to_bytes(n_bytes=4)
+               write_bytes data
+       end
+
+       # ---
+       # Arrays
+
+       # Write an array header for `len` items in the shortest possible MessagePack _array_ format
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_array(len: Int)
+       do
+               if len <= 0x0F then
+                       write_msgpack_fixarray len
+               else if len <= 0xFFFF then
+                       write_msgpack_array16 len
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_array32 len
+               else
+                       abort
+               end
+       end
+
+       # Write an array header for `len` items, max of 0x0F items
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0x0F`
+       fun write_msgpack_fixarray(len: Int)
+       do
+               assert len <= 0x0F
+               write_byte 0b1001_0000u8 | len.to_b
+       end
+
+       # Write an array header for `len` items, max of 0xFFFF items
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0xFFFF`
+       fun write_msgpack_array16(len: Int)
+       do
+               assert len <= 0xFFFF
+               write_byte 0xDCu8
+               write_bytes len.to_bytes(n_bytes=2)
+       end
+
+       # Write an array header for `len` items, max of 0xFFFF_FFFF items
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_array32(len: Int)
+       do
+               assert len <= 0xFFFF_FFFF
+               write_byte 0xDDu8
+               write_bytes len.to_bytes(n_bytes=4)
+       end
+
+       # ---
+       # Map
+
+       # Write a map header for `len` keys/value pairs in the shortest possible MessagePack _map_ format
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_map(len: Int)
+       do
+               if len <= 0x0F then
+                       write_msgpack_fixmap len
+               else if len <= 0xFFFF then
+                       write_msgpack_map16 len
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_map32 len
+               else
+                       abort
+               end
+       end
+
+       # Write a map header for `len` key/value pairs, max of 0x0F pairs
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0x0F`
+       fun write_msgpack_fixmap(len: Int)
+       do
+               assert len <= 0x0F
+               write_byte 0b1000_0000u8 | len.to_b
+       end
+
+       # Write a map header for `len` key/value pairs, max of 0xFFFF pairs
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0xFFFF`
+       fun write_msgpack_map16(len: Int)
+       do
+               assert len <= 0xFFFF
+               write_byte 0xDEu8
+               write_bytes len.to_bytes(n_bytes=2)
+       end
+
+       # Write a map header for `len` key/value pairs, max of 0xFFFF_FFFF pairs
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_map32(len: Int)
+       do
+               assert len <= 0xFFFF_FFFF
+               write_byte 0xDFu8
+               write_bytes len.to_bytes(n_bytes=4)
+       end
+
+       # ---
+       # Ext
+
+       # Write an application-specific extension for `typ` and `bytes` in the shortest possible MessagePack _ext_ format
+       #
+       # Require: `bytes.length <= 0xFFFF_FFFF`
+       #
+       # ~~~
+       # var writer = new BytesWriter
+       # writer.write_msgpack_ext(0x0Au8, b"\x0B\x0C\x0D")
+       # assert writer.bytes == b"\xC7\x03\x0A\x0B\x0C\x0D"
+       # ~~~
+       fun write_msgpack_ext(typ: Byte, bytes: Bytes)
+       do
+               var len = bytes.length
+               if len == 1 then
+                       write_msgpack_fixext1 typ
+                       write_byte bytes.first
+               else if len == 2 then
+                       write_msgpack_fixext2 typ
+                       write_bytes bytes
+               else if len == 4 then
+                       write_msgpack_fixext4 typ
+                       write_bytes bytes
+               else if len == 8 then
+                       write_msgpack_fixext8 typ
+                       write_bytes bytes
+               else if len == 16 then
+                       write_msgpack_fixext16 typ
+                       write_bytes bytes
+               else if len <= 0xFF then
+                       write_msgpack_ext8(typ, len)
+                       write_bytes bytes
+               else if len <= 0xFFFF then
+                       write_msgpack_ext16(typ, len)
+                       write_bytes bytes
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_ext32(typ, len)
+                       write_bytes bytes
+               else
+                       abort
+               end
+       end
+
+       # Write the header for an application-specific extension of one data byte
+       #
+       # After writing the header, clients should write the data byte.
+       fun write_msgpack_fixext1(typ: Byte)
+       do
+               write_byte 0xD4u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of two data bytes
+       #
+       # After writing the header, clients should write the two data bytes.
+       fun write_msgpack_fixext2(typ: Byte)
+       do
+               write_byte 0xD5u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of 4 data bytes
+       #
+       # After writing the header, clients should write the 4 data bytes.
+       fun write_msgpack_fixext4(typ: Byte)
+       do
+               write_byte 0xD6u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of 8 data bytes
+       #
+       # After writing the header, clients should write the 8 data bytes.
+       fun write_msgpack_fixext8(typ: Byte)
+       do
+               write_byte 0xD7u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of 16 data bytes
+       #
+       # After writing the header, clients should write the 16 data bytes.
+       fun write_msgpack_fixext16(typ: Byte)
+       do
+               write_byte 0xD8u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of `len` data bytes
+       #
+       # After writing the header, clients should write the data bytes.
+       #
+       # Require: `len >= 0 and <= 0xFF`
+       fun write_msgpack_ext8(typ: Byte, len: Int)
+       do
+               assert len >= 0 and len <= 0xFF
+               write_byte 0xC7u8
+               write_byte len.to_b
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of `len` data bytes
+       #
+       # After writing the header, clients should write the data bytes.
+       #
+       # Require: `len >= 0 and <= 0xFFFF`
+       fun write_msgpack_ext16(typ: Byte, len: Int)
+       do
+               assert len >= 0 and len <= 0xFFFF
+               write_byte 0xC8u8
+               write_bytes len.to_bytes(n_bytes=2)
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of `len` data bytes
+       #
+       # After writing the header, clients should write the data bytes.
+       #
+       # Require: `len >= 0 and <= 0xFFFF_FFFF`
+       fun write_msgpack_ext32(typ: Byte, len: Int)
+       do
+               assert len >= 0 and len <= 0xFFFF_FFFF
+               write_byte 0xC9u8
+               write_bytes len.to_bytes(n_bytes=4)
+               write_byte typ
+       end
+
+       # TODO timestamps
+end
index 767b27d..3cd64db 100644 (file)
@@ -17,7 +17,7 @@
 # Pre order sets and partial order set (ie hierarchies)
 module poset
 
-import serialization
+import serialization::serialization_core
 
 # Pre-order set graph.
 # This class models an incremental pre-order graph where new nodes and edges can be added (but not removed).
index 80f2007..c79b111 100644 (file)
@@ -91,19 +91,73 @@ class Mix
        # Play `chunk` on `channel`
        #
        # If `channel == -1` the first unreserved channel is used.
-       # The sound is repeated `loops` times, `loops == 0` plays it once and
-       # `loops == -1` loops infinitely.
-       fun play_channel(channel: Int, chunk: MixChunk, loops: Int): Bool `{
-               return Mix_PlayChannel(channel, chunk, loops) == 0;
+       # The sound is repeated `loops` times, `loops == 0` plays it once,
+       # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+       #
+       # Returns the channel used, or `-1` on error.
+       fun play_channel(channel: Int, chunk: MixChunk, loops: Int): Int `{
+               return Mix_PlayChannel(channel, chunk, loops);
        `}
 
-       # Set the chunk volume out of `mix.max_volume` and return the previous value
+       # Play `chunk` on `channel`
        #
-       # Use `volume = -1` to only read the previous value.
+       # If `channel == -1` the first unreserved channel is used.
+       # The sound is repeated `loops` times, `loops == 0` plays it once,
+       # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+       # If `ticks != -1`, the sample plays for at most `ticks` milliseconds.
+       fun play_channel_timed(channel: Int, chunk: MixChunk, loops, ticks: Int): Int `{
+               return Mix_PlayChannelTimed(channel, chunk, loops, ticks);
+       `}
+
+       # Halt/stop `channel` playback
+       #
+       # If `channel == -1`, halt all channels.
+       fun halt_channel(channel: Int) `{
+               Mix_HaltChannel(channel);
+       `}
+
+       # Halt `channel` in `ticks` milliseconds and return the number of channels set to expire
+       #
+       # If `channel == -1`, halt all channels.
+       fun expire_channel(channel, ticks: Int): Int `{
+               return Mix_ExpireChannel(channel, ticks);
+       `}
+
+       # Reserve `num` channels from being used by `play_channel(-1...)`
+       #
+       # Returns the number of of channels reserved.
+       fun reserve_channels(num: Int): Int `{
+               return Mix_ReserveChannels(num);
+       `}
+
+       # Set the `volume` of `channel`, out of `mix.max_volume`
+       #
+       # If `channel == -1`, set the volume of all channels.
+       #
+       # Returns the current volume of the channel, or if `channel == -1` the average volume.
+       fun volume(channel, volume: Int): Int `{
+               return Mix_Volume(channel, volume);
+       `}
+
+       # Set the `volume` for `chunk`, out of `mix.max_volume`
+       #
+       # If `volume == -1`, only read the previous value.
+       #
+       # Returns the previous volume value.
        fun volume_chunk(chunk: MixChunk, volume: Int) `{
                Mix_VolumeChunk(chunk, volume);
        `}
 
+       # Pause `channel`, or all playing channels if -1
+       fun pause(channel: Int) `{
+               Mix_Pause(channel);
+       `}
+
+       # Unpause `channel`, or all paused channels if -1
+       fun resume(channel: Int) `{
+               Mix_Resume(channel);
+       `}
+
        # ---
        # Music
 
index 3554d22..e1b9a16 100644 (file)
@@ -15,7 +15,7 @@
 # Services for caching serialization engines
 module caching
 
-import serialization
+import serialization_core
 private import engine_tools
 
 # A `Serializer` with a `cache`
index 4742ad4..bd4b417 100644 (file)
@@ -15,7 +15,7 @@
 # Advanced services for serialization engines
 module engine_tools
 
-import serialization
+import serialization_core
 intrude import core::collection::hash_collection
 
 # Maps instances to a value, uses `is_same_serialized` and `serialization_hash`.
@@ -36,6 +36,7 @@ class StrictHashMap[K, V]
                var c = _array[i]
                while c != null do
                        var ck = c._key
+                       assert ck != null
                        if ck.is_same_serialized(k) then
                                break
                        end
@@ -44,3 +45,53 @@ class StrictHashMap[K, V]
                return c
        end
 end
+
+redef interface Object
+       # Is `self` the same as `other` in a serialization context?
+       #
+       # Used to determine if an object has already been serialized.
+       fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
+
+       # Hash value use for serialization
+       #
+       # Used in combination with `is_same_serialized`. If two objects are the same
+       # in a serialization context, they must have the same `serialization_hash`.
+       fun serialization_hash: Int do return object_id
+end
+
+redef class String
+       redef fun serialization_hash do return hash
+       redef fun is_same_serialized(o) do return self == o
+end
+
+redef class Text
+
+       # Strip the `nullable` prefix from the type name `self`
+       #
+       # ~~~
+       # assert "String".strip_nullable == "String"
+       # assert "nullable Array[Int]".strip_nullable == "Array[Int]"
+       # assert "Map[Set[String], Set[Int]]".strip_nullable == "Map[Set[String], Set[Int]]"
+       # ~~~
+       fun strip_nullable: Text
+       do
+               var prefix = "nullable "
+               return if has_prefix(prefix) then substring_from(prefix.length) else self
+       end
+
+       # Strip the `nullable` prefix and the params from the type name `self`
+       #
+       # ~~~
+       # assert "String".strip_nullable_and_params == "String"
+       # assert "nullable Array[Int]".strip_nullable_and_params == "Array"
+       # assert "Map[Set[String], Set[Int]]".strip_nullable_and_params == "Map"
+       # ~~~
+       fun strip_nullable_and_params: Text
+       do
+               var class_name = strip_nullable
+
+               var bracket_index = class_name.index_of('[')
+               if bracket_index == -1 then return class_name
+               return class_name.substring(0, bracket_index)
+       end
+end
diff --git a/lib/serialization/inspect.nit b/lib/serialization/inspect.nit
new file mode 100644 (file)
index 0000000..ec5d466
--- /dev/null
@@ -0,0 +1,294 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Refine `Serializable::inspect` to show more useful information
+module inspect
+
+import serialization_core
+private import caching
+
+private fun inspect_testing: Bool do return "NIT_TESTING".environ == "true"
+
+# Serialization engine writing the object attributes to strings
+private class InspectSerializer
+       super CachingSerializer
+
+       # Target writing stream
+       var stream: Writer
+
+       redef var current_object = null
+
+       var first_object: nullable Object = null
+
+       redef fun serialize(object)
+       do
+               if object == null then
+                       stream.write "null"
+               else
+                       if current_object == null then
+                               first_object = object
+                       end
+
+                       var last_object = current_object
+                       current_object = object
+                       object.accept_inspect_serializer self
+                       current_object = last_object
+               end
+       end
+
+       var first_attribute_serialized = false
+
+       redef fun serialize_attribute(name, value)
+       do
+               if first_attribute_serialized then
+                       stream.write ", "
+               else
+                       stream.write " "
+                       first_attribute_serialized = true
+               end
+
+               stream.write name
+               stream.write ":"
+
+               super
+       end
+
+       redef fun serialize_reference(object)
+       do
+               if cache.has_object(object) then
+                       # Cycle
+                       var id = object.object_id
+                       if inspect_testing then id = cache.id_for(object)
+
+                       stream.write "<"
+                       stream.write object.class_name
+                       stream.write "#"
+                       stream.write id.to_s
+                       stream.write ">"
+               else if object != first_object and (not object isa DirectSerializable) then
+                       # Another object, print class and id only
+                       var id = object.object_id
+                       if inspect_testing then id = cache.new_id_for(object)
+
+                       stream.write "<"
+                       stream.write object.class_name
+                       stream.write "#"
+                       stream.write id.to_s
+                       stream.write ">"
+               else
+                       # Main object
+                       serialize object
+               end
+       end
+end
+
+redef class Serializable
+
+       # Improve the default inspection reading serializable attributes
+       #
+       # Simple immutable data are inspected as they would be written in Nit code.
+       #
+       # ~~~
+       # assert 123.inspect == "123"
+       # assert 1.5.inspect == "1.5"
+       # assert 0xa1u8.inspect == "0xa1u8"
+       # assert 'c'.inspect == "'c'"
+       # assert "asdf\n".inspect == "\"asdf\\n\""
+       # ~~~
+       #
+       # Inspections of mutable serializable objects show their dynamic type,
+       # their `object_id` and their first level attributes. When testing,
+       # the `object_id` is replaced by an id unique to each call to `inspect`.
+       #
+       # ~~~
+       # class MyClass
+       #     serialize
+       #
+       #     var i: Int
+       #     var o: nullable Object
+       # end
+       #
+       # var class_with_null = new MyClass(123)
+       # assert class_with_null.to_s == class_with_null.inspect
+       # assert class_with_null.to_s == "<MyClass#0 i:123, o:null>"
+       #
+       # var class_with_other = new MyClass(456, class_with_null)
+       # assert class_with_other.to_s == "<MyClass#0 i:456, o:<MyClass#1>>"
+       #
+       # var class_with_cycle = new MyClass(789)
+       # class_with_cycle.o = class_with_cycle
+       # assert class_with_cycle.to_s == "<MyClass#0 i:789, o:<MyClass#0>>"
+       # ~~~
+       #
+       # Items of collections are flattened and appended to the output.
+       #
+       # ~~~
+       # assert [1, 2, 3].inspect == "<Array[Int]#0 [1, 2, 3]>"
+       #
+       # var set = new HashSet[Object].from([1, 1.5, "two": Object])
+       # assert set.inspect == """<HashSet[Object]#0 [1, 1.5, "two"]>"""
+       #
+       # var map = new Map[Int, String]
+       # map[1] = "one"
+       # map[2] = "two"
+       # assert map.inspect == """<HashMap[Int, String]#0 {1:"one", 2:"two"}>"""
+       # ~~~
+       #
+       # Inspections producing over 80 characters are cut short.
+       #
+       # ~~~
+       # var long_class = new MyClass(123456789, "Some " + "very "*8 + "long string")
+       # assert long_class.to_s == "<MyClass#0 i:123456789, o:\"Some very very very very very very very very long s…>"
+       # ~~~
+       redef fun inspect
+       do
+               var stream = new StringWriter
+               var serializer = new InspectSerializer(stream)
+               serializer.serialize self
+               stream.close
+               var str = stream.to_s
+
+               # Cut long inspects
+               var max_length = 80
+               if str.length > max_length then
+                       str = str.substring(0, max_length-2) + "…>"
+               end
+
+               return str
+       end
+
+       private fun accept_inspect_serializer(v: InspectSerializer)
+       do
+               v.stream.write "<"
+
+               v.stream.write class_name
+               v.stream.write "#"
+
+               var id = object_id
+               if inspect_testing then id = v.cache.new_id_for(self)
+               v.stream.write id.to_s
+
+               accept_inspect_serializer_core v
+
+               v.stream.write ">"
+       end
+
+       private fun accept_inspect_serializer_core(v: InspectSerializer)
+       do v.serialize_core(self)
+end
+
+redef class Int
+       redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Float
+       redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Bool
+       redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Char
+       redef fun accept_inspect_serializer(v)
+       do
+               v.stream.write "'"
+               v.stream.write to_s.escape_to_nit
+               v.stream.write "'"
+       end
+end
+
+redef class Byte
+       redef fun accept_inspect_serializer(v)
+       do
+               v.stream.write to_s
+               v.stream.write "u8"
+       end
+end
+
+redef class CString
+       redef fun accept_inspect_serializer_core(v)
+       do
+               v.stream.write " \""
+               v.stream.write to_s.escape_to_nit
+               v.stream.write_char '"'
+       end
+end
+
+redef class Text
+
+       redef fun accept_inspect_serializer(v)
+       do
+               v.stream.write "\""
+               v.stream.write escape_to_nit
+               v.stream.write "\""
+       end
+end
+
+redef class Collection[E]
+       private fun serialize_as_inspect(v: InspectSerializer)
+       do
+               v.stream.write "["
+               var is_first = true
+               for e in self do
+                       if is_first then
+                               is_first = false
+                       else
+                               v.stream.write ", "
+                       end
+
+                       if not v.try_to_serialize(e) then
+                               assert e != null
+                               v.stream.write e.inspect
+                       end
+               end
+               v.stream.write "]"
+       end
+end
+
+redef class SimpleCollection[E]
+       redef fun accept_inspect_serializer_core(v)
+       do
+               v.stream.write " "
+               serialize_as_inspect v
+       end
+end
+
+redef class Map[K, V]
+       redef fun accept_inspect_serializer_core(v)
+       do
+               v.stream.write " \{"
+
+               var first = true
+               for key, val in self do
+                       if not first then
+                               v.stream.write ", "
+                       else first = false
+
+                       if not v.try_to_serialize(key) then
+                               assert key != null
+                               v.stream.write key.inspect
+                       end
+
+                       v.stream.write ":"
+
+                       if not v.try_to_serialize(val) then
+                               assert val != null
+                               v.stream.write val.inspect
+                       end
+               end
+
+               v.stream.write "\}"
+       end
+end
diff --git a/lib/serialization/safe.nit b/lib/serialization/safe.nit
new file mode 100644 (file)
index 0000000..36fe4b9
--- /dev/null
@@ -0,0 +1,109 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Services for safer deserialization engines
+module safe
+
+import poset
+
+import serialization
+private import engine_tools
+
+# Deserialization engine limiting which types can be deserialized
+class SafeDeserializer
+       super Deserializer
+
+       # Accepted parameterized classes to deserialize
+       #
+       # If `whitelist.empty`, all types are accepted.
+       #
+       # ~~~
+       # import json
+       #
+       # class MyClass
+       #     serialize
+       # end
+       #
+       # var json_string = """
+       # {"__class": "MyClass"}
+       # """
+       #
+       # var deserializer = new JsonDeserializer(json_string)
+       # var obj = deserializer.deserialize
+       # assert deserializer.errors.is_empty
+       # assert obj isa MyClass
+       #
+       # deserializer = new JsonDeserializer(json_string)
+       # deserializer.whitelist.add "Array[String]"
+       # deserializer.whitelist.add "AnotherAcceptedClass"
+       # obj = deserializer.deserialize
+       # assert deserializer.errors.length == 1
+       # assert obj == null
+       # ~~~
+       var whitelist = new Array[Text]
+
+       # Should objects be checked if they a subtype of the static type before deserialization?
+       #
+       # Defaults to `true`, as it should always be activated.
+       # It can be turned off to implement the subtype check itself.
+       var check_subtypes = true is writable
+
+       # Should `self` accept to deserialize an instance of `dynamic_type` for an attribute wuth `static_type`?
+       #
+       # Uses `whitelist` if not empty...
+       # Check correct inheritance if `check_subtypes`...
+       fun accept(dynamic_type: Text, static_type: nullable Text): Bool
+       do
+               if whitelist.not_empty and not whitelist.has(dynamic_type) then
+                       errors.add new Error("Deserialization Error: '{dynamic_type}' not in whitelist")
+                       return false
+               end
+
+               if static_type != null and check_subtypes then
+                       var static_class = static_type.strip_nullable_and_params.to_s
+                       var dynamic_class = dynamic_type.strip_nullable_and_params.to_s
+                       if not class_inheritance_metamodel.has_edge(dynamic_class, static_class) then
+                               errors.add new Error("Deserialization Error: `{dynamic_type}` is not a subtype of the static type `{static_type}`")
+                               return false
+                       end
+               end
+
+               return true
+       end
+end
+
+redef class Sys
+       # Class inheritance graph, implemented by the `json` package
+       #
+       # ~~~
+       # import json
+       #
+       # var hierarchy = class_inheritance_metamodel
+       # assert hierarchy.has_edge("String", "Object")
+       # assert not hierarchy.has_edge("Object", "String")
+       # ~~~
+       fun class_inheritance_metamodel: POSet[String] is abstract
+end
+
+redef class Deserializer
+       redef fun deserialize_class(name)
+       do
+               if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
+               if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
+               if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
+               if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
+
+               return super
+       end
+end
index 597ae98..cdbaed4 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Abstract services to serialize Nit objects to different formats
-#
-# This module declares the `serialize` annotation to mark Nit classes as serializable.
-# For an introduction to this service, refer to the documentation of the `serialization` group.
-# This documentation provides more technical information on interesting entitie of this module.
-#
-# Interesting entities for end users of serializable classes:
-#
-# * Serialize an instance subclass of `Serializable` with either
-#   `Serializer::serializable` and `Serializable::serialize`.
-# * Deserialize an object using `Deserializer::deserialize`.
-#   The object type must the be checked with an `assert` or otherwise.
-#
-# Interesting entities to create custom serializable classes:
-#
-# * Subclass `Serializable` to declare a class as serializable and to customize
-#   the serialization and deserialization behavior.
-# * Redefine `Serializable::core_serialize_to` to customize the serialization
-#   of the receiver class.
-# * Redefine `Deserializer::deserialize_class` to customize the deserialization
-#   of a specific class by name.
-#
-# Interesting entities for serialization format:
-#
-# * Subclass `Serializer` and `Deserializer` with custom serices.
-# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
-# * In `Deserializer`; `deserialize`, `deserialize_attribute and
-#   `notify_of_creation` must be redefined.
-module serialization is
-       new_annotation auto_serializable
-       new_annotation serialize
-       new_annotation noserialize
-       new_annotation serialize_as
-end
-
-intrude import core::queue
-import meta
-
-# Abstract serialization service to be sub-classed by specialized services.
-interface Serializer
-       # Entry point method of this service, serialize the `object`
-       #
-       # This method, and refinements, should handle `null` and probably
-       # use double dispatch to customize the bahavior per serializable objects.
-       fun serialize(object: nullable Serializable) is abstract
-
-       # The object currently serialized by `serialized`
-       #
-       # Can be used by a custom serializer to add domain-specific serialization behavior.
-       protected fun current_object: nullable Object is abstract
-
-       # Serialize an object, with full serialization or a simple reference
-       protected fun serialize_reference(object: Serializable) is abstract
-
-       # Serialize an attribute to compose a serializable object
-       #
-       # This method should be called from `Serializable::core_serialize_to`.
-       fun serialize_attribute(name: String, value: nullable Object)
-       do
-               if not try_to_serialize(value) then
-                       warn("argument {name} of type {value.class_name} is not serializable.")
-               end
-       end
-
-       # Serialize `value` is possie, i.e. it is `Serializable` or `null`
-       fun try_to_serialize(value: nullable Object): Bool
-       do
-               if value isa Serializable then
-                       value.serialize_to_or_delay(self)
-               else if value == null then
-                       serialize value
-               else return false
-               return true
-       end
-
-       # The method is called when a standard `value` is serialized
-       #
-       # The default behavior is to call `value.core_serialize_to(self)` but it
-       # can be redefined by a custom serializer to add domain-specific serialization behavior.
-       fun serialize_core(value: Serializable)
-       do
-               value.core_serialize_to(self)
-       end
-
-       # Warn of problems and potential errors (such as if an attribute
-       # is not serializable)
-       fun warn(msg: String) do print "Serialization warning: {msg}"
-end
-
-# Abstract deserialization service
-#
-# The main service is `deserialize`.
-abstract class Deserializer
-       # Deserialize and return an object, storing errors in the attribute `errors`
-       #
-       # If a `static_type` is given, only subtypes of the `static_type` are accepted.
-       #
-       # This method behavior varies according to the implementation engines.
-       fun deserialize(static_type: nullable String): nullable Object is abstract
-
-       # Deserialize the attribute with `name` from the object open for deserialization
-       #
-       # The `static_type` restricts what kind of object can be deserialized.
-       #
-       # Return the deserialized value or null on error, and set
-       # `deserialize_attribute_missing` to whether the attribute was missing.
-       #
-       # Internal method to be implemented by the engines.
-       fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
-
-       # Was the attribute queried by the last call to `deserialize_attribute` missing?
-       var deserialize_attribute_missing = false
-
-       # Register a newly allocated object (even if not completely built)
-       #
-       # Internal method called by objects in creation, to be implemented by the engines.
-       fun notify_of_creation(new_object: Object) is abstract
-
-       # Deserialize the next available object as an instance of `class_name`
-       #
-       # Return the deserialized object on success and
-       # record in `errors` if `class_name` is unknown.
-       #
-       # This method should be redefined for each custom subclass of `Serializable`.
-       # All refinement should look for a precise `class_name` and call super
-       # on unsupported classes.
-       protected fun deserialize_class(class_name: String): nullable Object do
-               if class_name == "Error" then return new Error.from_deserializer(self)
-               return deserialize_class_intern(class_name)
-       end
-
-       # Generated service to deserialize the next available object as an instance of `class_name`
-       #
-       # Refinements to this method will be generated by the serialization phase.
-       # To avoid conflicts, there should not be any other refinements to this method.
-       # You can instead use `deserialize_class`.
-       protected fun deserialize_class_intern(class_name: String): nullable Object do
-               errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
-               return null
-       end
-
-       # Should `self` keep trying to deserialize an object after an error?
-       #
-       # This behavior takes effect after each attribute deserialization with
-       # errors such as a missing attribute or the value is of the wrong type.
-       # If `keep_going`, the attribute will be skipped but the engine will
-       # deserialize the next attribute.
-       # If `not keep_going`, the engine stops deserializing right away.
-       #
-       # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
-       #
-       # Default at `true`.
-       var keep_going: nullable Bool = null is writable
-
-       # Errors encountered in the last call to `deserialize`
-       var errors = new Array[Error]
-end
-
-# Deserialization error related to an attribute of `receiver`
-abstract class AttributeError
-       super Error
-
-       # Parent object of the problematic attribute
-       var receiver: Object
-
-       # Name of the problematic attribute in `receiver`
-       var attribute_name: String
-end
-
-# Invalid dynamic type for a deserialized attribute
-class AttributeTypeError
-       super AttributeError
-
-       autoinit receiver, attribute_name, attribute, expected_type
-
-       # Deserialized object that isn't of the `expected_type`
-       var attribute: nullable Object
-
-       # Name of the type expected for `attribute`
-       var expected_type: String
-
-       redef var message is lazy do
-               var attribute = attribute
-               var found_type = if attribute != null then attribute.class_name else "null"
-
-               return "Deserialization Error: {
-               }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
-       end
-end
-
-# Missing attribute at deserialization
-class AttributeMissingError
-       super AttributeError
-
-       autoinit receiver, attribute_name
-
-       redef var message is lazy do
-               return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
-       end
-end
-
-# Instances of this class can be passed to `Serializer::serialize`
-interface Serializable
-       # Serialize `self` to `serializer`
-       #
-       # This is a shortcut to `Serializer::serialize`.
-       fun serialize_to(serializer: Serializer) do serializer.serialize(self)
-
-       # Actual serialization of `self` to `serializer`
-       #
-       # This writes the full data of `self` to `serializer`.
-       #
-       # This method can be redefined in sub classes and refinements.
-       # It should use `Serializer::serialize_attribute` to to register real or
-       # logical attributes.
-       #
-       # Any refinement should have its equivalent refinement of
-       # `Deserializer::deserialize_class` to support this custom deserialization.
-       fun core_serialize_to(serializer: Serializer) do end
-
-       # Accept references or force direct serialization (using `serialize_to`)
-       #
-       # The subclass change the default behavior, which will accept references,
-       # to force to always serialize copies of `self`.
-       private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
-
-       # Create an instance of this class from the `deserializer`
-       #
-       # This constructor is refined by subclasses to correctly build their instances.
-       init from_deserializer(deserializer: Deserializer) is nosuper do end
-end
-
-redef interface Object
-       # Is `self` the same as `other` in a serialization context?
-       #
-       # Used to determine if an object has already been serialized.
-       fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
-
-       # Hash value use for serialization
-       #
-       # Used in combination with `is_same_serialized`. If two objects are the same
-       # in a serialization context, they must have the same `serialization_hash`.
-       fun serialization_hash: Int do return object_id
-end
-
-# Instances of this class are not delayed and instead serialized immediately
-# This applies mainly to `universal` types
-interface DirectSerializable
-       super Serializable
-
-       redef fun serialize_to_or_delay(v) do serialize_to(v)
-end
-
-redef class Bool super DirectSerializable end
-redef class Char super DirectSerializable end
-redef class Int super DirectSerializable end
-redef class Float super DirectSerializable end
-redef class CString super DirectSerializable end
-redef class Text super DirectSerializable end
-redef class SimpleCollection[E] super Serializable end
-redef class Map[K, V] super Serializable end
-
-redef class Couple[F, S]
-       super Serializable
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-               var first = v.deserialize_attribute("first")
-               var second = v.deserialize_attribute("second")
-               init(first, second)
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("first", first)
-               v.serialize_attribute("second", second)
-       end
-end
-
-redef class Ref[E]
-       super Serializable
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-               var item = v.deserialize_attribute("item")
-               init item
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("item", first)
-       end
-end
-
-redef class Error
-       super Serializable
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var message = v.deserialize_attribute("message")
-               if not message isa String then message = ""
-               init message
-
-               var cause = v.deserialize_attribute("cause")
-               if cause isa nullable Error then self.cause = cause
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("message", message)
-               v.serialize_attribute("cause", cause)
-       end
-end
-
-# ---
-# core::queue classes
-
-redef abstract class ProxyQueue[E]
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
-               if not seq isa Sequence[E] then seq = new Array[E]
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "seq")
-               end
-
-               init seq
-       end
-
-       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
-end
-
-redef class RandQueue[E]
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
-               if not seq isa SimpleCollection[E] then seq = new Array[E]
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "seq")
-               end
-
-               init seq
-       end
-
-       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
-end
-
-redef class MinHeap[E]
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
-               if not items isa Array[E] then items = new Array[E]
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "items")
-               end
-
-               var comparator = v.deserialize_attribute("comparator", "Comparator")
-               if not comparator isa Comparator then comparator = default_comparator
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "comparator")
-               end
-
-               init comparator
-               self.items.add_all items
-       end
+# General serialization services
+module serialization
 
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("items", items)
-               v.serialize_attribute("comparator", comparator)
-       end
-end
+import serialization_core
+import inspect
diff --git a/lib/serialization/serialization_core.nit b/lib/serialization/serialization_core.nit
new file mode 100644 (file)
index 0000000..a19cfca
--- /dev/null
@@ -0,0 +1,390 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Abstract services to serialize Nit objects to different formats
+#
+# This module declares the `serialize` annotation to mark Nit classes as serializable.
+# For an introduction to this service, refer to the documentation of the `serialization` group.
+# This documentation provides more technical information on interesting entitie of this module.
+#
+# Interesting entities for end users of serializable classes:
+#
+# * Serialize an instance subclass of `Serializable` with either
+#   `Serializer::serializable` and `Serializable::serialize`.
+# * Deserialize an object using `Deserializer::deserialize`.
+#   The object type must the be checked with an `assert` or otherwise.
+#
+# Interesting entities to create custom serializable classes:
+#
+# * Subclass `Serializable` to declare a class as serializable and to customize
+#   the serialization and deserialization behavior.
+# * Redefine `Serializable::core_serialize_to` to customize the serialization
+#   of the receiver class.
+# * Redefine `Deserializer::deserialize_class` to customize the deserialization
+#   of a specific class by name.
+#
+# Interesting entities for serialization format:
+#
+# * Subclass `Serializer` and `Deserializer` with custom serices.
+# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
+# * In `Deserializer`; `deserialize`, `deserialize_attribute and
+#   `notify_of_creation` must be redefined.
+module serialization_core is
+       new_annotation auto_serializable
+       new_annotation serialize
+       new_annotation noserialize
+       new_annotation serialize_as
+end
+
+intrude import core::queue
+import meta
+
+# Abstract serialization service to be sub-classed by specialized services.
+interface Serializer
+       # Entry point method of this service, serialize the `object`
+       #
+       # This method, and refinements, should handle `null` and probably
+       # use double dispatch to customize the bahavior per serializable objects.
+       fun serialize(object: nullable Serializable) is abstract
+
+       # The object currently serialized by `serialized`
+       #
+       # Can be used by a custom serializer to add domain-specific serialization behavior.
+       protected fun current_object: nullable Object is abstract
+
+       # Serialize an object, with full serialization or a simple reference
+       protected fun serialize_reference(object: Serializable) is abstract
+
+       # Serialize an attribute to compose a serializable object
+       #
+       # This method should be called from `Serializable::core_serialize_to`.
+       fun serialize_attribute(name: String, value: nullable Object)
+       do
+               if not try_to_serialize(value) then
+                       assert value != null # null would have been serialized
+                       warn("argument {name} of type {value.class_name} is not serializable.")
+               end
+       end
+
+       # Serialize `value` is possible, i.e. it is `Serializable` or `null`
+       fun try_to_serialize(value: nullable Object): Bool
+       do
+               if value isa Serializable then
+                       value.serialize_to_or_delay(self)
+               else if value == null then
+                       serialize value
+               else return false
+               return true
+       end
+
+       # The method is called when a standard `value` is serialized
+       #
+       # The default behavior is to call `value.core_serialize_to(self)` but it
+       # can be redefined by a custom serializer to add domain-specific serialization behavior.
+       fun serialize_core(value: Serializable)
+       do
+               value.core_serialize_to(self)
+       end
+
+       # Warn of problems and potential errors (such as if an attribute
+       # is not serializable)
+       fun warn(msg: String) do print "Serialization warning: {msg}"
+end
+
+# Abstract deserialization service
+#
+# The main service is `deserialize`.
+abstract class Deserializer
+       # Deserialize and return an object, storing errors in the attribute `errors`
+       #
+       # If a `static_type` is given, only subtypes of the `static_type` are accepted.
+       #
+       # This method behavior varies according to the implementation engines.
+       fun deserialize(static_type: nullable String): nullable Object is abstract
+
+       # Deserialize the attribute with `name` from the object open for deserialization
+       #
+       # The `static_type` restricts what kind of object can be deserialized.
+       #
+       # Return the deserialized value or null on error, and set
+       # `deserialize_attribute_missing` to whether the attribute was missing.
+       #
+       # Internal method to be implemented by the engines.
+       fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
+
+       # Was the attribute queried by the last call to `deserialize_attribute` missing?
+       var deserialize_attribute_missing = false
+
+       # Register a newly allocated object (even if not completely built)
+       #
+       # Internal method called by objects in creation, to be implemented by the engines.
+       fun notify_of_creation(new_object: Object) is abstract
+
+       # Deserialize the next available object as an instance of `class_name`
+       #
+       # Return the deserialized object on success and
+       # record in `errors` if `class_name` is unknown.
+       #
+       # This method should be redefined for each custom subclass of `Serializable`.
+       # All refinement should look for a precise `class_name` and call super
+       # on unsupported classes.
+       protected fun deserialize_class(class_name: Text): nullable Object do
+               if class_name == "Error" then return new Error.from_deserializer(self)
+               return deserialize_class_intern(class_name)
+       end
+
+       # Generated service to deserialize the next available object as an instance of `class_name`
+       #
+       # Refinements to this method will be generated by the serialization phase.
+       # To avoid conflicts, there should not be any other refinements to this method.
+       # You can instead use `deserialize_class`.
+       protected fun deserialize_class_intern(class_name: Text): nullable Object do
+               errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
+               return null
+       end
+
+       # Should `self` keep trying to deserialize an object after an error?
+       #
+       # This behavior takes effect after each attribute deserialization with
+       # errors such as a missing attribute or the value is of the wrong type.
+       # If `keep_going`, the attribute will be skipped but the engine will
+       # deserialize the next attribute.
+       # If `not keep_going`, the engine stops deserializing right away.
+       #
+       # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
+       #
+       # Default at `true`.
+       var keep_going: nullable Bool = null is writable
+
+       # Errors encountered in the last call to `deserialize`
+       var errors = new Array[Error]
+end
+
+# Deserialization error related to an attribute of `receiver`
+abstract class AttributeError
+       super Error
+
+       # Parent object of the problematic attribute
+       var receiver: Object
+
+       # Name of the problematic attribute in `receiver`
+       var attribute_name: String
+end
+
+# Invalid dynamic type for a deserialized attribute
+class AttributeTypeError
+       super AttributeError
+
+       autoinit receiver, attribute_name, attribute, expected_type
+
+       # Deserialized object that isn't of the `expected_type`
+       var attribute: nullable Object
+
+       # Name of the type expected for `attribute`
+       var expected_type: String
+
+       redef var message is lazy do
+               var attribute = attribute
+               var found_type = if attribute != null then attribute.class_name else "null"
+
+               return "Deserialization Error: {
+               }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
+       end
+end
+
+# Missing attribute at deserialization
+class AttributeMissingError
+       super AttributeError
+
+       autoinit receiver, attribute_name
+
+       redef var message is lazy do
+               return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
+       end
+end
+
+# Instances of this class can be passed to `Serializer::serialize`
+interface Serializable
+       # Serialize `self` to `serializer`
+       #
+       # This is a shortcut to `Serializer::serialize`.
+       fun serialize_to(serializer: Serializer) do serializer.serialize(self)
+
+       # Actual serialization of `self` to `serializer`
+       #
+       # This writes the full data of `self` to `serializer`.
+       #
+       # This method can be redefined in sub classes and refinements.
+       # It should use `Serializer::serialize_attribute` to to register real or
+       # logical attributes.
+       #
+       # Any refinement should have its equivalent refinement of
+       # `Deserializer::deserialize_class` to support this custom deserialization.
+       fun core_serialize_to(serializer: Serializer) do end
+
+       # Accept references or force direct serialization (using `serialize_to`)
+       #
+       # The subclass change the default behavior, which will accept references,
+       # to force to always serialize copies of `self`.
+       private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
+
+       # Create an instance of this class from the `deserializer`
+       #
+       # This constructor is refined by subclasses to correctly build their instances.
+       init from_deserializer(deserializer: Deserializer) is nosuper do end
+end
+
+# Instances of this class are not delayed and instead serialized immediately
+# This applies mainly to `universal` types
+interface DirectSerializable
+       super Serializable
+
+       redef fun serialize_to_or_delay(v) do serialize_to(v)
+end
+
+redef class Bool super DirectSerializable end
+redef class Char super DirectSerializable end
+redef class Byte super DirectSerializable end
+redef class Int super DirectSerializable end
+redef class Float super DirectSerializable end
+redef class CString super DirectSerializable end
+redef class Text super DirectSerializable end
+redef class SimpleCollection[E] super Serializable end
+redef class Map[K, V] super Serializable end
+
+redef class Couple[F, S]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var first = v.deserialize_attribute("first")
+               var second = v.deserialize_attribute("second")
+               init(first, second)
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("first", first)
+               v.serialize_attribute("second", second)
+       end
+end
+
+redef class Ref[E]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var item = v.deserialize_attribute("item")
+               init item
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("item", first)
+       end
+end
+
+redef class Error
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var message = v.deserialize_attribute("message")
+               if not message isa String then message = ""
+               init message
+
+               var cause = v.deserialize_attribute("cause")
+               if cause isa nullable Error then self.cause = cause
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("message", message)
+               v.serialize_attribute("cause", cause)
+       end
+end
+
+# ---
+# core::queue classes
+
+redef abstract class ProxyQueue[E]
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
+               if not seq isa Sequence[E] then seq = new Array[E]
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "seq")
+               end
+
+               init seq
+       end
+
+       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class RandQueue[E]
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
+               if not seq isa SimpleCollection[E] then seq = new Array[E]
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "seq")
+               end
+
+               init seq
+       end
+
+       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class MinHeap[E]
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
+               if not items isa Array[E] then items = new Array[E]
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "items")
+               end
+
+               var comparator = v.deserialize_attribute("comparator", "Comparator")
+               if not comparator isa Comparator then comparator = default_comparator
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "comparator")
+               end
+
+               init comparator
+               self.items.add_all items
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("items", items)
+               v.serialize_attribute("comparator", comparator)
+       end
+end
index 4a30827..0b48c22 100644 (file)
                          ng-class='currentTab == "signature" ? "active" : ""'>
                                <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
                        </div>
-                       <div id='{{mentity.html_id}}-doc' class='tab-pane'
-                         ng-class='currentTab == "doc" ? "active" : ""'>
-                               <entity-doc mentity='mentity' />
-                       </div>
                        <div id='{{mentity.html_id}}-grade' class='tab-pane'
                          ng-class='currentTab == "grade" ? "active" : ""'>
                                <entity-rating mentity='mentity' ratings='ratings'>
@@ -30,9 +26,6 @@
                                <li ng-class='currentTab == "signature" ? "active" : ""' ng-if='!noSynopsis'>
                                        <a ng-click='currentTab = "signature"'>Signature</a>
                                </li>
-                               <li ng-class='currentTab == "doc" ? "active" : ""'>
-                                       <a ng-click='currentTab = "doc"'>Doc</a>
-                               </li>
                                <li ng-class='currentTab == "grade" ? "active" : ""'>
                                        <a ng-click='loadEntityStars(); currentTab = "grade"'>Grade</a>
                                </li>
index 7c69f1e..06955d0 100644 (file)
@@ -1,5 +1,64 @@
-<span class="glyphicon glyphicon-tag" ng-class='{
+<span>
+       <!-- package -->
+       <span ng-if='mentity.class_name == "MPackage"'>
+               <span class='glyphicon glyphicon-book text-muted' style='top: 3px' />
+       </span>
+
+       <!-- groups -->
+       <span ng-if='mentity.class_name == "MGroup"'>
+               <span class='glyphicon glyphicon-folder-close text-muted' />
+       </span>
+
+       <!-- modules -->
+       <span ng-if='mentity.class_name == "MModule"'>
+               <span class='glyphicon glyphicon-file text-muted' style='top: 2px' />
+       </span>
+
+       <!-- classes -->
+       <span ng-if='mentity.class_name == "MClass"'>
+               <span class='glyphicon glyphicon-stop' ng-class='{
+                       "text-success": mentity.visibility == "public",
+                       "text-warning": mentity.visibility == "protected",
+                       "text-danger": mentity.visibility == "private",
+               }' />
+       </span>
+
+       <!-- classdefs -->
+       <span ng-if='mentity.class_name == "MClassDef"' ng-class='{
                "text-success": mentity.visibility == "public",
                "text-warning": mentity.visibility == "protected",
                "text-danger": mentity.visibility == "private",
-}' />
+       }'>
+               <span class='glyphicon' style='top: 2px; left:2px' ng-class='{
+                       "glyphicon-plus": mentity.is_intro,
+                       "glyphicon-asterisk": !mentity.is_intro
+               }' />
+       </span>
+
+       <!-- props -->
+       <span ng-if='mentity.class_name == "MAttribute" ||
+                mentity.class_name == "MMethod" ||
+                mentity.class_name == "MVirtualTypeProp"
+       '>
+               <span class='glyphicon glyphicon-tag' ng-class='{
+                       "text-success": mentity.visibility == "public",
+                       "text-warning": mentity.visibility == "protected",
+                       "text-danger": mentity.visibility == "private",
+               }' />
+       </span>
+
+       <!-- propdefs -->
+       <span ng-if='mentity.class_name == "MAttributeDef" ||
+               mentity.class_name == "MMethodDef" ||
+               mentity.class_name == "MVirtualTypePropDef"'
+               ng-class='{
+                       "text-success": mentity.visibility == "public",
+                       "text-warning": mentity.visibility == "protected",
+                       "text-danger": mentity.visibility == "private"
+       }'>
+               <span class='glyphicon' style='top: 2px; left:2px' ng-class='{
+                       "glyphicon-plus": mentity.is_intro,
+                       "glyphicon-asterisk": !mentity.is_intro
+               }' />
+       </span>
+</span>
index d5dd3ef..c5deb0a 100644 (file)
                                .state('doc.entity.doc', {
                                        url: '',
                                        templateUrl: 'views/doc/doc.html',
-                                       controller: function(mentity) {
+                                       resolve: {
+                                               doc: function(Model, $q, $stateParams, $state) {
+                                                       var d = $q.defer();
+                                                       Model.loadEntityDoc($stateParams.id, d.resolve,
+                                                               function() {
+                                                                       $state.go('404', null, { location: false })
+                                                               });
+                                                       return d.promise;
+                                               }
+                                       },
+                                       controller: function(mentity, doc) {
                                                this.mentity = mentity;
+                                               this.doc = doc;
                                        },
                                        controllerAs: 'vm',
                                })
                                                                        $state.go('404', null, { location: false })
                                                                });
                                                        return d.promise;
+                                               },
+                                               inh: function(Model, $q, $stateParams, $state) {
+                                                       var d = $q.defer();
+                                                       Model.loadEntityInh($stateParams.id, d.resolve,
+                                                               function() {
+                                                                       $state.go('404', null, { location: false })
+                                                               });
+                                                       return d.promise;
                                                }
                                        },
-                                       controller: function(graph, $sce) {
+                                       controller: function(inh, graph, $sce) {
                                                this.graph = $sce.trustAsHtml(graph);
+                                               this.inh = inh;
                                        },
                                        controllerAs: 'vm',
                                })
                                                .error(cbErr);
                                },
 
+                               loadEntityDoc: function(id, cb, cbErr) {
+                                       $http.get('/api/entity/' + id + '/doc')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
                                loadEntityLinearization: function(id, cb, cbErr) {
                                        $http.get('/api/linearization/' + id)
                                                .success(cb)
                                                .error(cbErr);
                                },
 
+                               loadEntityInh: function(id, cb, cbErr) {
+                                       $http.get('/api/inheritance/' + id)
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
                                search: function(q, n, cb, cbErr) {
                                        $http.get('/api/search?q=' + q + '&n=' + n)
                                                .success(cb)
index 66cd995..7cd0ea2 100644 (file)
@@ -1,4 +1,10 @@
 <div>
-       <entity-list list-title='Class definitions'
+       <entity-list list-title='Groups' ng-if='vm.mentity.class_name == "MPackage"'
+               list-entities='vm.defs' list-object-filter='{}' />
+
+       <entity-list list-title='Groups & Modules' ng-if='vm.mentity.class_name == "MGroup"'
+               list-entities='vm.defs' list-object-filter='{}' />
+
+       <entity-list list-title='Class definitions' ng-if='vm.mentity.class_name == "MModule"'
                list-entities='vm.defs' list-object-filter='{}' />
 </div>
index 1cf3c8b..1e60694 100644 (file)
@@ -3,22 +3,16 @@
                <ui-summary target='#summary-content' />
        </div>
        <div class='col-xs-9' id='summary-content'>
-               <entity-card mentity='vm.mentity' default-tab='doc' no-synopsis='true' />
-
-               <entity-list list-title='Groups' list-entities='vm.mentity.mgroups'
-                       list-object-filter='{}' />
-
-               <entity-list list-title='Parent group' list-entities='[vm.mentity.parent]'
-                       list-object-filter='{}' ng-if='vm.mentity.parent' />
-
-               <entity-list list-title='Subgroups' list-entities='vm.mentity.mgroups'
-                       list-object-filter='{}' />
-
-               <entity-list list-title='Modules' list-entities='vm.mentity.mmodules'
-                       list-object-filter='{}' />
-
-               <entity-list list-title='Imported modules' list-entities='vm.mentity.imports'
-                       list-object-filter='{}' />
+               <div class='card'>
+                       <div class='card-body'>
+                               <div ng-if='vm.doc'>
+                                       <div ng-bind-html='vm.doc.documentation'></div>
+                               </div>
+                               <div ng-if='!vm.doc'>
+                                       <i class='text-muted'>No documentation for this entity.</i>
+                               </div>
+                       </div>
+               </div>
 
                <entity-list list-title='Introduced classes' list-entities='vm.mentity.intro_mclasses'
                        list-object-filter='{}' />
                <entity-list list-title='Class redefinitions' list-entities='vm.mentity.redef_mclassdefs'
                        list-object-filter='{}' />
 
-               <entity-list list-title='Parents'
-                       list-entities='vm.mentity.parents'
-                       list-object-filter='{}' />
-
                <entity-list list-title='Constructors'
                        list-entities='vm.mentity.all_mproperties'
                        list-object-filter='{is_init: true}' />
index 1a0dcf7..3543b9f 100644 (file)
                        </a>
                </li>
 
+               <!-- definitions -->
+               <li role='presentation' ui-sref-active='active' ng-if='
+                               vm.mentity.class_name == "MPackage" ||
+                               vm.mentity.class_name == "MGroup" ||
+                               vm.mentity.class_name == "MModule"'>
+                       <a ui-sref='.defs'>
+                               <span class='glyphicon glyphicon-list'/>
+                               <span ng-if='vm.mentity.class_name == "MPackage"'>Groups</span>
+                               <span ng-if='vm.mentity.class_name == "MGroup"'>Content</span>
+                               <span ng-if='vm.mentity.class_name == "MModule"'>Classes</span>
+                       </a>
+               </li>
+
                <!-- graph -->
                <li role='presentation' ui-sref-active='active' ng-if='
                                vm.mentity.class_name == "MPackage" ||
                        </a>
                </li>
 
-               <!-- definitions -->
-               <li role='presentation' ui-sref-active='active' ng-if='
-                               vm.mentity.class_name == "MModule"'>
-                       <a ui-sref='.defs'>
-                               <span class='glyphicon glyphicon-asterisk'/> Class definitions
-                       </a>
-               </li>
-
                <!-- all -->
                <li role='presentation' ui-sref-active='active' ng-if='
                                vm.mentity.class_name == "MClass"'>
index a346d99..2968cce 100644 (file)
@@ -1,5 +1,15 @@
-<div class='card'>
-       <div class='card-body text-center'>
-               <entity-graph mentity='mentity' graph='vm.graph' />
+<div>
+       <div class='card'>
+               <div class='card-body text-center'>
+                       <entity-graph mentity='mentity' graph='vm.graph' />
+               </div>
        </div>
+
+       <!-- Parents -->
+       <entity-list list-title='Parents'
+               list-entities='vm.inh.direct_greaters' list-object-filter='{}' />
+
+       <!-- Children -->
+       <entity-list list-title='Children'
+               list-entities='vm.inh.direct_smallers' list-object-filter='{}' />
 </div>
index 3e4e38d..1ae78e6 100644 (file)
@@ -143,7 +143,11 @@ do
                if deserializer_npropdef == null then return
 
                # Collect local types expected to be deserialized
-               var types_to_deserialize = new Set[String]
+               #
+               # Map types' `name` to their `full_name`.
+               #
+               # FIXME use only the full name when there's a `class_full_name`
+               var types_to_deserialize = new Map[String, String]
 
                ## Local serializable standard class without parameters
                for nclassdef in nclassdefs do
@@ -151,7 +155,7 @@ do
                        if mclass == null then continue
 
                        if mclass.arity == 0 and mclass.kind == concrete_kind then
-                               types_to_deserialize.add mclass.name
+                               types_to_deserialize[mclass.name] = mclass.full_name
                        end
                end
 
@@ -188,7 +192,7 @@ do
                                                break
                                        end
 
-                                       if is_serializable then types_to_deserialize.add mtype.to_s
+                                       if is_serializable then types_to_deserialize[mtype.name] = mtype.full_name
                                end
                        end
                end
@@ -198,8 +202,15 @@ do
                code.add "redef fun deserialize_class_intern(name)"
                code.add "do"
 
-               for name in types_to_deserialize do
-                       code.add "      if name == \"{name}\" then return new {name}.from_deserializer(self)"
+               for name, full_name in types_to_deserialize do
+
+                       if full_name.has('-') then
+                               # Invalid module name, it is either artificial or a script
+                               # without module declaration (like those generated by nitunit)
+                               full_name = name
+                       end
+
+                       code.add "      if name == \"{name}\" then return new {full_name}.from_deserializer(self)"
                end
 
                code.add "      return super"
index a269ee4..7e9a506 100644 (file)
@@ -225,7 +225,7 @@ private class SerializationPhasePreModel
                end
 
                var code = """
-redef init from_deserializer(v: Deserializer) do abort"""
+redef init from_deserializer(v) do abort"""
 
                var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
                npropdefs.add npropdef
index 3005aa7..f654c3e 100644 (file)
@@ -21,14 +21,16 @@ import pipeline
 import astutil
 import serialization
 
-# Fully process a content as a nit source file.
-fun hightlightcode(hl: HighlightVisitor, content: String): HLCode
+# Fully process `content` as a Nit source file.
+#
+# Set `print_errors = true` to print errors in the code to the console.
+fun hightlightcode(hl: HighlightVisitor, content: String, print_errors: nullable Bool): HLCode
 do
        # Prepare a stand-alone tool context
        var tc = new ToolContext
        tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core
        tc.keep_going = true # no exit, obviously
-       tc.opt_warn.value = -1 # no output, obviously
+       if print_errors != true then tc.opt_warn.value = -1 # no output
 
        # Prepare an stand-alone model and model builder.
        # Unfortunately, models are enclosing and append-only.
index 6d012e6..125866a 100644 (file)
@@ -135,6 +135,7 @@ redef class ModelBuilder
                                alpha_comparator.sort(fs)
                                # Try each entry as a group or a module
                                for f in fs do
+                                       if f.first == '.' then continue
                                        var af = a/f
                                        mgroup = identify_group(af)
                                        if mgroup != null then
@@ -636,6 +637,7 @@ redef class ModelBuilder
                var files = p.files
                alpha_comparator.sort(files)
                for f in files do
+                       if f.first == '.' then continue
                        var fp = p/f
                        var g = identify_group(fp)
                        # Recursively scan for groups of the same package
index ba6a684..c80f929 100644 (file)
@@ -473,19 +473,19 @@ redef class MType
                end
                #print "4.is {sub} a {sup}? <- no more resolution"
 
-               assert sub isa MClassType # It is the only remaining type
-
-               # A unfixed formal type can only accept itself
-               if sup isa MFormalType then
-                       return false
+               if sub isa MBottomType or sub isa MErrorType then
+                       return true
                end
 
-               if sup isa MNullType then
-                       # `sup` accepts only null
+               assert sub isa MClassType else print_error "{sub} <? {sup}" # It is the only remaining type
+
+               # Handle sup-type when the sub-type is class-based (other cases must have be identified before).
+               if sup isa MFormalType or sup isa MNullType or sup isa MBottomType or sup isa MErrorType then
+                       # These types are not super-types of Class-based types.
                        return false
                end
 
-               assert sup isa MClassType # It is the only remaining type
+               assert sup isa MClassType else print_error "got {sup} {sub.inspect}" # It is the only remaining type
 
                # Now both are MClassType, we need to dig
 
index 5276632..45c429b 100644 (file)
@@ -53,8 +53,9 @@ private class ATypeCounterVisitor
        do
                if n isa AAnnotation then return
 
-               if n isa AType then
-                       var mclassdef = self.nclassdef.mclassdef.as(not null)
+               if n isa AType then do
+                       var mclassdef = self.nclassdef.mclassdef
+                       if mclassdef == null then break
                        var mtype = modelbuilder.resolve_mtype(mclassdef, n)
                        if mtype != null then
                                self.typecount.inc(mtype)
index e6c6896..7c43e51 100644 (file)
@@ -96,7 +96,7 @@ redef class MDoc
        end
 end
 
-redef class Location
+redef class nitc::Location
        serialize
 
        redef fun core_serialize_to(v) do
index a0ead05..c544446 100644 (file)
@@ -93,7 +93,7 @@ class TestModelSerialization
        end
 end
 
-redef class Location
+redef class nitc::Location
        serialize
 
        # Avoid diff on location absolute path
index 1e3fb5d..3af570a 100644 (file)
@@ -872,8 +872,8 @@ class NeoModel
        end
 
        # Get a `Location` from its string representation.
-       private fun to_location(loc: String): Location do
-               return new Location.from_string(loc)
+       private fun to_location(loc: String): nitc::Location do
+               return new nitc::Location.from_string(loc)
        end
 
        # Get a `MVisibility` from its string representation.
index 31077c4..4f1a148 100644 (file)
@@ -26,6 +26,7 @@ redef class APIRouter
                use("/search", new APISearch(config))
                use("/random", new APIRandom(config))
                use("/entity/:id", new APIEntity(config))
+               use("/entity/:id/doc", new APIEntityDoc(config))
                use("/code/:id", new APIEntityCode(config))
                use("/uml/:id", new APIEntityUML(config))
                use("/linearization/:id", new APIEntityLinearization(config))
@@ -147,6 +148,26 @@ class APIEntity
        end
 end
 
+# Return the full MDoc of a MEntity.
+#
+# Example: `GET /entity/core::Array/doc`
+class APIEntityDoc
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+
+               var obj = new JsonObject
+               var mdoc = mentity.mdoc_or_fallback
+               if mdoc != null then
+                       obj["documentation"] = mdoc.html_documentation.write_to_string
+                       obj["location"] = mdoc.location
+               end
+               res.json obj
+       end
+end
+
 # List ancestors, parents, child and descendants of MEntity
 #
 # Example: `GET /entity/core::Array/inheritance`
@@ -190,7 +211,13 @@ class APIEntityDefs
                var mentity = mentity_from_uri(req, res)
                if mentity == null then return
                var mentities: Array[MEntity]
-               if mentity isa MModule then
+               if mentity isa MPackage then
+                       mentities = mentity.mgroups.to_a
+               else if mentity isa MGroup then
+                       mentities = new Array[MEntity]
+                       mentities.add_all mentity.in_nesting.direct_smallers
+                       mentities.add_all mentity.mmodules
+               else if mentity isa MModule then
                        mentities = mentity.mclassdefs
                else if mentity isa MClass then
                        mentities = mentity.mclassdefs
index e9f1b61..d50cc2a 100644 (file)
@@ -207,6 +207,7 @@ redef class MEntityRef
                v.serialize_attribute("mdoc", mentity.mdoc_or_fallback)
                v.serialize_attribute("visibility", mentity.visibility.to_s)
                v.serialize_attribute("modifiers", mentity.collect_modifiers)
+               v.serialize_attribute("class_name", mentity.class_name)
                var mentity = self.mentity
                if mentity isa MMethod then
                        v.serialize_attribute("msignature", mentity.intro.msignature)
@@ -226,7 +227,6 @@ redef class MDoc
        # Add doc down processing
        redef fun core_serialize_to(v) do
                v.serialize_attribute("html_synopsis", html_synopsis.write_to_string)
-               v.serialize_attribute("html_documentation", html_documentation.write_to_string)
        end
 end
 
@@ -342,11 +342,9 @@ end
 redef class POSetElement[E]
        super Serializable
 
-       redef fun serialize_to(v) do
+       redef fun core_serialize_to(v) do
                assert self isa POSetElement[MEntity]
-               v.serialize_attribute("greaters", to_mentity_refs(greaters))
                v.serialize_attribute("direct_greaters", to_mentity_refs(direct_greaters))
                v.serialize_attribute("direct_smallers", to_mentity_refs(direct_smallers))
-               v.serialize_attribute("smallers", to_mentity_refs(smallers))
        end
 end
diff --git a/tests/sav/nitce/test_inspect_serialization.res b/tests/sav/nitce/test_inspect_serialization.res
new file mode 100644 (file)
index 0000000..2ee51e5
--- /dev/null
@@ -0,0 +1,49 @@
+# Custom:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Inspect:
+<A#0 b:true, c:'a', f:0.123, i:1234, serialization_specific_name:"asdf", n:null>
+
+# Custom:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Inspect:
+<B#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"hjkl", n:…>
+
+# Custom:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Inspect:
+<C#0 a:<A#1>, b:<B#2>, aa:<A#1>>
+
+# Custom:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Inspect:
+<D#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"new line …>
+
+# Custom:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Inspect:
+<E#0 a:<Array#1>, b:<Array#2>>
+
+# Custom:
+<F: 2222>
+
+# Inspect:
+<F#0 n:2222>
+
+# Custom:
+<F: 33.33>
+
+# Inspect:
+<F#0 n:33.33>
+
+# Custom:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Inspect:
+<G#0 hs:<HashSet#1>, s:<ArraySet#2>, hm:<HashMap#3>, am:<ArrayMap#4>>
+
index d757542..3cb2062 100644 (file)
@@ -50,7 +50,7 @@ Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "Array"
 Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
 # Nit:
-<E: 2222>
+<F: 2222>
 
 # Json:
 {"__kind": "obj", "__id": 0, "__class": "F","n":2222}
@@ -60,7 +60,7 @@ null
 
 Deserialization Error: Doesn't know how to deserialize class "F"
 # Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Json:
 {"__kind": "obj", "__id": 0, "__class": "F","n":33.33}
diff --git a/tests/sav/nitce/test_json_deserialization_alt2.res b/tests/sav/nitce/test_json_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..6b18cdb
--- /dev/null
@@ -0,0 +1,79 @@
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{"a":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null},"b":{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"},"aa":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"new line ->\n<-","n":null,"ii":1111,"ss":"\tf\"\r\\/","d":null}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{"a":["hello",1234,123.4],"b":["hella",2345,234.5]}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{"n":2222}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# Nit:
+<F: 33.33>
+
+# Json:
+{"n":33.33}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{"hs":[-1,0],"s":["one","two"],"hm":{"one":1,"two":2},"am":{"three":"3","four":"4"}}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
index a623837..82371a6 100644 (file)
@@ -116,7 +116,7 @@ Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "Array"
 Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
 # Nit:
-<E: 2222>
+<F: 2222>
 
 # Json:
 {
@@ -129,7 +129,7 @@ null
 
 Deserialization Error: Doesn't know how to deserialize class "F"
 # Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Json:
 {
diff --git a/tests/sav/nitce/test_json_deserialization_alt4.res b/tests/sav/nitce/test_json_deserialization_alt4.res
new file mode 100644 (file)
index 0000000..1546862
--- /dev/null
@@ -0,0 +1,150 @@
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Json:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{
+       "n": 2222
+}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# Nit:
+<F: 33.33>
+
+# Json:
+{
+       "n": 33.33
+}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
diff --git a/tests/sav/nitce/test_msgpack_deserialization_alt2.res b/tests/sav/nitce/test_msgpack_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..21e30b8
--- /dev/null
@@ -0,0 +1,170 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: ; b: >
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+       "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+       "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/nitce/test_msgpack_deserialization_alt3.res b/tests/sav/nitce/test_msgpack_deserialization_alt3.res
new file mode 100644 (file)
index 0000000..8cd614f
--- /dev/null
@@ -0,0 +1,231 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "A", {
+       "b": true,
+       "c": {
+               "typ": 124,
+               "data": [97]
+       },
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "B", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "C", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "A", {
+               "b": true,
+               "c": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "B", {
+               "b": false,
+               "c": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       }],
+       "aa": {
+               "typ": 125,
+               "data": [1]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "D", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x45\x82\xA1\x61\x93\xD4\x7B\x01\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xD4\x7B\x02\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "E", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "Array", ["hello", 1234, 123.4]],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "Array", ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x46\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F", {
+       "n": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x46\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F", {
+       "n": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x47\x84\xA2\x68\x73\x93\xD4\x7B\x01\xA7\x48\x61\x73\x68\x53\x65\x74\x92\xFF\x00\xA1\x73\x93\xD4\x7B\x02\xA8\x41\x72\x72\x61\x79\x53\x65\x74\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x93\xD4\x7B\x03\xA7\x48\x61\x73\x68\x4D\x61\x70\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x93\xD4\x7B\x04\xA8\x41\x72\x72\x61\x79\x4D\x61\x70\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "G", {
+       "hs": [{
+               "typ": 123,
+               "data": [1]
+       }, "HashSet", [-1, 0]],
+       "s": [{
+               "typ": 123,
+               "data": [2]
+       }, "ArraySet", ["one", "two"]],
+       "hm": [{
+               "typ": 123,
+               "data": [3]
+       }, "HashMap", {
+               "one": 1,
+               "two": 2
+       }],
+       "am": [{
+               "typ": 123,
+               "data": [4]
+       }, "ArrayMap", {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: ; s: ; hm: ; am: >
+
diff --git a/tests/sav/nitce/test_msgpack_deserialization_alt4.res b/tests/sav/nitce/test_msgpack_deserialization_alt4.res
new file mode 100644 (file)
index 0000000..a05f017
--- /dev/null
@@ -0,0 +1,279 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x41\x86\x92\xD4\x7B\x02\xA1\x62\xC3\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x04\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x05\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x07\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "A"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": true,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [97]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 0.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 1234,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "asdf",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x42\x88\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\x92\xD4\x7B\x07\xA1\x6E\x0C\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "B"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "hjkl",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": 12,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x43\x83\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA1\x41\x86\x92\xD4\x7B\x05\xA1\x62\xC3\x92\xD4\x7B\x06\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x07\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x08\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x09\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x0A\xA1\x6E\xC0\xD4\x7D\x05\x93\xD4\x7B\x0B\x92\xD4\x7B\x0C\xA1\x42\x88\xD4\x7D\x05\xC2\xD4\x7D\x06\xD4\x7C\x62\xD4\x7D\x07\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xD4\x7D\x08\xCD\x09\x29\xD4\x7D\x09\xA4\x68\x6A\x6B\x6C\xD4\x7D\x0A\x0C\x92\xD4\x7B\x0D\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x0E\xA2\x73\x73\xA4\x71\x77\x65\x72\x92\xD4\x7B\x0F\xA2\x61\x61\xD4\x7D\x03
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "C"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "A"], {
+               "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": true,
+               "[<MsgPackExt typ: 0x7b, data: \\x06>,c]": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "[<MsgPackExt typ: 0x7b, data: \\x07>,f]": 0.123,
+               "[<MsgPackExt typ: 0x7b, data: \\x08>,i]": 1234,
+               "[<MsgPackExt typ: 0x7b, data: \\x09>,serialization_specific_name]": "asdf",
+               "[<MsgPackExt typ: 0x7b, data: \\x0A>,n]": null
+       }],
+       "<MsgPackExt typ: 0x7d, data: \\x05>": [{
+               "typ": 123,
+               "data": [11]
+       }, [{
+               "typ": 123,
+               "data": [12]
+       }, "B"], {
+               "<MsgPackExt typ: 0x7d, data: \\x05>": false,
+               "<MsgPackExt typ: 0x7d, data: \\x06>": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "<MsgPackExt typ: 0x7d, data: \\x07>": 123.123,
+               "<MsgPackExt typ: 0x7d, data: \\x08>": 2345,
+               "<MsgPackExt typ: 0x7d, data: \\x09>": "hjkl",
+               "<MsgPackExt typ: 0x7d, data: \\x0A>": 12,
+               "[<MsgPackExt typ: 0x7b, data: \\x0D>,ii]": 1111,
+               "[<MsgPackExt typ: 0x7b, data: \\x0E>,ss]": "qwer"
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0F>,aa]": {
+               "typ": 125,
+               "data": [3]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x44\x89\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\x92\xD4\x7B\x07\xA1\x6E\xC0\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\x92\xD4\x7B\x0A\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "D"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "new line ->\n<-",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "\tf\"\r\\/",
+       "[<MsgPackExt typ: 0x7b, data: \\x0A>,d]": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x45\x82\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\x92\xD4\x7B\x05\xA1\x62\x93\xD4\x7B\x06\xD4\x7D\x04\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "E"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "Array"], ["hello", 1234, 123.4]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": [{
+               "typ": 123,
+               "data": [6]
+       }, {
+               "typ": 125,
+               "data": [4]
+       }, ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x46\x81\x92\xD4\x7B\x02\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x46\x81\x92\xD4\x7B\x02\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x47\x84\x92\xD4\x7B\x02\xA2\x68\x73\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA7\x48\x61\x73\x68\x53\x65\x74\x92\xFF\x00\x92\xD4\x7B\x05\xA1\x73\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xA8\x41\x72\x72\x61\x79\x53\x65\x74\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\x92\xD4\x7B\x08\xA2\x68\x6D\x93\xD4\x7B\x09\x92\xD4\x7B\x0A\xA7\x48\x61\x73\x68\x4D\x61\x70\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\x92\xD4\x7B\x0B\xA2\x61\x6D\x93\xD4\x7B\x0C\x92\xD4\x7B\x0D\xA8\x41\x72\x72\x61\x79\x4D\x61\x70\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "G"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,hs]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "HashSet"], [-1, 0]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,s]": [{
+               "typ": 123,
+               "data": [6]
+       }, [{
+               "typ": 123,
+               "data": [7]
+       }, "ArraySet"], ["one", "two"]],
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,hm]": [{
+               "typ": 123,
+               "data": [9]
+       }, [{
+               "typ": 123,
+               "data": [10]
+       }, "HashMap"], {
+               "one": 1,
+               "two": 2
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0B>,am]": [{
+               "typ": 123,
+               "data": [12]
+       }, [{
+               "typ": 123,
+               "data": [13]
+       }, "ArrayMap"], {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: ; s: ; hm: ; am: >
+
diff --git a/tests/sav/niti/fixme/test_json_deserialization_alt2.res b/tests/sav/niti/fixme/test_json_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..49c3e35
--- /dev/null
@@ -0,0 +1,79 @@
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{"a":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null},"b":{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"},"aa":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"new line ->\n<-","n":null,"ii":1111,"ss":"\tf\"\r\\/","d":null}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{"a":["hello",1234,123.4],"b":["hella",2345,234.5]}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{"n":2222}
+
+# Back in Nit:
+<F: 2222>
+
+# Nit:
+<F: 33.33>
+
+# Json:
+{"n":33.33}
+
+# Back in Nit:
+<F: 33.33>
+
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{"hs":[-1,0],"s":["one","two"],"hm":{"one":1,"two":2},"am":{"three":"3","four":"4"}}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: ; am: >
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `Bool`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `Bool`
diff --git a/tests/sav/niti/fixme/test_json_deserialization_alt4.res b/tests/sav/niti/fixme/test_json_deserialization_alt4.res
new file mode 100644 (file)
index 0000000..d7156b6
--- /dev/null
@@ -0,0 +1,150 @@
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Json:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{
+       "n": 2222
+}
+
+# Back in Nit:
+<F: 2222>
+
+# Nit:
+<F: 33.33>
+
+# Json:
+{
+       "n": 33.33
+}
+
+# Back in Nit:
+<F: 33.33>
+
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: ; am: >
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `Bool`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `Bool`
diff --git a/tests/sav/niti/fixme/test_msgpack_deserialization_alt2.res b/tests/sav/niti/fixme/test_msgpack_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..a1c58f3
--- /dev/null
@@ -0,0 +1,170 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+       "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+       "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
index 8f3e651..6658182 100644 (file)
@@ -13,12 +13,13 @@ redef class Deserializer
        redef fun deserialize_class(name)
        do
                # Module: test_serialization
-               if name == "Array[Text]" then return new Array[Text].from_deserializer(self)
                if name == "Array[Map[String, nullable Object]]" then return new Array[Map[String, nullable Object]].from_deserializer(self)
                if name == "Array[String]" then return new Array[String].from_deserializer(self)
-               if name == "StrictHashMap[Int, Object]" then return new StrictHashMap[Int, Object].from_deserializer(self)
+               if name == "Array[Text]" then return new Array[Text].from_deserializer(self)
                if name == "Array[Error]" then return new Array[Error].from_deserializer(self)
+               if name == "StrictHashMap[Int, Object]" then return new StrictHashMap[Int, Object].from_deserializer(self)
                if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
+               if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
                if name == "Array[Int]" then return new Array[Int].from_deserializer(self)
                if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
                if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
@@ -29,7 +30,6 @@ redef class Deserializer
                if name == "Array[Float]" then return new Array[Float].from_deserializer(self)
                if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
-               if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
                if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
                if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
                if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
index caa407f..7e723de 100644 (file)
@@ -1,3 +1,3 @@
 process http://localhost:3000/api/entity/core. 1+0/1
 Error with http://localhost:3000/api/entity/core
-http://localhost:3000/api/entity/core: Unexpected Eof; is acceptable instead: value
+http://localhost:3000/api/entity/core: Empty JSON
index 4157c19..46a95ec 100644 (file)
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
 # Src:
-<E: 2222>
+<F: 2222>
 # Dst:
-<E: 2222>
+<F: 2222>
 
 # Src:
-<E: 33.33>
+<F: 33.33>
 # Dst:
-<E: 33.33>
+<F: 33.33>
 
 # Src:
 <G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
diff --git a/tests/sav/test_inspect_serialization.res b/tests/sav/test_inspect_serialization.res
new file mode 100644 (file)
index 0000000..313b090
--- /dev/null
@@ -0,0 +1,49 @@
+# Custom:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Inspect:
+<A#0 b:true, c:'a', f:0.123, i:1234, serialization_specific_name:"asdf", n:null>
+
+# Custom:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Inspect:
+<B#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"hjkl", n:…>
+
+# Custom:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Inspect:
+<C#0 a:<A#1>, b:<B#2>, aa:<A#1>>
+
+# Custom:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Inspect:
+<D#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"new line …>
+
+# Custom:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Inspect:
+<E#0 a:<Array[Object]#1>, b:<Array[nullable Serializable]#2>>
+
+# Custom:
+<F: 2222>
+
+# Inspect:
+<F[Int]#0 n:2222>
+
+# Custom:
+<F: 33.33>
+
+# Inspect:
+<F[Float]#0 n:33.33>
+
+# Custom:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Inspect:
+<G#0 hs:<HashSet[Int]#1>, s:<ArraySet[String]#2>, hm:<HashMap[String, Int]#3>,…>
+
index 91b7df1..0b26d28 100644 (file)
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
 # Nit:
-<E: 2222>
+<F: 2222>
 
 # Json:
 {"__kind": "obj", "__id": 0, "__class": "F[Int]","n":2222}
 
 # Back in Nit:
-<E: 2222>
+<F: 2222>
 
 # Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Json:
 {"__kind": "obj", "__id": 0, "__class": "F[Float]","n":33.33}
 
 # Back in Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Nit:
 <G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
index f7aaf31..896b094 100644 (file)
@@ -4,18 +4,27 @@
 # Json:
 {"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}
 
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
 # Nit:
 <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
 
 # Json:
 {"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"}
 
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
 # Nit:
 <C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
 
 # Json:
 {"a":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null},"b":{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"},"aa":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}}
 
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
 Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
@@ -24,27 +33,45 @@ Serialization warning: Cycle detected in serialized object, replacing reference
 # Json:
 {"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"new line ->\n<-","n":null,"ii":1111,"ss":"\tf\"\r\\/","d":null}
 
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
 # Nit:
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
 # Json:
 {"a":["hello",1234,123.4],"b":["hella",2345,234.5]}
 
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
 # Nit:
-<E: 2222>
+<F: 2222>
 
 # Json:
 {"n":2222}
 
+# Back in Nit:
+<F: 2222>
+
 # Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Json:
 {"n":33.33}
 
+# Back in Nit:
+<F: 33.33>
+
 # Nit:
 <G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
 
 # Json:
 {"hs":[-1,0],"s":["one","two"],"hm":{"one":1,"two":2},"am":{"three":"3","four":"4"}}
 
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
index d58b2de..ccfb302 100644 (file)
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
 # Nit:
-<E: 2222>
+<F: 2222>
 
 # Json:
 {
 }
 
 # Back in Nit:
-<E: 2222>
+<F: 2222>
 
 # Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Json:
 {
 }
 
 # Back in Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Nit:
 <G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
index 3c56592..ceaeca5 100644 (file)
@@ -11,6 +11,9 @@
        "n": null
 }
 
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
 # Nit:
 <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
 
@@ -26,6 +29,9 @@
        "ss": "qwer"
 }
 
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
 # Nit:
 <C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
 
@@ -59,6 +65,9 @@
        }
 }
 
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
 Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
@@ -77,6 +86,10 @@ Serialization warning: Cycle detected in serialized object, replacing reference
        "d": null
 }
 
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
 # Nit:
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
@@ -86,22 +99,31 @@ Serialization warning: Cycle detected in serialized object, replacing reference
        "b": ["hella", 2345, 234.5]
 }
 
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
 # Nit:
-<E: 2222>
+<F: 2222>
 
 # Json:
 {
        "n": 2222
 }
 
+# Back in Nit:
+<F: 2222>
+
 # Nit:
-<E: 33.33>
+<F: 33.33>
 
 # Json:
 {
        "n": 33.33
 }
 
+# Back in Nit:
+<F: 33.33>
+
 # Nit:
 <G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
 
@@ -119,3 +141,8 @@ Serialization warning: Cycle detected in serialized object, replacing reference
        }
 }
 
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
index 7da4254..0dac2b2 100644 (file)
Binary files a/tests/sav/test_json_static.res and b/tests/sav/test_json_static.res differ
diff --git a/tests/sav/test_msgpack_deserialization.res b/tests/sav/test_msgpack_deserialization.res
new file mode 100644 (file)
index 0000000..8ae5166
--- /dev/null
@@ -0,0 +1,104 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt1.res b/tests/sav/test_msgpack_deserialization_alt1.res
new file mode 100644 (file)
index 0000000..a454a78
--- /dev/null
@@ -0,0 +1,135 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "A", {
+       "b": true,
+       "c": {
+               "typ": 124,
+               "data": [97]
+       },
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "B", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "C", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "A", {
+               "b": true,
+               "c": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "B", {
+               "b": false,
+               "c": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       }],
+       "aa": {
+               "typ": 125,
+               "data": [1]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "D", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt2.res b/tests/sav/test_msgpack_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..2a5d0ad
--- /dev/null
@@ -0,0 +1,170 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: ; b: >
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+       "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+       "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt3.res b/tests/sav/test_msgpack_deserialization_alt3.res
new file mode 100644 (file)
index 0000000..6aa484f
--- /dev/null
@@ -0,0 +1,231 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "A", {
+       "b": true,
+       "c": {
+               "typ": 124,
+               "data": [97]
+       },
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "B", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "C", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "A", {
+               "b": true,
+               "c": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "B", {
+               "b": false,
+               "c": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       }],
+       "aa": {
+               "typ": 125,
+               "data": [1]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "D", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x45\x82\xA1\x61\x93\xD4\x7B\x01\xAD\x41\x72\x72\x61\x79\x5B\x4F\x62\x6A\x65\x63\x74\x5D\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xD4\x7B\x02\xBC\x41\x72\x72\x61\x79\x5B\x6E\x75\x6C\x6C\x61\x62\x6C\x65\x20\x53\x65\x72\x69\x61\x6C\x69\x7A\x61\x62\x6C\x65\x5D\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "E", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "Array[Object]", ["hello", 1234, 123.4]],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "Array[nullable Serializable]", ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA6\x46\x5B\x49\x6E\x74\x5D\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F[Int]", {
+       "n": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA8\x46\x5B\x46\x6C\x6F\x61\x74\x5D\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F[Float]", {
+       "n": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x47\x84\xA2\x68\x73\x93\xD4\x7B\x01\xAC\x48\x61\x73\x68\x53\x65\x74\x5B\x49\x6E\x74\x5D\x92\xFF\x00\xA1\x73\x93\xD4\x7B\x02\xB0\x41\x72\x72\x61\x79\x53\x65\x74\x5B\x53\x74\x72\x69\x6E\x67\x5D\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x93\xD4\x7B\x03\xB4\x48\x61\x73\x68\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x49\x6E\x74\x5D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x93\xD4\x7B\x04\xB8\x41\x72\x72\x61\x79\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x53\x74\x72\x69\x6E\x67\x5D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "G", {
+       "hs": [{
+               "typ": 123,
+               "data": [1]
+       }, "HashSet[Int]", [-1, 0]],
+       "s": [{
+               "typ": 123,
+               "data": [2]
+       }, "ArraySet[String]", ["one", "two"]],
+       "hm": [{
+               "typ": 123,
+               "data": [3]
+       }, "HashMap[String, Int]", {
+               "one": 1,
+               "two": 2
+       }],
+       "am": [{
+               "typ": 123,
+               "data": [4]
+       }, "ArrayMap[String, String]", {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt4.res b/tests/sav/test_msgpack_deserialization_alt4.res
new file mode 100644 (file)
index 0000000..e6affaa
--- /dev/null
@@ -0,0 +1,279 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x41\x86\x92\xD4\x7B\x02\xA1\x62\xC3\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x04\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x05\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x07\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "A"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": true,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [97]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 0.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 1234,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "asdf",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x42\x88\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\x92\xD4\x7B\x07\xA1\x6E\x0C\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "B"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "hjkl",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": 12,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x43\x83\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA1\x41\x86\x92\xD4\x7B\x05\xA1\x62\xC3\x92\xD4\x7B\x06\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x07\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x08\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x09\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x0A\xA1\x6E\xC0\xD4\x7D\x05\x93\xD4\x7B\x0B\x92\xD4\x7B\x0C\xA1\x42\x88\xD4\x7D\x05\xC2\xD4\x7D\x06\xD4\x7C\x62\xD4\x7D\x07\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xD4\x7D\x08\xCD\x09\x29\xD4\x7D\x09\xA4\x68\x6A\x6B\x6C\xD4\x7D\x0A\x0C\x92\xD4\x7B\x0D\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x0E\xA2\x73\x73\xA4\x71\x77\x65\x72\x92\xD4\x7B\x0F\xA2\x61\x61\xD4\x7D\x03
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "C"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "A"], {
+               "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": true,
+               "[<MsgPackExt typ: 0x7b, data: \\x06>,c]": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "[<MsgPackExt typ: 0x7b, data: \\x07>,f]": 0.123,
+               "[<MsgPackExt typ: 0x7b, data: \\x08>,i]": 1234,
+               "[<MsgPackExt typ: 0x7b, data: \\x09>,serialization_specific_name]": "asdf",
+               "[<MsgPackExt typ: 0x7b, data: \\x0A>,n]": null
+       }],
+       "<MsgPackExt typ: 0x7d, data: \\x05>": [{
+               "typ": 123,
+               "data": [11]
+       }, [{
+               "typ": 123,
+               "data": [12]
+       }, "B"], {
+               "<MsgPackExt typ: 0x7d, data: \\x05>": false,
+               "<MsgPackExt typ: 0x7d, data: \\x06>": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "<MsgPackExt typ: 0x7d, data: \\x07>": 123.123,
+               "<MsgPackExt typ: 0x7d, data: \\x08>": 2345,
+               "<MsgPackExt typ: 0x7d, data: \\x09>": "hjkl",
+               "<MsgPackExt typ: 0x7d, data: \\x0A>": 12,
+               "[<MsgPackExt typ: 0x7b, data: \\x0D>,ii]": 1111,
+               "[<MsgPackExt typ: 0x7b, data: \\x0E>,ss]": "qwer"
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0F>,aa]": {
+               "typ": 125,
+               "data": [3]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x44\x89\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\x92\xD4\x7B\x07\xA1\x6E\xC0\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\x92\xD4\x7B\x0A\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "D"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "new line ->\n<-",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "\tf\"\r\\/",
+       "[<MsgPackExt typ: 0x7b, data: \\x0A>,d]": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x45\x82\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xAD\x41\x72\x72\x61\x79\x5B\x4F\x62\x6A\x65\x63\x74\x5D\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\x92\xD4\x7B\x05\xA1\x62\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xBC\x41\x72\x72\x61\x79\x5B\x6E\x75\x6C\x6C\x61\x62\x6C\x65\x20\x53\x65\x72\x69\x61\x6C\x69\x7A\x61\x62\x6C\x65\x5D\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "E"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "Array[Object]"], ["hello", 1234, 123.4]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": [{
+               "typ": 123,
+               "data": [6]
+       }, [{
+               "typ": 123,
+               "data": [7]
+       }, "Array[nullable Serializable]"], ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA6\x46\x5B\x49\x6E\x74\x5D\x81\x92\xD4\x7B\x02\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F[Int]"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA8\x46\x5B\x46\x6C\x6F\x61\x74\x5D\x81\x92\xD4\x7B\x02\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F[Float]"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x47\x84\x92\xD4\x7B\x02\xA2\x68\x73\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xAC\x48\x61\x73\x68\x53\x65\x74\x5B\x49\x6E\x74\x5D\x92\xFF\x00\x92\xD4\x7B\x05\xA1\x73\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xB0\x41\x72\x72\x61\x79\x53\x65\x74\x5B\x53\x74\x72\x69\x6E\x67\x5D\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\x92\xD4\x7B\x08\xA2\x68\x6D\x93\xD4\x7B\x09\x92\xD4\x7B\x0A\xB4\x48\x61\x73\x68\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x49\x6E\x74\x5D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\x92\xD4\x7B\x0B\xA2\x61\x6D\x93\xD4\x7B\x0C\x92\xD4\x7B\x0D\xB8\x41\x72\x72\x61\x79\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x53\x74\x72\x69\x6E\x67\x5D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "G"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,hs]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "HashSet[Int]"], [-1, 0]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,s]": [{
+               "typ": 123,
+               "data": [6]
+       }, [{
+               "typ": 123,
+               "data": [7]
+       }, "ArraySet[String]"], ["one", "two"]],
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,hm]": [{
+               "typ": 123,
+               "data": [9]
+       }, [{
+               "typ": 123,
+               "data": [10]
+       }, "HashMap[String, Int]"], {
+               "one": 1,
+               "two": 2
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0B>,am]": [{
+               "typ": 123,
+               "data": [12]
+       }, [{
+               "typ": 123,
+               "data": [13]
+       }, "ArrayMap[String, String]"], {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
index 68e4850..193af89 100644 (file)
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json::string_parser
+import json::static
 import json
 
 if args.length < 1 then
index 527821b..c0f1045 100644 (file)
@@ -79,7 +79,7 @@ class F[N: Numeric]
 
        var n: N
 
-       redef fun to_s do return "<E: {n}>"
+       redef fun to_s do return "<F: {n}>"
 end
 
 # Other collections
diff --git a/tests/test_inspect_serialization.nit b/tests/test_inspect_serialization.nit
new file mode 100644 (file)
index 0000000..42fd089
--- /dev/null
@@ -0,0 +1,23 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import test_deserialization
+
+var entities = new TestEntities
+var tests = entities.with_generics
+
+for o in tests do
+       print "# Custom:\n{o}\n"
+       print "# Inspect:\n{o.inspect}\n"
+end
index 6a4dfdb..62fa5eb 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import test_deserialization
 import json
-#alt1# import test_deserialization_serial
-#alt3# import test_deserialization_serial
+
+import test_deserialization
+import test_deserialization_serial
 
 var entities = new TestEntities
 
@@ -34,11 +34,13 @@ for o in tests do
        #alt4#serializer.pretty_json = true
        serializer.serialize(o)
 
-       var deserializer = new JsonDeserializer(stream.to_s)#alt2##alt4#
-       var deserialized = deserializer.deserialize#alt2##alt4#
+       var type_name: nullable String = o.class_name
+       type_name = null #alt2##alt4#
+       var deserializer = new JsonDeserializer(stream.to_s)
+       var deserialized = deserializer.deserialize(type_name)
 
        print "# Nit:\n{o}\n"
        print "# Json:\n{stream}\n"
-       print "# Back in Nit:\n{deserialized or else "null"}\n"#alt2##alt4#
-       if deserializer.errors.not_empty then print deserializer.errors.join("\n")#alt2##alt4#
+       print "# Back in Nit:\n{deserialized or else "null"}\n"
+       if deserializer.errors.not_empty then print deserializer.errors.join("\n")
 end
diff --git a/tests/test_msgpack_deserialization.nit b/tests/test_msgpack_deserialization.nit
new file mode 100644 (file)
index 0000000..d64bd44
--- /dev/null
@@ -0,0 +1,74 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# alt0 non-generics plain
+# alt1 non-generics metadata
+# alt2     generics plain
+# alt3     generics metadata
+# alt4     generics metadata cache_metadata_strings
+
+import test_deserialization
+import msgpack
+import msgpack::read
+import json
+#alt2# import test_deserialization_serial
+#alt3# import test_deserialization_serial
+#alt4# import test_deserialization_serial
+
+var entities = new TestEntities
+
+var tests = entities.without_generics
+#alt2#tests = entities.with_generics
+#alt3#tests = entities.with_generics
+#alt4#tests = entities.with_generics
+
+for o in tests do
+       var plain = true
+       #alt1#plain = false
+       #alt3#plain = false
+       #alt4#plain = false
+
+       var writer = new BytesWriter
+       var serializer = new MsgPackSerializer(writer)
+       serializer.plain_msgpack = plain
+       #alt4#serializer.cache_metadata_strings = true
+       serializer.serialize o
+       var bytes = writer.bytes
+
+       # Nit source
+       print "# 1. Nit source:\n{o}\n"
+
+       # Generated MessagePack
+       print "# 2. MsgPack:\n{bytes.chexdigest}\n"
+
+       # Python deserialization line to ensure compliance and otherwise debug
+       #print "msgpack.unpackb(b'{bytes.chexdigest}')"
+
+       # Show readable structure of the generated MessagePack as JSON
+       var msg = (new BytesReader(bytes)).read_msgpack
+       var json = if msg != null then
+                       msg.serialize_to_json(plain=true, pretty=true)
+               else "null"
+       print "# 3. JSON:\n{json}\n"
+
+       # Deserialize
+       var deserializer = new MsgPackDeserializer(new BytesReader(bytes))
+       if serializer.plain_msgpack then
+               var deserialized = deserializer.deserialize(o.class_name)
+               print "# 4. Back in Nit (no metadata):\n{deserialized or else "null"}\n"
+       else
+               var deserialized = deserializer.deserialize
+               print "# 4. Back in Nit (with metadata):\n{deserialized or else "null"}\n"
+       end
+end