Merge: Nitweb: boost response time and some improvements
authorJean Privat <jean@pryen.org>
Thu, 7 Sep 2017 14:48:58 +0000 (10:48 -0400)
committerJean Privat <jean@pryen.org>
Thu, 7 Sep 2017 14:48:58 +0000 (10:48 -0400)
The main objective of this PR is to shorten the frontend response time.
The major culprit was that the full documentation was serialized in each response.
This PR makes nitweb return only the synopsis and lets the frontend ask for the full documentation if needed.

Also uniformizes and cleans tabs for entities:
* Moved data between tabs (such as inheritance data to the inheritance tab)
* Added missing tabs for packages and groups (http://nitweb.moz-code.org/doc/core%3Ecollection%3E/defs)

Demo: http://nitweb.moz-code.org/

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

59 files changed:
.gitattributes
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
lib/dom/parser.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/shadow.nit [new file with mode: 0644]
lib/gamnit/display.nit
lib/gamnit/display_linux.nit
lib/gamnit/dynamic_resolution.nit
lib/gamnit/gamnit.nit
lib/gamnit/programs.nit
lib/glesv2/glesv2.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/markdown/markdown.nit
lib/mongodb/mongodb.nit
lib/sdl2/mixer.nit
lib/serialization/engine_tools.nit
lib/serialization/safe.nit [new file with mode: 0644]
lib/serialization/serialization.nit
src/frontend/serialization_code_gen_phase.nit
src/loader.nit
src/model/model_json.nit
src/model/test_model_json.nit
src/neo.nit
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/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/nitserial_args1.res
tests/sav/nitwebcrawl.res
tests/sav/test_binary_deserialization_alt1.res
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/test_adhoc_json_parse.nit
tests/test_deserialization.nit
tests/test_json_deserialization.nit

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 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 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 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..56e0575 100644 (file)
@@ -165,7 +165,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 +203,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..7637e02
--- /dev/null
@@ -0,0 +1,75 @@
+# 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
+
+# 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
+
+       redef fun mvp_matrix
+       do
+               # TODO cache
+
+               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
+end
index 3b320f9..d153954 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 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 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
 
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 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
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 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 b2d9418..7b02c8d 100644 (file)
@@ -755,7 +755,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 +1206,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 +1309,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; `}
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 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]
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 4742ad4..d43da6d 100644 (file)
@@ -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,48 @@ 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 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/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..3feb4ee 100644 (file)
@@ -74,11 +74,12 @@ interface Serializer
        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 possie, i.e. it is `Serializable` or `null`
+       # 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
@@ -140,7 +141,7 @@ abstract class Deserializer
        # 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
+       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
@@ -150,7 +151,7 @@ abstract class Deserializer
        # 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
+       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
@@ -246,19 +247,6 @@ interface Serializable
        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
@@ -269,6 +257,7 @@ 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
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 1502c40..615aba9 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 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 105c493..897dfd5 100644 (file)
@@ -94,7 +94,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 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/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`
index 8f3e651..f8172b0 100644 (file)
@@ -13,11 +13,11 @@ 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 == "Array[Int]" then return new Array[Int].from_deserializer(self)
                if name == "Array[nullable Object]" then return new Array[nullable Object].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>
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
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
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