Merge: listfull.sh: Quote paths
authorJean Privat <jean@pryen.org>
Wed, 7 Jun 2017 20:11:10 +0000 (16:11 -0400)
committerJean Privat <jean@pryen.org>
Wed, 7 Jun 2017 20:11:10 +0000 (16:11 -0400)
Make the list unambiguous and easy to consume by POSIX shell scripts.

Signed-off-by: Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>

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

55 files changed:
contrib/action_nitro/src/action_nitro.nit
contrib/model_viewer/src/globe.nit
contrib/model_viewer/src/model_viewer.nit
contrib/shibuqam/examples/shibuqamoauth.nit
contrib/tinks/assets/models/debris1.mtl
contrib/tinks/src/client/base.nit
contrib/tinks/src/client/client3d.nit
contrib/tinks/src/common.nit
contrib/tinks/src/server/server.nit
lib/actors/actors.nit
lib/actors/examples/agent_simulation/agent_simulation.nit [new file with mode: 0644]
lib/actors/examples/agent_simulation/simple_simulation.nit [new file with mode: 0644]
lib/actors/examples/chameneos-redux/chameneosredux.nit
lib/core/text/string_search.nit
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/more_meshes.nit
lib/gamnit/depth/more_models.nit
lib/gamnit/depth/selection.nit
lib/gamnit/display_linux.nit
lib/gamnit/examples/template/src/template.nit
lib/gamnit/model_parsers/obj.nit
lib/gamnit/network/client.nit
lib/gamnit/network/common.nit
lib/gamnit/network/network.nit
lib/gamnit/network/server.nit
lib/geometry/angles.nit
lib/gmp/native_gmp.nit [new file with mode: 0644]
lib/gmp/test_native_gmp.nit [new file with mode: 0644]
lib/matrix/projection.nit
lib/popcorn/examples/angular/example_angular.nit
lib/popcorn/pop_auth.nit
lib/popcorn/pop_handlers.nit
lib/popcorn/pop_json.nit [new file with mode: 0644]
lib/popcorn/pop_repos.nit
lib/popcorn/pop_templates.nit
lib/popcorn/pop_tracker.nit
lib/popcorn/pop_validation.nit
lib/pthreads/concurrent_collections.nit
src/frontend/actors_generation_phase.nit
src/metrics/detect_covariance.nit
src/metrics/static_types_metrics.nit
src/model/model.nit
src/modelbuilder_base.nit
src/modelize/modelize_class.nit
src/modelize/modelize_property.nit
src/nitni/nitni_callbacks.nit
src/parser/parser_nodes.nit
src/semantize/typing.nit
src/web/web_base.nit
tests/sav/base_gen_final_bound.res [deleted file]
tests/sav/error_class_generic_alt2.res
tests/sav/test_gen_inh.res
tests/sav/test_super_gen.res
tests/sav/test_super_gen_raf.res

index e5a68f9..b8dd4f4 100644 (file)
@@ -140,6 +140,7 @@ redef class App
 
                # Load 3d models
                iss_model.load
+               if iss_model.errors.not_empty then print_error iss_model.errors.join("\n")
 
                # Setup cameras
                world_camera.reset_height 60.0
@@ -507,7 +508,7 @@ end
 redef class Boss
        redef var actor is lazy do
                var actor = new Actor(app.iss_model, center)
-               actor.rotation = pi/2.0
+               actor.yaw = pi/2.0
                return actor
        end
 
@@ -556,7 +557,7 @@ redef class Player
                        var splatter = new Actor(app.splatter_model,
                                new Point3d[Float](center.x, 0.05 & 0.04, center.y))
                        splatter.scale = 32.0
-                       splatter.rotation = 2.0 * pi.rand
+                       splatter.yaw = 2.0*pi.rand
                        app.actors.add splatter
                end
 
index 8fe1158..a5a9a62 100644 (file)
@@ -148,7 +148,7 @@ class GlobeMaterial
 
                # Set uniforms
                program.scale.uniform 1.0
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
                program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0)
                program.color.uniform(color[0], color[1], color[2], color[3])
                program.is_surface.uniform is_surface
index 9353ef5..b93bea4 100644 (file)
@@ -67,8 +67,10 @@ redef class App
                world_camera.near = 0.1
                world_camera.far = 100.0
 
-               for model in models do model.load
-               for texture in asset_textures_by_name.values do texture.load
+               for model in models do
+                       model.load
+                       if model.errors.not_empty then print_error model.errors.join("\n")
+               end
 
                # Display the first model
                model = models[model_index]
@@ -146,7 +148,7 @@ redef class App
                var t = clock.total.to_f
 
                # Rotate the model
-               actors.first.rotation = t
+               actors.first.yaw = t
 
                # Move the light source
                var dist_to_light = 20.0
index 1aa33f9..326f0bf 100644 (file)
 module shibuqamoauth
 
 import popcorn
+import popcorn::pop_json
 import shibuqam
 import json
 
index 174d51f..d8e32fc 100644 (file)
@@ -9,7 +9,7 @@ Ks 0.287480 0.287480 0.287480
 Ni 1.000000
 d 1.000000
 illum 2
-map_Kd textures/tread.jpg
+map_Kd textures/TTread.jpg
 
 newmtl Treads
 Ns 178.431373
index bca5ae3..29f40c3 100644 (file)
@@ -35,22 +35,10 @@ redef class App
                else
                        print "Looking for a server..."
 
-                       var s = new UDPSocket
-                       s.enable_broadcast = true
-                       s.blocking = false
-                       s.broadcast(discovery_port, "Server? {handshake_app_name}")
-                       nanosleep(0, 100_000_000)
-
-                       var ptr = new Ref[nullable SocketAddress](null)
-                       var resp = s.recv_from(1024, ptr)
-                       var src = ptr.item
-
-                       if not resp.is_empty then
-                               var words = resp.split(" ")
-                               if words.length == 3 and words[0] == "Server!" and words[1] == handshake_app_name and words[2].is_numeric then
-                                       address = src.address
-                                       port = words[2].to_i
-                               end
+                       var servers = discover_local_servers
+                       if servers.not_empty then
+                               address = servers.first.address
+                               port = servers.first.port
                        end
                end
 
@@ -60,7 +48,7 @@ redef class App
                        # No command line
                        return new LocalServerContext
                else
-                       print "Connecting to:{address}:{port}"
+                       print "Connecting to {address}:{port}"
 
                        # Args are: tinks server_address {port}
                        if args.length > 1 then port = args[1].to_i
index 5c3d6b9..b37fc69 100644 (file)
@@ -122,8 +122,15 @@ redef class App
                show_splash_screen logo
 
                # Load everything
-               for model in models do model.load
-               for texture in all_root_textures do texture.load
+               for texture in all_root_textures do
+                       texture.load
+                       var error = texture.error
+                       if error != null then print_error error
+               end
+               for model in models do
+                       model.load
+                       if model.errors.not_empty then print_error model.errors.join("\n")
+               end
 
                # Modify all textures so they have a higher ambient color
                for model in models do
@@ -359,7 +366,7 @@ redef class Feature
                # Apply a random model and rotation to new features
                actor = new Actor(rule.models.rand,
                        new Point3d[Float](pos.x, 0.0, pos.y))
-               actor.rotation = 2.0*pi.rand
+               actor.yaw = 2.0*pi.rand
                actor.scale = 0.75
 
                self.actor = actor
@@ -426,7 +433,7 @@ redef class ExplosionEvent
                # Blast mark on the ground
                var blast = new Actor(app.blast_model, new Point3d[Float](pos.x, 0.05 & 0.04, pos.y))
                blast.scale = 3.0
-               blast.rotation = 2.0*pi.rand
+               blast.yaw = 2.0*pi.rand
                app.actors.add blast
 
                # Smoke
@@ -474,8 +481,8 @@ redef class TankMoveEvent
                        actor.center.z = pos.y
                end
 
-               tank.actors[0].rotation = tank.heading + pi
-               tank.actors[1].rotation = tank.turret.heading + pi
+               tank.actors[0].yaw = -tank.heading + pi
+               tank.actors[1].yaw = -tank.turret.heading + pi
 
                # Keep going only for the local tank
                var local_player = app.context.local_player
index b215d09..e7a82c9 100644 (file)
@@ -26,6 +26,5 @@ redef class Sys
        # Default listening port of the server
        fun default_listening_port: Int do return 18721
 
-       # Port to which clients send discovery requests
-       fun discovery_port: Int do return 18722
+       redef fun discovery_port do return 18722
 end
index 58555ae..69f2bdb 100644 (file)
@@ -26,13 +26,6 @@ redef class RemoteClient
 end
 
 redef class Server
-       # `UDPSocket` to which clients send discovery requests
-       var discovery_socket: UDPSocket do
-               var s = new UDPSocket
-               s.blocking = false
-               s.bind(null, discovery_port)
-               return s
-       end
 
        # The current game
        var game = new TGame is lazy, writable
@@ -62,24 +55,8 @@ redef class Server
                # Do game logic
                var turn = game.do_turn
 
-               # Respond to discovery requests
-               loop
-                       var ptr = new Ref[nullable SocketAddress](null)
-                       var read = discovery_socket.recv_from(1024, ptr)
-
-                       # No sender means there is no request (an error would also do it)
-                       var sender = ptr.item
-                       if sender == null then break
-
-                       var words = read.split(" ")
-                       if words.length != 2 or words[0] != "Server?" or words[1] != handshake_app_name then
-                               print "Server Warning: Rejected discovery request '{read}'"
-                               continue
-                       end
-
-                       discovery_socket.send_to(sender.address, sender.port,
-                               "Server! {handshake_app_name} {self.port}")
-               end
+               # Respond to discovery requests sent over UDP
+               answer_discovery_requests
 
                # Setup clients
                var new_clients = accept_clients
@@ -91,8 +68,6 @@ redef class Server
                        client.writer.serialize game
                        client.writer.serialize client.player
                        client.socket.flush
-
-                       clients.add client
                end
 
                if dedicated and clients.is_empty then
index 41ea816..d5e350f 100644 (file)
@@ -17,7 +17,7 @@ module actors is
        new_annotation actor
 end
 
-import pthreads::concurrent_collections
+intrude import pthreads::concurrent_collections
 intrude import pthreads
 intrude import pthreads::extra
 
@@ -34,24 +34,20 @@ abstract class Actor
        var instance: V
 
        # Mailbox used to receive and process messages
-       var mailbox = new BlockingQueue[Message].with_actor(self)
+       var mailbox = new Mailbox[Message].with_actor(self)
 
        # Is `self` working ?
        # i.e. does it have messages to process or is it processing one now ?
-       var working = false
+       var working = true
 
        redef fun main do
                loop
                        var m = mailbox.shift
                        if m isa ShutDownMessage then
-                               sys.active_actors.remove(self)
+                               sys.active_actors.decrement
                                return null
                        end
                        m.invoke(instance)
-                       if mailbox.is_empty then
-                               working = false
-                               sys.active_actors.remove(self)
-                       end
                end
        end
 
@@ -63,6 +59,64 @@ abstract class Actor
        end
 end
 
+# A Blocking queue implemented from a `ConcurrentList`
+# `shift` is blocking if there isn't any element in `self`
+# `push` or `unshift` releases every blocking threads
+# Corresponds to the mailbox of an actor
+class Mailbox[E]
+       super BlockingQueue[E]
+
+       # The associated actor
+       var actor: Actor is noautoinit
+
+       # init self with an associated actor
+       init with_actor(actor: Actor) do
+               self.actor = actor
+               sys.active_actors.increment
+       end
+
+       # Adding the signal to release eventual waiting thread(s)
+       redef fun push(e) do
+               mutex.lock
+               if real_collection.is_empty and not actor.working then
+                       actor.working = true
+                       sys.active_actors.increment
+                       real_collection.push(e)
+                       self.cond.signal
+               else
+                       real_collection.push(e)
+               end
+               mutex.unlock
+       end
+
+       redef fun unshift(e) do
+               mutex.lock
+               real_collection.unshift(e)
+               self.cond.signal
+               mutex.unlock
+       end
+
+       # If empty, blocks until an item is inserted with `push` or `unshift`
+       redef fun shift do
+               mutex.lock
+               if real_collection.is_empty then
+                       actor.working = false
+                       sys.active_actors.decrement
+                       while real_collection.is_empty do self.cond.wait(mutex)
+               end
+               var r = real_collection.shift
+               mutex.unlock
+               return r
+       end
+
+       redef fun is_empty do
+               mutex.lock
+               var r = real_collection.is_empty
+               mutex.unlock
+               return r
+       end
+end
+
 # A Message received by a Mailbox
 # In fact, this is the reification of a call
 # Each Message class represent a call to make on `instance` via `invoke`
@@ -151,91 +205,49 @@ class Future[E]
        end
 end
 
-# A Blocking queue implemented from a `ConcurrentList`
-# `shift` is blocking if there isn't any element in `self`
-# `push` or `unshift` releases every blocking threads
-# Corresponds to the mailbox of an actor
-class BlockingQueue[E]
-       super ConcurrentList[E]
-
-       # The associated actor
-       var actor: Actor is noautoinit
-
-       # Used to block or signal on waiting threads
-       private var cond = new PthreadCond
-
-       # init self with an associated actor
-       init with_actor(actor: Actor) do  self.actor = actor
-
-       # Adding the signal to release eventual waiting thread(s)
-       redef fun push(e) do
-               mutex.lock
-               if real_collection.is_empty and not actor.working then
-                       actor.working = true
-                       sys.active_actors.push(actor)
-               end
-               real_collection.push(e)
-               self.cond.signal
-               mutex.unlock
-       end
-
-       redef fun unshift(e) do
-               mutex.lock
-               real_collection.unshift(e)
-               self.cond.signal
-               mutex.unlock
-       end
+# A counter on which threads can wait until its value is 0
+class SynchronizedCounter
 
-       # If empty, blocks until an item is inserted with `push` or `unshift`
-       redef fun shift do
-               mutex.lock
-               while real_collection.is_empty do self.cond.wait(mutex)
-               var r = real_collection.shift
-               mutex.unlock
-               return r
-       end
-end
+       # The starting value, always starts with 0
+       private var c = 0
 
-# A collection which `is_empty` method blocks until it's empty
-class ReverseBlockingQueue[E]
-       super ConcurrentList[E]
-
-       # Used to block or signal on waiting threads
        private var cond = new PthreadCond
+       private var mutex = new Mutex
 
-       # Adding the signal to release eventual waiting thread(s)
-       redef fun push(e) do
+       # Increment the counter atomically
+       fun increment do
                mutex.lock
-               real_collection.push(e)
+               c += 1
                mutex.unlock
        end
 
-       # When the Queue is empty, signal any
-       # possible waiting thread
-       redef fun remove(e) do
+       # Decrement the counter atomically,
+       # signals to waiting thread(s) if `c == 0`
+       fun decrement do
                mutex.lock
-               real_collection.remove(e)
-               if real_collection.is_empty then cond.signal
+               c -= 1
+               if c == 0 then
+                       cond.signal
+               end
                mutex.unlock
        end
 
-       # Wait until the Queue is empty
-       redef fun is_empty do
+       # Block until `c == 0`
+       fun wait do
                mutex.lock
-               while not real_collection.is_empty do self.cond.wait(mutex)
+               while c != 0 do cond.wait(mutex)
                mutex.unlock
-               return true
        end
 end
 
 redef class Sys
 
-       # List of running actors
-       var active_actors = new ReverseBlockingQueue[Actor] is lazy
+       # Number of active actors
+       var active_actors = new SynchronizedCounter
 
        redef fun run do
                super
                # The program won't end until every actor is done
-               active_actors.is_empty
+               active_actors.wait
        end
 end
diff --git a/lib/actors/examples/agent_simulation/agent_simulation.nit b/lib/actors/examples/agent_simulation/agent_simulation.nit
new file mode 100644 (file)
index 0000000..35fe57c
--- /dev/null
@@ -0,0 +1,60 @@
+# 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.
+
+# a "Framework" to make Multi-Agent Simulations in Nit
+module agent_simulation is no_warning("missing-doc")
+
+import actors
+
+# Master of the simulation, it initiates the steps and waits for them to terminate
+class ClockAgent
+       actor
+
+       # Number of steps to do in the simulation
+       var nb_steps: Int
+
+       # List of Agents in the simulation
+       var agents: Array[Agent]
+
+       # Number of agents that finished their step
+       var nb_finished = 0
+
+       fun do_step do
+               for a in agents do a.async.do_step
+               nb_steps -= 1
+       end
+
+       fun finished_step do
+               nb_finished += 1
+               if nb_finished == agents.length then
+                       nb_finished = 0
+                       if nb_steps != 0 then async.do_step
+               end
+       end
+end
+
+class Agent
+       actor
+
+       # Doing a step in the simulation
+       fun do_step do
+       end
+
+       fun end_step do clock_agent.async.finished_step
+
+end
+
+redef class Sys
+       var clock_agent: ClockAgent is noautoinit,writable
+end
diff --git a/lib/actors/examples/agent_simulation/simple_simulation.nit b/lib/actors/examples/agent_simulation/simple_simulation.nit
new file mode 100644 (file)
index 0000000..d8be6c6
--- /dev/null
@@ -0,0 +1,57 @@
+# 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.
+
+# Using `agent_simulation` by refining the Agent class to make
+# a multi-agent simulation where every agent know each other
+# The steps consist of each agent greeting each other, and
+# waiting for every other agent to respond before notifying
+# to the `ClockAgent` that they finished their step.
+module simple_simulation
+
+import agent_simulation
+
+redef class Agent
+       var others = new Array[Agent]
+       var count = 0
+
+       fun greet(message: String, other: Agent) do other.async.greet_back("Hello back !")
+
+       fun greet_back(message: String) do
+               count -= 1
+               if count == 0 then end_step
+       end
+
+       redef fun do_step do
+               for o in others do
+                       o.async.greet("Hello !", self)
+                       count += 1
+               end
+       end
+end
+
+var nb_steps = 0
+var nb_agents = 0
+if args.is_empty or args.length != 2 then
+       nb_steps = 10
+       nb_agents = 10
+else
+       nb_steps = args[0].to_i
+       nb_agents = args[1].to_i
+end
+
+var agents = new Array[Agent]
+for i in [0..nb_agents[ do agents.add(new Agent)
+for a in agents do for b in agents do if a != b then a.others.add(b)
+clock_agent = new ClockAgent(nb_steps, agents)
+clock_agent.async.do_step
index 9143e22..60361a8 100644 (file)
@@ -128,7 +128,7 @@ fun work(n, nb_colors : Int ) do
 
        for c in creatures do c.async.run
 
-       active_actors.is_empty
+       active_actors.wait
 
        var total = 0
        for c in creatures do
index 6d6302e..7648816 100644 (file)
@@ -482,6 +482,15 @@ redef class Text
                return self.split_with(pattern).join(string)
        end
 
+       # Replace the first occurrence of `pattern` with `string`
+       #
+       #     assert "hlelo".replace_first("le", "el") == "hello"
+       #     assert "hello".replace_first('l', "")    == "helo"
+       fun replace_first(pattern: Pattern, string: Text): String
+       do
+               return self.split_once_on(pattern).join(string)
+       end
+
        # Does `self` contains at least one instance of `pattern`?
        #
        #     assert "hello".has('l')
index 994e8be..890395a 100644 (file)
@@ -46,8 +46,30 @@ class Actor
        # Position of this sprite in world coordinates
        var center: Point3d[Float] is writable
 
-       # Rotation on the Z axis
-       var rotation = 0.0 is writable
+       # Rotation around the X axis (+ looks up, - looks down)
+       #
+       # Positive values look up, and negative look down.
+       #
+       # All actor rotations follow the right hand rule.
+       # The default orientation of the model should look towards -Z.
+       var pitch = 0.0 is writable
+
+       # Rotation around the Y axis (+ turns left, - turns right)
+       #
+       # Positive values turn `self` to the left, and negative values to the right.
+       #
+       # All actor rotations follow the right hand rule.
+       # The default orientation of the model should look towards -Z.
+       var yaw = 0.0 is writable
+
+       # Rotation around the Z axis (looking to -Z: + turns counterclockwise, - clockwise)
+       #
+       # From the default camera point of view, looking down on the Z axis,
+       # positive values turn `self` counterclockwise, and negative values clockwise.
+       #
+       # All actor rotations follow the right hand rule.
+       # The default orientation of the model should look towards -Z.
+       var roll = 0.0 is writable
 
        # Scale applied to the model
        var scale = 1.0 is writable
@@ -83,6 +105,9 @@ abstract class Model
        # Load this model in memory
        fun load do end
 
+       # Errors raised at loading
+       var errors = new Array[Error]
+
        # All `LeafModel` composing this model
        #
        # Usually, there is one `LeafModel` per material.
index 4cc8dcb..746a952 100644 (file)
@@ -49,7 +49,7 @@ class SmoothMaterial
                # Actor specs
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                # From mesh
                program.coord.array_enabled = true
@@ -97,6 +97,9 @@ class TexturedMaterial
        # Texture applied to the specular color
        var specular_texture: nullable Texture = null is writable
 
+       # Bump map TODO
+       private var normals_texture: nullable Texture = null is writable
+
        redef fun draw(actor, model)
        do
                var mesh = model.mesh
@@ -140,6 +143,17 @@ class TexturedMaterial
                        program.use_map_specular.uniform false
                end
 
+               texture = normals_texture
+               if texture != null then
+                       glActiveTexture gl_TEXTURE3
+                       glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
+                       program.use_map_bump.uniform true
+                       program.map_bump.uniform 3
+                       sample_used_texture = texture
+               else
+                       program.use_map_bump.uniform false
+               end
+
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
 
@@ -171,7 +185,8 @@ class TexturedMaterial
 
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+
+               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                program.ambient_color.uniform(ambient_color[0], ambient_color[1], ambient_color[2], ambient_color[3]*actor.alpha)
                program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3]*actor.alpha)
@@ -216,7 +231,8 @@ class NormalsMaterial
 
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+
+               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
@@ -229,8 +245,8 @@ class NormalsMaterial
        end
 end
 
-# Graphic program to display 3D models with Lamber diffuse lighting
-class LambertProgram
+# Graphic program to display 3D models with Blinn-Phong specular lighting
+class BlinnPhongProgram
        super GamnitProgramFromSource
 
        redef var vertex_shader_source = """
@@ -263,20 +279,19 @@ class LambertProgram
                // Output for the fragment shader
                varying vec2 v_tex_coord;
                varying vec3 v_normal;
-               varying vec4 v_light_center;
-               varying vec4 v_camera;
+               varying vec4 v_to_light;
+               varying vec4 v_to_camera;
 
                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);
-                       v_normal = normalize(vec4(normal, 0.0) * rotation * mvp).xyz;
-
-                       gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
-
-                       // TODO compute v_light_center and v_camera on the CPU side and pass as uniforms
-                       v_light_center = vec4(light_center, 0.0) * mvp;
-                       v_camera = vec4(camera, 0.0) * mvp;
+                       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);
                }
                """ @ glsl_vertex_shader
 
@@ -286,8 +301,8 @@ class LambertProgram
                // Input from the vertex shader
                varying vec2 v_tex_coord;
                varying vec3 v_normal;
-               varying vec4 v_light_center;
-               varying vec4 v_camera;
+               varying vec4 v_to_light;
+               varying vec4 v_to_camera;
 
                // Colors
                uniform vec4 ambient_color;
@@ -316,21 +331,39 @@ class LambertProgram
 
                void main()
                {
-                       // Lambert diffusion
-                       vec3 light_dir = normalize(v_light_center.xyz);
-                       float lambert = max(dot(light_dir, v_normal), 0.0);
-
-                       if (use_map_ambient)
-                               gl_FragColor = ambient_color * texture2D(map_ambient, v_tex_coord);
-                       else
-                               gl_FragColor = ambient_color;
-
-                       if (use_map_diffuse)
-                               gl_FragColor += lambert * diffuse_color * texture2D(map_diffuse, v_tex_coord);
-                       else
-                               gl_FragColor += lambert * diffuse_color;
-
+                       // Normal
+                       vec3 normal = v_normal;
+                       if (use_map_bump) {
+                               // TODO
+                               vec3 bump = 2.0 * texture2D(map_bump, v_tex_coord).rgb - 1.0;
+                       }
+
+                       // Ambient light
+                       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);
+
+                       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) {
+                               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
+                       }
+
+                       vec4 specular = s * specular_color;
+                       if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
+
+                       gl_FragColor = ambient + diffuse + specular;
                        if (gl_FragColor.a < 0.01) discard;
+
+                       //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug
                }
                """ @ glsl_fragment_shader
 
@@ -355,6 +388,12 @@ class LambertProgram
        # Specularity texture unit
        var map_specular = uniforms["map_specular"].as(UniformSampler2D) is lazy
 
+       # Should this program use the texture `map_bump`?
+       var use_map_bump = uniforms["use_map_bump"].as(UniformBool) is lazy
+
+       # Bump texture unit
+       var map_bump = uniforms["map_bump"].as(UniformSampler2D) is lazy
+
        # Normal per vertex
        var normal = attributes["normal"].as(AttributeVec3) is lazy
 
@@ -390,8 +429,10 @@ class LambertProgram
 end
 
 # Program to color objects from their normal vectors
+#
+# May be used in place of `BlinnPhongProgram` for debugging or effect.
 class NormalProgram
-       super LambertProgram
+       super BlinnPhongProgram
 
        redef var fragment_shader_source = """
                precision mediump float;
@@ -407,7 +448,7 @@ class NormalProgram
 end
 
 redef class App
-       private var versatile_program = new LambertProgram is lazy
+       private var versatile_program = new BlinnPhongProgram is lazy
 
        private var normals_program = new NormalProgram is lazy
 end
index 0a4d53c..e316b89 100644 (file)
@@ -79,22 +79,31 @@ class Plane
        # TODO use gl_TRIANGLE_FAN instead
 end
 
-# Cube, with 6 faces
+# Cuboid, or rectangular prism, with 6 faces and right angles
 #
-# Occupies `[-0.5..0.5]` on all three axes.
-class Cube
+# Can be created from a `Boxed3d` using `to_mesh`.
+class Cuboid
        super Mesh
 
+       # Width, on the X axis
+       var width: Float
+
+       # Height, on the Y axis
+       var height: Float
+
+       # Depth, on the Z axis
+       var depth: Float
+
        redef var vertices is lazy do
-               var a = [-0.5, -0.5, -0.5]
-               var b = [ 0.5, -0.5, -0.5]
-               var c = [-0.5,  0.5, -0.5]
-               var d = [ 0.5,  0.5, -0.5]
+               var a = [-0.5*width, -0.5*height, -0.5*depth]
+               var b = [ 0.5*width, -0.5*height, -0.5*depth]
+               var c = [-0.5*width,  0.5*height, -0.5*depth]
+               var d = [ 0.5*width,  0.5*height, -0.5*depth]
 
-               var e = [-0.5, -0.5,  0.5]
-               var f = [ 0.5, -0.5,  0.5]
-               var g = [-0.5,  0.5,  0.5]
-               var h = [ 0.5,  0.5,  0.5]
+               var e = [-0.5*width, -0.5*height,  0.5*depth]
+               var f = [ 0.5*width, -0.5*height,  0.5*depth]
+               var g = [-0.5*width,  0.5*height,  0.5*depth]
+               var h = [ 0.5*width,  0.5*height,  0.5*depth]
 
                var vertices = new Array[Float]
                for v in [a, c, d, a, d, b, # front
@@ -135,6 +144,35 @@ class Cube
        redef var center = new Point3d[Float](0.0, 0.0, 0.0) is lazy
 end
 
+# Cube, with 6 faces, edges of equal length and square angles
+#
+# Occupies `[-0.5..0.5]` on all three axes.
+class Cube
+       super Cuboid
+
+       noautoinit
+
+       init
+       do
+               width = 1.0
+               height = 1.0
+               depth = 1.0
+       end
+end
+
+redef class Boxed3d[N]
+       # Create a `Cuboid` mesh with the dimension of `self`
+       #
+       # Does not use the position of `self`, but it can be given to an `Actor`.
+       fun to_mesh: Cuboid
+       do
+               var width = right.to_f-left.to_f
+               var height = top.to_f-bottom.to_f
+               var depth = front.to_f-back.to_f
+               return new Cuboid(width, height, depth)
+       end
+end
+
 # Sphere with `radius` and a number of faces set by `n_meridians` and `n_parallels`
 class UVSphere
        super Mesh
index 8d7d8a1..eb148c9 100644 (file)
@@ -39,20 +39,24 @@ class ModelAsset
 
        init do models.add self
 
+       private var loaded = false
+
        redef fun load
        do
                var ext = path.file_extension
                if ext == "obj" then
                        load_obj_file
                else
-                       print_error "Model failed to load: Extension '{ext or else "null"}' unrecognized"
+                       errors.add new Error("Model at '{path}' failed to load: Extension '{ext or else "null"}' unrecognized")
                end
 
-               if leaves.is_empty then
+               if leaves_cache.is_empty then
                        # Nothing was loaded, use a cube with the default material
                        var leaf = placeholder_model
-                       leaves.add leaf
+                       leaves_cache.add leaf
                end
+
+               loaded = true
        end
 
        private fun load_obj_file
@@ -61,8 +65,8 @@ class ModelAsset
                var text_asset = new TextAsset(path)
                var content = text_asset.to_s
                if content.is_empty then
-                       print_error "Model failed to load: Asset empty at '{self.path}'"
-                       leaves.add new LeafModel(new Cube, new Material)
+                       errors.add new Error("Model failed to load: Asset empty at '{self.path}'")
+                       leaves_cache.add new LeafModel(new Cube, new Material)
                        return
                end
 
@@ -70,8 +74,8 @@ class ModelAsset
                var parser = new ObjFileParser(content)
                var obj_def = parser.parse
                if obj_def == null then
-                       print_error "Model failed to load: .obj format error on '{self.path}'"
-                       leaves.add new LeafModel(new Cube, new Material)
+                       errors.add new Error("Model failed to load: .obj format error on '{self.path}'")
+                       leaves_cache.add new LeafModel(new Cube, new Material)
                        return
                end
 
@@ -80,10 +84,29 @@ class ModelAsset
 
                # Build models
                var converter = new ModelFromObj(path, obj_def)
-               converter.models leaves
+               converter.models leaves_cache
+               errors.add_all converter.errors
        end
 
-       redef var leaves = new Array[LeafModel]
+       redef fun leaves
+       do
+               if not loaded then
+                       # Lazy load
+                       load
+
+                       # Print errors when lazy loading only
+                       if errors.length == 1 then
+                               print_error errors.first
+                       else if errors.length > 1 then
+                               print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
+                               print_error errors.join("\n* ")
+                       end
+               end
+
+               return leaves_cache
+       end
+
+       private var leaves_cache = new Array[LeafModel]
 end
 
 # Short-lived service to convert an `ObjDef` to `models`
@@ -98,7 +121,11 @@ private class ModelFromObj
        # Parsed .obj definition
        var obj_def: ObjDef
 
-       fun models(array: Array[LeafModel])
+       # Errors raised by calls to `models`
+       var errors = new Array[Error]
+
+       # Fill `leaves` with models described in `obj_def`
+       fun models(leaves: Array[LeafModel])
        do
                # Sort faces by material
                var mtl_to_faces = new MultiHashMap[String, ObjFace]
@@ -113,23 +140,22 @@ private class ModelFromObj
                end
 
                # Load material libs
-               # TODO do not load each libs more than once
-               var mtl_libs = new Map[String, Map[String, MtlDef]]
+               var mtl_libs = sys.mtl_libs
                var lib_names = obj_def.material_libs
                for name in lib_names do
-                       var lib_path = self.path.dirname / name
-                       var lib_asset = new TextAsset(lib_path)
+                       var asset_path = self.path.dirname / name
+                       var lib_asset = new TextAsset(asset_path)
                        lib_asset.load
 
                        var error = lib_asset.error
                        if error != null then
-                               print_error error.to_s
+                               errors.add error
                                continue
                        end
 
                        var mtl_parser = new MtlFileParser(lib_asset.to_s)
                        var mtl_lib = mtl_parser.parse
-                       mtl_libs[name] = mtl_lib
+                       mtl_libs[asset_path] = mtl_lib
                end
 
                # Create 1 mesh per material, and prepare materials
@@ -149,7 +175,8 @@ private class ModelFromObj
                        var mtl_lib_name = faces.first.material_lib
                        var mtl_name = faces.first.material_name
                        if mtl_lib_name != null and mtl_name != null then
-                               var mtl_lib = mtl_libs[mtl_lib_name]
+                               var asset_path = self.path.dirname / mtl_lib_name
+                               var mtl_lib = mtl_libs[asset_path]
                                var mtl = mtl_lib.get_or_null(mtl_name)
                                if mtl != null then
                                        mtl_def = mtl
@@ -158,7 +185,7 @@ private class ModelFromObj
                                                texture_names.add self.path.dirname / e
                                        end
                                else
-                                       print_error "mtl '{mtl_name}' not found in '{mtl_lib_name}'"
+                                       errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                end
                        end
 
@@ -170,6 +197,10 @@ private class ModelFromObj
                        if not asset_textures_by_name.keys.has(name) then
                                var tex = new TextureAsset(name)
                                asset_textures_by_name[name] = tex
+
+                               tex.load
+                               var error = tex.error
+                               if error != null then errors.add error
                        end
                end
 
@@ -215,7 +246,7 @@ private class ModelFromObj
                        if material == null then material = new Material
 
                        var model = new LeafModel(mesh, material)
-                       array.add model
+                       leaves.add model
                end
        end
 
@@ -381,6 +412,9 @@ redef class Sys
        # Textures loaded from .mtl files for models
        var asset_textures_by_name = new Map[String, TextureAsset]
 
+       # Loaded .mtl material definitions, sorted by path in assets and material name
+       private var mtl_libs = new Map[String, Map[String, MtlDef]]
+
        # All instantiated asset models
        var models = new Set[ModelAsset]
 
index 3d0e841..954b962 100644 (file)
@@ -157,7 +157,7 @@ redef class Material
 
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                var display = app.display
                assert display != null
index 634656b..cb6bdf0 100644 (file)
@@ -36,6 +36,10 @@ redef class GamnitDisplay
 
        redef fun show_cursor=(val) do sdl.show_cursor = val
 
+       redef fun lock_cursor=(val) do sdl.relative_mouse_mode = val
+
+       redef fun lock_cursor do return sdl.relative_mouse_mode
+
        # Setup SDL, wm, EGL in order
        redef fun setup
        do
index 8439b29..9c2c220 100644 (file)
@@ -63,8 +63,8 @@ redef class App
                # cause glitches on mobiles devices with small depth buffer.
                world_camera.near = 1.0
 
-               # Make the background blue and opaque.
-               glClearColor(0.0, 0.0, 1.0, 1.0)
+               # Make the background sky blue and opaque.
+               glClearColor(0.5, 0.8, 1.0, 1.0)
 
                # If the first command line argument is an integer, add extra sprites.
                if args.not_empty and args.first.is_int then
@@ -109,8 +109,9 @@ redef class App
                if event isa QuitEvent or
                  (event isa KeyEvent and event.name == "escape" and event.is_up) then
                        # When window close button, escape or back key is pressed
-                       # show the average FPS over the last few seconds.
-                       print "{current_fps} fps"
+                       print "Ran at {current_fps} FPS in the last few seconds"
+
+                       print "Performance statistics to detect bottlenecks:"
                        print sys.perfs
 
                        # Quit abruptly
index abd573d..e9f8499 100644 (file)
@@ -155,7 +155,7 @@ class ObjDef
        # Faces
        var faces = new Array[ObjFace]
 
-       # Referenced material libraries
+       # Relative paths to referenced material libraries
        fun material_libs: Set[String] do
                var libs = new Set[String]
                for face in faces do
index 38db2db..8aad96f 100644 (file)
@@ -40,7 +40,7 @@
 # ~~~
 module client
 
-import common
+intrude import common
 
 # Information of the remove server
 class RemoteServerConfig
@@ -126,3 +126,55 @@ class RemoteServer
                return true
        end
 end
+
+# Discover local servers responding on UDP `discovery_port`
+#
+# Sends a message in the format `gamnit::network? handshake_app_name` and
+# looks for the response `gamnit::network! handshake_app_name port_number`.
+# Waits for `timeout`, or the default 0.1 seconds, after sending the message.
+#
+# The server usually responds using the method `answer_discovery_requests`.
+# When receiving responses, the client may then choose a server and
+# connect via `new RemoteServer`.
+#
+# ~~~
+# var servers = discover_local_servers
+# if servers.not_empty then
+#     var server = new RemoteServer(servers.first)
+#     server.connect
+#     server.writer.serialize "hello server"
+#     server.socket.close
+# end
+# ~~~
+fun discover_local_servers(timeout: nullable Float): Array[RemoteServerConfig]
+do
+       timeout = timeout or else 0.1
+
+       var s = new UDPSocket
+       s.enable_broadcast = true
+       s.blocking = false
+       s.broadcast(discovery_port, "{discovery_request_message} {handshake_app_name}")
+       timeout.sleep
+
+       var r = new Array[RemoteServerConfig]
+       loop
+               var ptr = new Ref[nullable SocketAddress](null)
+               var resp = s.recv_from(1024, ptr)
+               var src = ptr.item
+
+               if resp.is_empty then
+                       # No response
+                       break
+               else
+                       assert src != null
+                       var words = resp.split(" ")
+                       if words.length == 3 and words[0] == discovery_response_message and
+                          words[1] == handshake_app_name and words[2].is_int then
+                               var address = src.address
+                               var port = words[2].to_i
+                               r.add new RemoteServerConfig(address, port)
+                       end
+               end
+       end
+       return r
+end
index 54e764a..579145b 100644 (file)
@@ -22,8 +22,9 @@ import binary::serialization
 #
 # This name must be the same between client/server and
 # it should not be used by other programs that may interfere.
-#
 # Both client and server refuse connections with a different name.
+#
+# This value must not contain spaces.
 fun handshake_app_name: String do return program_name
 
 # Version of the communication protocol to use in the handshake
@@ -32,4 +33,17 @@ fun handshake_app_name: String do return program_name
 # that different versions indicates incompatible protocols.
 #
 # Both client and server refuse connections with a different version.
+#
+# This value must not contain spaces.
 fun handshake_app_version: String do return "0.0"
+
+# Server port listening for discovery requests
+#
+# This name must be the same between client/server.
+fun discovery_port: Int do return 18722
+
+# First word in discovery requests
+private fun discovery_request_message: String do return "gamnit::network?"
+
+# First word in discovery responses
+private fun discovery_response_message: String do return "gamnit::network!"
index 83fd35d..93e1407 100644 (file)
 # limitations under the License.
 
 # Easy client/server logic for games and simple distributed applications
+#
+# Both `gamnit::client` and `gamnit::server` can be used separately or
+# together by importing `gamnit::network`.
+# Use both modules to create an program that discover local servers
+# or create one if none is found:
+#
+# ~~~
+# redef fun handshake_app_name do return "network_test"
+#
+# # Discover local servers
+# var servers = discover_local_servers
+# if servers.not_empty then
+#     # Try to connect to the first local server
+#     var server_info = servers.first
+#     var server = new RemoteServer(server_info)
+#
+#     if not server.connect then
+#         print_error "Failed to connect to {server_info.address}:{server_info.port}"
+#     else if not server.handshake then
+#         print_error "Failed handshake with {server_info.address}:{server_info.port}"
+#     else
+#         # Connected!
+#         print "Connected to {server_info.address}:{server_info.port}"
+#
+#         # Write something and close connection
+#         server.writer.serialize "hello server"
+#         server.socket.as(not null).close
+#     end
+# else
+#     # Create a local server
+#     var connect_port = 33729
+#     print "Launching server: connect on {connect_port}, discovery on {discovery_port}"
+#     var server = new Server(connect_port)
+#
+#     # Don't loop if testing
+#     if "NIT_TESTING".environ == "true" then exit 0
+#
+#     loop
+#         # Respond to discovery requests
+#         server.answer_discovery_requests
+#
+#         # Accept new clients
+#         var new_clients = server.accept_clients
+#         for client in new_clients do
+#             # Read something and close connection
+#             assert client.reader.deserialize == "hello server"
+#             client.socket.close
+#         end
+#     end
+# end
+# ~~~
 module network
 
 import server
index fcff7a2..70cf002 100644 (file)
 #
 #     # `accept_clients` in non-blocking,
 #     # sleep before tying again, or do something else.
-#     nanosleep(0, 50000000)
+#     0.5.sleep
 #     printn "."
 # end
 # ~~~
 module server
 
-import common
+intrude import common
 
 # Game server controller
 class Server
@@ -56,19 +56,24 @@ class Server
        # All connected `RemoteClient`
        var clients = new Array[RemoteClient]
 
-       # Socket accepting new connections
+       # TCP socket accepting new connections
+       #
+       # Opened on the first call to `accept_clients`.
        var listening_socket: TCPServer is lazy do
-               print port
                var socket = new TCPServer(port)
                socket.listen 8
                socket.blocking = false
                return socket
        end
-       init do listening_socket
 
        # Accept currently waiting clients and return them as an array
-       fun accept_clients: Array[RemoteClient]
+       #
+       # If `add_to_clients`, the default, the new clients are added to `clients`.
+       # Otherwise, the return value of `accept_clients` may be added to `clients`
+       # explicitly by the caller after an extra verification or sorting.
+       fun accept_clients(add_to_clients: nullable Bool): Array[RemoteClient]
        do
+               add_to_clients = add_to_clients or else true
                assert not listening_socket.closed
 
                var new_clients = new Array[RemoteClient]
@@ -87,17 +92,63 @@ class Server
                                client_socket.close
                        end
                end
+
+               if add_to_clients then clients.add_all new_clients
+
                return new_clients
        end
 
        # Broadcast a `message` to all `clients`, then flush the connection
-       fun broadcast(message: Serializable)
+       #
+       # The client `except` is skipped and will not receive the `message`.
+       fun broadcast(message: Serializable, except: nullable RemoteClient)
        do
-               for client in clients do
+               for client in clients do if client != except then
                        client.writer.serialize(message)
                        client.socket.flush
                end
        end
+
+       # Respond to pending discovery requests by sending the TCP listening address and port
+       #
+       # Returns the number of valid requests received.
+       #
+       # The response messages includes the TCP listening address and port
+       # for remote clients to connect with TCP using `connect`.
+       # These connections are accepted by the server with `accept_clients`.
+       fun answer_discovery_requests: Int
+       do
+               var count = 0
+               loop
+                       var ptr = new Ref[nullable SocketAddress](null)
+                       var read = discovery_socket.recv_from(1024, ptr)
+
+                       # No sender means there is no discovery request
+                       var sender = ptr.item
+                       if sender == null then break
+
+                       var words = read.split(" ")
+                       if words.length != 2 or words[0] != discovery_request_message or words[1] != handshake_app_name then
+                               print "Server Warning: Rejected discovery request '{read}' from {sender.address}:{sender.port}"
+                               continue
+                       end
+
+                       var msg = "{discovery_response_message} {handshake_app_name} {self.port}"
+                       discovery_socket.send_to(sender.address, sender.port, msg)
+                       count += 1
+               end
+               return count
+       end
+
+       # UDP socket responding to discovery requests
+       #
+       # Usually opened on the first call to `answer_discovery_request`.
+       var discovery_socket: UDPSocket is lazy do
+               var s = new UDPSocket
+               s.blocking = false
+               s.bind(null, discovery_port)
+               return s
+       end
 end
 
 # Reference to a remote client connected to this server
@@ -127,7 +178,7 @@ class RemoteClient
        # Check for compatibility with the client
        fun handshake: Bool
        do
-               print "Server: Handshake requested by {socket.address}"
+               print "Server: Handshake initiated by {socket.address}"
 
                # Make sure it is the same app
                var server_app = sys.handshake_app_name
index f999637..1c8f10d 100644 (file)
@@ -31,8 +31,7 @@ redef class Point[N]
        do
                var dx = other.x.to_f - x.to_f
                var dy = other.y.to_f - y.to_f
-               var a = sys.atan2(dy.to_f, dx.to_f)
-               return a
+               return sys.atan2(dy.to_f, dx.to_f)
        end
 end
 
@@ -52,19 +51,21 @@ redef universal Float
                return s
        end
 
-       # Linear interpolation on the arc delimited by `self` and `other` at `p` out of 1.0
+       # Linear interpolation of between the angles `a` and `b`, in radians
        #
        # The result is normalized with `angle_normalize`.
        #
        # ~~~
-       # assert 0.0.angle_lerp(pi, 0.5).is_approx(0.5*pi, 0.0001)
-       # assert 0.0.angle_lerp(pi, 8.5).is_approx(0.5*pi, 0.0001)
-       # assert 0.0.angle_lerp(pi, 7.5).is_approx(-0.5*pi, 0.0001)
+       # assert 0.5.angle_lerp(0.0, pi).is_approx(0.5*pi, 0.0001)
+       # assert 8.5.angle_lerp(0.0, pi).is_approx(0.5*pi, 0.0001)
+       # assert 7.5.angle_lerp(0.0, pi).is_approx(-0.5*pi, 0.0001)
+       # assert 0.5.angle_lerp(0.2, 2.0*pi-0.1).is_approx(0.05, 0.0001)
        # ~~~
-       fun angle_lerp(other, p: Float): Float
+       fun angle_lerp(a, b: Float): Float
        do
-               var d = other - self
-               var a = self + d*p
-               return a.angle_normalize
+               var d = b - a
+               while d > pi do d -= 2.0*pi
+               while d < -pi do d += 2.0*pi
+               return (a + d*self).angle_normalize
        end
 end
diff --git a/lib/gmp/native_gmp.nit b/lib/gmp/native_gmp.nit
new file mode 100644 (file)
index 0000000..8c8b86a
--- /dev/null
@@ -0,0 +1,262 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Low-level GMP features
+module native_gmp is ldflags("-lgmp")
+
+in "C header" `{
+    #include <gmp.h>
+`}
+
+# Multi precision Integer
+extern class NativeMPZ `{mpz_ptr`}
+
+    # Initializing
+
+    new `{
+        mpz_ptr self =  (mpz_ptr)malloc(sizeof(mpz_t));
+        mpz_init(self);
+        return self;
+    `}
+
+    # Arithmetic Functions
+
+    fun add(res, op: NativeMPZ) `{
+        mpz_add(res, self, op);
+    `}
+
+    fun add_ui(res: NativeMPZ, op: UInt64) `{
+        mpz_add_ui(res, self, *op);
+    `}
+
+    fun sub(res, op: NativeMPZ) `{
+        mpz_sub(res, self, op);
+    `}
+
+    fun sub_ui(res: NativeMPZ, op: UInt64) `{
+        mpz_sub_ui(res, self, *op);
+    `}
+
+    fun mul(res, op: NativeMPZ) `{
+        mpz_mul(res, self, op);
+    `}
+
+    fun mul_si(res: NativeMPZ, op: Int) `{
+        mpz_mul_si(res, self, op);
+    `}
+
+    fun neg(res: NativeMPZ) `{
+        mpz_neg(res, self);
+    `}
+
+    fun abs(res: NativeMPZ) `{
+        mpz_abs(res, self);
+    `}
+
+    fun tdiv_q(res, op: NativeMPZ) `{
+        mpz_tdiv_q(res, self, op);
+    `}
+
+    fun tdiv_q_ui(res: NativeMPZ, op: UInt64) `{
+        mpz_tdiv_q_ui(res, self, *op);
+    `}
+
+    fun mod(res, op: NativeMPZ) `{
+        mpz_mod(res, self, op);
+    `}
+
+    fun mod_ui(res: NativeMPZ, op: UInt64) `{
+        mpz_mod_ui(res, self, *op);
+    `}
+
+    fun pow_ui(res: NativeMPZ, op: UInt64) `{
+        mpz_pow_ui(res, self, *op);
+    `}
+
+    #Number Theoretic Functions
+
+    fun probab_prime_p(reps: Int32): Int `{
+        return mpz_probab_prime_p(self, reps);
+    `}
+
+    fun nextprime(res: NativeMPZ) `{
+        mpz_nextprime(res, self);
+    `}
+
+    fun gcd(res, op: NativeMPZ) `{
+        mpz_gcd(res, self, op);
+    `}
+
+    fun gcd_ui(res: NativeMPZ, op: UInt64) `{
+        mpz_gcd_ui(res, self, *op);
+    `}
+
+    # Comparison Functions
+
+    fun cmp(op: NativeMPZ): Int `{
+        return mpz_cmp(self, op);
+    `}
+
+    fun cmp_si(op: Int): Int `{
+        return mpz_cmp_si(self, op);
+    `}
+
+    # Assignment
+
+    fun set(op: NativeMPZ) `{ mpz_set(self, op); `}
+
+    fun set_si(op: Int) `{ mpz_set_si(self, op); `}
+
+    fun set_d(op: Float) `{ mpz_set_d(self, op); `}
+
+    fun set_q(op: NativeMPQ) `{ mpz_set_q(self, op); `}
+
+    fun set_str(str: CString, base: Int32) `{
+        mpz_set_str(self, str, base);
+    `}
+
+    fun swap(op: NativeMPZ) `{ mpz_swap(self, op); `}
+
+    # Conversion Functions
+
+    fun get_si: Int `{ return mpz_get_si(self); `}
+
+    fun get_d: Float `{ return mpz_get_d(self); `}
+
+    fun get_str(base: Int32): CString `{
+        return mpz_get_str(NULL, base, self);
+    `}
+
+    # Delete this NativeMPZ
+    fun finalize `{
+        mpz_clear(self);
+        free(self);
+    `}
+end
+
+# Multi precision Rational
+extern class NativeMPQ `{mpq_ptr`}
+
+    # Initializing
+
+    new `{
+        mpq_ptr self = (mpq_ptr)malloc(sizeof(mpq_t));
+        mpq_init(self);
+        return self;
+    `}
+
+    # Arithmetic Functions
+
+    fun add(res, op: NativeMPQ) `{
+        mpq_add(res, self, op);
+    `}
+
+    fun sub(res, op: NativeMPQ) `{
+        mpq_sub(res, self, op);
+    `}
+
+    fun mul(res, op: NativeMPQ) `{
+        mpq_mul(res, self, op);
+    `}
+
+    fun div(res, op: NativeMPQ) `{
+        mpq_div(res, self, op);
+    `}
+
+    fun neg(res: NativeMPQ) `{
+        mpq_neg(res, self);
+    `}
+
+    fun abs(res: NativeMPQ) `{
+        mpq_abs(res, self);
+    `}
+
+    fun inv(res: NativeMPQ) `{
+        mpq_inv(res, self);
+    `}
+
+    # Assignment
+
+    fun set(op: NativeMPQ) `{ mpq_set(self, op); `}
+
+    fun set_z(op: NativeMPZ) `{ mpq_set_z(self, op); `}
+
+    fun set_si(op1: Int, op2: Int) `{
+        mpq_set_si(self, op1, op2);
+        mpq_canonicalize(self);
+    `}
+
+    fun set_d(op: Float) `{ mpq_set_d(self, op); `}
+
+    fun set_str(str: CString) `{
+        mpq_set_str(self, str, 10);
+        mpq_canonicalize(self);
+    `}
+
+    fun swap(op: NativeMPQ) `{ mpq_swap(self, op); `}
+
+    # Convertion Functions
+
+    fun get_d: Float `{ return mpq_get_d(self); `}
+
+    fun get_str(base: Int32): CString `{
+        return mpq_get_str(NULL, base, self);
+    `}
+
+    # Comparison Functions
+
+    fun cmp(op: NativeMPQ): Int `{
+        return mpq_cmp(self, op);
+    `}
+
+#    fun cmp_z(op: NativeMPZ): Int `{
+#        return mpq_cmp_z(self, op);
+#    `}
+
+    fun cmp_si(num: Int, den: Int): Int `{
+        return mpq_cmp_si(self, num, den);
+    `}
+
+    fun equal(op: NativeMPQ): Bool `{
+        return mpq_equal(self, op);
+    `}
+
+    # Getter
+
+    fun numref: NativeMPZ `{
+        return mpq_numref(self);
+    `}
+
+    fun denref: NativeMPZ `{
+        return mpq_denref(self);
+    `}
+
+    # Delete this NativeMPZ
+    fun finalize `{
+        mpq_clear(self);
+        free(self);
+    `}
+end
+
+# Boxing of C Unsigned Long
+extern class UInt64 `{ uint64_t* `}
+    new `{
+        uint64_t *self = (uint64_t*)malloc(sizeof(uint64_t));
+        return self;
+    `}
+
+    fun set_si(val: Int) `{
+        *self = (uint64_t)val;
+    `}
+end
diff --git a/lib/gmp/test_native_gmp.nit b/lib/gmp/test_native_gmp.nit
new file mode 100644 (file)
index 0000000..b27a3c0
--- /dev/null
@@ -0,0 +1,361 @@
+# 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.
+
+module test_native_gmp is test_suite
+
+import test_suite
+import native_gmp
+
+class TestNativeMPZ
+    super TestSuite
+
+    var op1: NativeMPZ
+    var op2: NativeMPZ
+    var ui: UInt64
+    var r: NativeMPQ
+    var res: NativeMPZ
+
+    init do end
+
+    redef fun before_test do
+        op1 = new NativeMPZ
+        op2 = new NativeMPZ
+        ui = new UInt64
+        r = new NativeMPQ
+        res = new NativeMPZ
+    end
+
+    redef fun after_test do
+        op1.finalize
+        op2.finalize
+        ui.free
+        r.finalize
+        res.finalize
+    end
+
+    fun test_add do
+        op1.set_si 10
+        op2.set_si 20
+        op1.add(res, op2)
+        assert(res.cmp_si(30) == 0)
+    end
+
+    fun test_add_ui do
+        op1.set_si 10
+        ui.set_si 20
+        op1.add_ui(res, ui)
+        assert(res.cmp_si(30) == 0)
+    end
+
+    fun test_sub do
+        op1.set_si 30
+        op2.set_si 20
+        op1.sub(res, op2)
+        assert(res.cmp_si(10) == 0)
+    end
+
+    fun test_sub_ui do
+        op1.set_si 30
+        ui.set_si 20
+        op1.sub_ui(res, ui)
+        assert(res.cmp_si(10) == 0)
+    end
+
+    fun test_mul do
+        op1.set_si 10
+        op2.set_si 2
+        op1.mul(res, op2)
+        assert(res.cmp_si(20) == 0)
+    end
+
+    fun test_mul_si do
+        op1.set_si 2
+        op1.mul_si(res, 20)
+        assert(res.cmp_si(40) == 0)
+    end
+
+    fun test_neg do
+        op1.set_si 10
+        op1.neg(res)
+        assert(res.cmp_si(-10) == 0)
+    end
+
+    fun test_abs do
+        op1.set_si -10
+        op1.abs(res)
+        assert(res.cmp_si(10) == 0)
+    end
+
+    fun test_tdiv_q do
+        op1.set_si 11
+        op2.set_si 2
+        op1.tdiv_q(res, op2)
+        assert(res.cmp_si(5) == 0)
+    end
+
+    fun test_tdiv_q_ui do
+        op1.set_si 20
+        ui.set_si 20
+        op1.tdiv_q_ui(res, ui)
+        assert(res.cmp_si(1) == 0)
+    end
+
+    fun test_mod do
+        op1.set_si 11
+        op2.set_si 2
+        op1.mod(res, op2)
+        assert(res.cmp_si(1) == 0)
+    end
+
+    fun test_mod_ui do
+        op1.set_si 20
+        ui.set_si 20
+        op1.mod_ui(res, ui)
+        assert(res.cmp_si(0) == 0)
+    end
+
+    fun test_probab_prime_p do
+        op1.set_si 11
+        assert(op1.probab_prime_p(10i32) == 2)
+    end
+
+    fun test_nextprime do
+        op1.set_si 7
+        op1.nextprime res
+        assert(res.cmp_si(11) == 0)
+    end
+
+    fun test_gcd do
+        op1.set_si 12
+        op2.set_si 8
+        op1.gcd(res, op2)
+        assert(res.cmp_si(4) == 0)
+    end
+
+    fun test_gcd_ui do
+        op1.set_si 30
+        ui.set_si 20
+        op1.gcd_ui(res, ui)
+        assert(res.cmp_si(10) == 0)
+    end
+
+    fun test_cmp do
+        op1.set_si 12
+        op2.set_si 12
+        assert(op1.cmp(op2) == 0)
+    end
+
+    fun test_cmp_si do
+        op1.set_si 30
+        assert(op1.cmp_si(30) == 0)
+    end
+
+    fun test_set do
+        op1.set_si 12
+        op2.set op1
+        assert(op1.cmp(op2) == 0)
+    end
+
+    fun test_set_si do
+        op1.set_si 30
+        assert(op1.cmp_si(30) == 0)
+    end
+
+    fun test_set_d do
+        op1.set_d 3.0
+        assert(op1.cmp_si(3) == 0)
+    end
+
+    fun test_set_q do
+        r.set_si(30, 1)
+        op1.set_q r
+        assert(op1.cmp_si(30) == 0)
+    end
+
+    fun test_set_str do
+        op1.set_str("30".to_cstring, 10i32)
+        assert(op1.cmp_si(30) == 0)
+    end
+
+    fun test_swap do
+        op1.set_si 10
+        op2.set_si 20
+        op1.swap op2
+        assert(op1.cmp_si(20) == 0 and op2.cmp_si(10) == 0)
+    end
+
+    fun test_get_si do
+        op1.set_si 12
+        assert(op1.get_si == 12)
+    end
+
+    fun test_get_d do
+        op1.set_si 12
+        assert(op1.get_d == 12.0)
+    end
+
+    fun test_get_str do
+        op1.set_si 12
+        assert(op1.get_str(10i32).to_s == "12")
+    end
+end
+
+class TestNativeMPQ
+    super TestSuite
+
+    var op1: NativeMPQ
+    var op2: NativeMPQ
+    var l: NativeMPZ
+    var res: NativeMPQ
+
+    init do end
+
+    redef fun before_test do
+        op1 = new NativeMPQ
+        op2 = new NativeMPQ
+        l = new NativeMPZ
+        res = new NativeMPQ
+    end
+
+    redef fun after_test do
+        op1.finalize
+        op2.finalize
+        l.finalize
+        res.finalize
+    end
+
+    fun test_add do
+        op1.set_si(10, 3)
+        op2.set_si(20, 3)
+        op1.add(res, op2)
+        assert(res.cmp_si(10, 1) == 0)
+    end
+
+    fun test_sub do
+        op1.set_si(20, 3)
+        op2.set_si(10, 3)
+        op1.sub(res, op2)
+        assert(res.cmp_si(10, 3) == 0)
+    end
+
+    fun test_mul do
+        op1.set_si(10, 3)
+        op2.set_si(10, 1)
+        op1.mul(res, op2)
+        assert(res.cmp_si(100, 3) == 0)
+    end
+
+    fun test_div do
+        op1.set_si(10, 3)
+        op2.set_si(2, 1)
+        op1.div(res, op2)
+        assert(res.cmp_si(5, 3) == 0)
+    end
+
+    fun test_neg do
+        op1.set_si(10, 3)
+        op1.neg(res)
+        assert(res.cmp_si(-10, 3) == 0)
+    end
+
+    fun test_abs do
+        op1.set_si(-10, 3)
+        op1.abs(res)
+        assert(res.cmp_si(10, 3) == 0)
+    end
+
+    fun test_inv do
+        op1.set_si(10, 3)
+        op1.inv(res)
+        assert(res.cmp_si(3, 10) == 0)
+    end
+
+    fun test_set do
+        op1.set_si(10, 3)
+        res.set op1
+        assert(res.cmp(op1) == 0)
+    end
+
+    fun test_set_z do
+        l.set_si 10
+        res.set_z l
+        assert(res.cmp_si(10, 1) == 0)
+    end
+
+    fun test_set_si do
+        res.set_si(10, 3)
+        assert(res.cmp_si(10, 3) == 0)
+    end
+
+    fun test_set_d do
+        res.set_d(0.5)
+        assert(res.cmp_si(1, 2) == 0)
+    end
+
+    fun test_set_str do
+        res.set_str "1/2".to_cstring
+        assert(res.cmp_si(1, 2) == 0)
+    end
+
+    fun test_swap do
+        op1.set_si(10, 3)
+        res.swap(op1)
+        assert(res.cmp_si(10, 3) == 0)
+    end
+
+    fun test_get_d do
+        res.set_si(1, 2)
+        assert(res.get_d == 0.5)
+    end
+
+    fun test_get_str do
+        res.set_si(1, 2)
+        assert(res.get_str(10i32).to_s == "1/2")
+    end
+
+    fun test_cmp do
+        op1.set_si(10, 3)
+        op2.set_si(10, 3)
+        assert(op1.cmp(op2) == 0)
+    end
+
+#    fun test_cmp_z do
+#        op1.set_si(10, 1)
+#        l.set_si 10
+#        assert(op1.cmp_z(l) == 0)
+#    end
+
+    fun test_cmp_si do
+        op1.set_si(10, 3)
+        assert(op1.cmp_si(10, 3) == 0)
+    end
+
+    fun test_equal do
+        op1.set_si(10, 3)
+        op2.set_si(10, 3)
+        assert op1.equal(op2)
+    end
+
+    fun test_numref do
+        op1.set_si(10, 3)
+        l.set_si 10
+        assert(op1.numref.cmp(l) == 0)
+    end
+
+    fun test_denref do
+        op1.set_si(10, 3)
+        l.set_si 3
+        assert(op1.denref.cmp(l) == 0)
+    end
+end
index 908761a..a9bf8b8 100644 (file)
@@ -150,4 +150,27 @@ redef class Matrix
                var rotated = self * rotation
                self.items = rotated.items
        end
+
+       # Rotation matrix from Euler angles `pitch`, `yaw` and `roll` in radians
+       #
+       # Apply a composition of intrinsic rotations around the axes x-y'-z''.
+       # Or `pitch` around the X axis, `yaw` around Y and `roll` around Z,
+       # applied successively. All rotations follow the right hand rule.
+       #
+       # This service aims to respect the world axes and logic of `gamnit`,
+       # it may not correspond to all needs.
+       new gamnit_euler_rotation(pitch, yaw, roll: Float)
+       do
+               var c1 = pitch.cos
+               var s1 = pitch.sin
+               var c2 = yaw.cos
+               var s2 = yaw.sin
+               var c3 = roll.cos
+               var s3 = roll.sin
+               return new Matrix.from(
+                       [[          c2*c3,          -c2*s3,   -s2, 0.0],
+                        [ c1*s3+c3*s1*s2,  c1*c3-s1*s2*s3, c2*s1, 0.0],
+                        [-s1*s3+c1*c3*s2, -c3*s1-c1*s2*s3, c1*c2, 0.0],
+                        [            0.0,             0.0,   0.0, 1.0]])
+       end
 end
index 9212a8e..e229a8c 100644 (file)
@@ -15,6 +15,7 @@
 # limitations under the License.
 
 import popcorn
+import popcorn::pop_json
 
 class CounterAPI
        super Handler
index 8039ba9..2f84be7 100644 (file)
@@ -77,6 +77,7 @@
 # ~~~
 module pop_auth
 
+import pop_json
 import pop_sessions
 import github
 
index 6b1e825..e657d68 100644 (file)
@@ -18,8 +18,6 @@
 module pop_handlers
 
 import pop_routes
-import json::static
-import json
 import csv
 
 # Class handler for a route.
@@ -450,16 +448,6 @@ redef class HttpResponse
                send(html, status)
        end
 
-       # Write data as JSON and set the right content type header.
-       fun json(json: nullable Serializable, status: nullable Int) do
-               header["Content-Type"] = media_types["json"].as(not null)
-               if json == null then
-                       send(null, status)
-               else
-                       send(json.to_json, status)
-               end
-       end
-
        # Write data as CSV and set the right content type header.
        fun csv(csv: nullable CsvDocument, status: nullable Int) do
                header["Content-Type"] = media_types["csv"].as(not null)
@@ -470,16 +458,6 @@ redef class HttpResponse
                end
        end
 
-       # Write error as JSON.
-       #
-       # Format: `{"message": message, "status": status}`
-       fun json_error(message: String, status: Int) do
-               var obj = new JsonObject
-               obj["status"] = status
-               obj["message"] = message
-               json(obj, status)
-       end
-
        # Redirect response to `location`
        #
        # Use by default 303 See Other as it is the RFC
diff --git a/lib/popcorn/pop_json.nit b/lib/popcorn/pop_json.nit
new file mode 100644 (file)
index 0000000..64a06e1
--- /dev/null
@@ -0,0 +1,185 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2017 Alexandre Terrasa <alexandre@moz-code.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.
+
+# Introduce useful services for JSON REST API handlers.
+#
+# Validation and Deserialization of request bodies:
+#
+# ~~~nit
+# class MyJsonHandler
+#      super Handler
+#
+#      # Validator used do validate the body
+#      redef var validator = new MyFormValidator
+#
+#      # Define the kind of objects expected by the deserialization process
+#      redef type BODY: MyForm
+#
+#      redef fun post(req, res) do
+#              var post = validate_body(req, res)
+#              if post == null then return # Validation error: let popcorn return a HTTP 400
+#              var form = deserialize_body(req, res)
+#              if form == null then return # Deserialization error: let popcorn return a HTTP 400
+#
+#              # TODO do something with the input
+#              print form.name
+#      end
+# end
+#
+# class MyForm
+#      serialize
+#
+#      var name: String
+# end
+#
+# class MyFormValidator
+#      super ObjectValidator
+#
+#      init do
+#              add new StringField("name", min_size=1, max_size=255)
+#      end
+# end
+# ~~~
+module pop_json
+
+import json
+import pop_handlers
+import pop_validation
+
+redef class Handler
+
+       # Validator used to check body input
+       #
+       # Here we use the `pop_validation` module to validate JSON document from the request body.
+       var validator: nullable DocumentValidator = null
+
+       # Validate body input with `validator`
+       #
+       # Try to validate the request body as a json document using `validator`:
+       # * Returns the validated string input if the result of the validation is ok.
+       # * Answers a json error and returns `null` if something went wrong.
+       # * If no `validator` is set, returns the body without validation.
+       #
+       # Example:
+       #
+       # ~~~nit
+       # class ValidatedHandler
+       #       super Handler
+       #
+       #       redef var validator = new MyObjectValidator
+       #
+       #       redef fun post(req, res) do
+       #               var body = validate_body(req, res)
+       #               if body == null then return # Validation error
+       #               # At this point popcorn returned a HTTP 400 code with the validation error
+       #               # if the validation failed.
+       #
+       #               # TODO do something with the input
+       #               print body
+       #       end
+       # end
+       #
+       # class MyObjectValidator
+       #       super ObjectValidator
+       #
+       #       init do
+       #               add new StringField("name", min_size=1, max_size=255)
+       #       end
+       # end
+       # ~~~
+       fun validate_body(req: HttpRequest, res: HttpResponse): nullable String do
+               var body = req.body
+
+               var validator = self.validator
+               if validator == null then return body
+
+               if not validator.validate(body) then
+                       res.json(validator.validation, 400)
+                       return null
+               end
+               return body
+       end
+
+       # Deserialize the request body
+       #
+       # Returns the deserialized request body body or `null` if something went wrong.
+       # If the object cannot be deserialized, answers with a HTTP 400.
+       #
+       # See `BODY` and `new_body_object`.
+       #
+       # Example:
+       # ~~~nit
+       # class MyDeserializedHandler
+       #       super Handler
+       #
+       #       redef type BODY: MyObject
+       #
+       #       redef fun post(req, res) do
+       #               var form = deserialize_body(req, res)
+       #               if form == null then return # Deserialization error
+       #               # At this point popcorn returned a HTTP 400 code if something was wrong with
+       #               # the deserialization process
+       #
+       #               # TODO do something with the input
+       #               print form.name
+       #       end
+       # end
+       #
+       # class MyObject
+       #       serialize
+       #
+       #       var name: String
+       # end
+       # ~~~
+       fun deserialize_body(req: HttpRequest, res: HttpResponse): nullable BODY do
+               var body = req.body
+               var deserializer = new JsonDeserializer(body)
+               var form = deserializer.deserialize(body)
+               if not form isa BODY or deserializer.errors.not_empty then
+                       res.json_error("Bad input", 400)
+                       return null
+               end
+               return form
+       end
+
+       # Kind of objects returned by `deserialize_body`
+       #
+       # Define it in each sub handlers depending on the kind of objects sent in request bodies.
+       type BODY: Serializable
+end
+
+redef class HttpResponse
+
+       # Write data as JSON and set the right content type header.
+       fun json(json: nullable Serializable, status: nullable Int) do
+               header["Content-Type"] = media_types["json"].as(not null)
+               if json == null then
+                       send(null, status)
+               else
+                       send(json.to_json, status)
+               end
+       end
+
+       # Write error as JSON.
+       #
+       # Format: `{"message": message, "status": status}`
+       fun json_error(message: String, status: Int) do
+               var obj = new JsonObject
+               obj["status"] = status
+               obj["message"] = message
+               json(obj, status)
+       end
+end
index 3bdf2b0..2040c83 100644 (file)
@@ -37,6 +37,7 @@
 #
 # import popcorn
 # import popcorn::pop_repos
+# import popcorn::pop_json
 #
 # # Serializable book representation.
 # class Book
@@ -254,6 +255,7 @@ end
 # ~~~
 # import popcorn
 # import popcorn::pop_repos
+# import popcorn::pop_json
 #
 # # First, let's create a User abstraction:
 #
index f76c1e5..4aea76d 100644 (file)
@@ -83,6 +83,7 @@
 module pop_templates
 
 import popcorn::pop_handlers
+import popcorn::pop_json
 import template::macro
 
 redef class HttpResponse
index 048509e..9ecf5b7 100644 (file)
@@ -47,6 +47,7 @@ module pop_tracker
 import popcorn
 import popcorn::pop_config
 import popcorn::pop_logging
+import popcorn::pop_json
 import popcorn::pop_repos
 
 redef class AppConfig
index e95e517..ab92185 100644 (file)
@@ -24,6 +24,7 @@
 #
 # ~~~
 # import popcorn
+# import popcorn::pop_json
 # import serialization
 #
 # # Serializable book representation.
index e0c53af..3c5ae98 100644 (file)
@@ -508,3 +508,76 @@ class ConcurrentList[E]
                return value
        end
 end
+
+# A collection which `is_empty` method blocks until it's empty
+class ReverseBlockingQueue[E]
+       super ConcurrentList[E]
+
+       # Used to block or signal on waiting threads
+       private var cond = new PthreadCond
+
+       # Adding the signal to release eventual waiting thread(s)
+       redef fun push(e) do
+               mutex.lock
+               real_collection.push(e)
+               mutex.unlock
+       end
+
+       # When the Queue is empty, signal any possible waiting thread
+       redef fun remove(e) do
+               mutex.lock
+               real_collection.remove(e)
+               if real_collection.is_empty then cond.signal
+               mutex.unlock
+       end
+
+       # Wait until the Queue is empty
+       redef fun is_empty do
+               mutex.lock
+               while not real_collection.is_empty do self.cond.wait(mutex)
+               mutex.unlock
+               return true
+       end
+end
+
+# A Blocking queue implemented from a `ConcurrentList`
+# `shift` is blocking if there isn't any element in `self`
+# `push` or `unshift` releases every blocking threads
+class BlockingQueue[E]
+       super ConcurrentList[E]
+
+       # Used to block or signal on waiting threads
+       private var cond = new PthreadCond
+
+       # Adding the signal to release eventual waiting thread(s)
+       redef fun push(e) do
+               mutex.lock
+               real_collection.push(e)
+               self.cond.signal
+               real_collection.push(e)
+               mutex.unlock
+       end
+
+       redef fun unshift(e) do
+               mutex.lock
+               real_collection.unshift(e)
+               self.cond.signal
+               mutex.unlock
+       end
+
+       # If empty, blocks until an item is inserted with `push` or `unshift`
+       redef fun shift do
+               mutex.lock
+               while real_collection.is_empty do self.cond.wait(mutex)
+               var r = real_collection.shift
+               mutex.unlock
+               return r
+       end
+
+       redef fun is_empty do
+               mutex.lock
+               var r = real_collection.is_empty
+               mutex.unlock
+               return r
+       end
+end
index b67d390..eaf8153 100644 (file)
@@ -15,6 +15,7 @@
 # See `nit/lib/actors/actors.nit` for the abstraction on which the generated classes are based
 module actors_generation_phase
 
+import actors_injection_phase
 import modelize
 import gen_nit
 import modelbuilder
@@ -22,6 +23,8 @@ import modelbuilder
 private class ActorPhase
        super Phase
 
+       var generated_actor_modules = new Array[String]
+
        # Source code of the actor classes to generate
        var actors = new Array[String]
 
@@ -34,27 +37,14 @@ private class ActorPhase
        # Redefinitions of annotated classes
        var redef_classes = new Array[String]
 
-       redef fun process_annotated_node(nclass, nat)
-       do
-               if nat.n_atid.n_id.text != "actor" then return
-
-               if not nclass isa AStdClassdef then
-                       toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
-                       return
-               end
-
-               # Get the module associated with this class
-               var mclassdef = nclass.mclassdef
-               assert mclassdef != null
-
-               var mmod = mclassdef.mmodule
+       fun generate_actor_classes(mclassdef: MClassDef, mmod: MModule) do
                if not mmod.generate_actor_submodule then mmod.generate_actor_submodule = true
 
                # Get the name of the annotated class
                var classname = mclassdef.name
 
                # Generate the actor class
-               actors.add(
+               if mclassdef.is_intro then actors.add(
 """
 class Actor{{{classname}}}
        super Actor
@@ -64,7 +54,11 @@ end
                ######## Generate the Messages classes ########
 
                # Get all the methods definitions
-               var propdefs = mclassdef.mpropdefs
+               var propdefs = new Array[MPropDef]
+               for propdef in mclassdef.mpropdefs do
+                       if propdef.is_intro then propdefs.add(propdef)
+               end
+
                var methods = new Array[MMethodDef]
                for p in propdefs do
                        if p isa MMethodDef then
@@ -77,7 +71,8 @@ end
 
                # Generate the superclass for all Messages classes (each actor has its own Message super class)
                var msg_class_name = "Message" + classname
-               messages.add(
+
+               if mclassdef.is_intro then messages.add(
 """
 class {{{msg_class_name}}}
        super Message
@@ -173,12 +168,14 @@ end
                # All of the functions of the proxy too
 
                # Let's generate the proxy class then
+
+               var redef_virtual_type = ""
+               if mclassdef.is_intro then redef_virtual_type = "redef type E: Actor{classname}"
                proxys.add(
 """
 redef class Proxy{{{classname}}}
 
-       redef type E: Actor{{{classname}}}
-       #var actor: Actor{{{classname}}} is noinit
+       {{{redef_virtual_type}}}
 
        init proxy(base_class: {{{classname}}}) do
                actor = new Actor{{{classname}}}(base_class)
@@ -189,12 +186,46 @@ redef class Proxy{{{classname}}}
 end
 """)
 
-               redef_classes.add(
+       if mclassdef.is_intro then redef_classes.add(
 """
 redef class {{{classname}}}
-redef var async is lazy do return new Proxy{{{classname}}}.proxy(self)
+       var m = new Mutex
+       var lazy_proxy: Proxy{{{classname}}} is lazy do return new Proxy{{{classname}}}.proxy(self)
+
+       redef fun async: Proxy{{{classname}}} do
+               m.lock
+               var p = lazy_proxy
+               m.unlock
+               return p
+       end
 end
 """)
+
+       end
+
+       redef fun process_nmodule(nmodule) do
+               var mmod = nmodule.mmodule
+               if mmod == null then return
+
+               if generated_actor_modules.has(mmod.name) then return
+
+               var mclasses_defs = mmod.mclassdefs
+               for mclass_def in mclasses_defs do
+                       var mclass = mclass_def.mclass
+                       var actor = mclass.actor
+                       if actor != null then generate_actor_classes(mclass_def, mmod)
+               end
+
+       end
+
+       redef fun process_annotated_node(nclass, nat)
+       do
+               if nat.n_atid.n_id.text != "actor" then return
+
+               if not nclass isa AStdClassdef then
+                       toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
+                       return
+               end
        end
 
        redef fun process_nmodule_after(nmodule) do
@@ -226,6 +257,10 @@ end
                # for mmod in mmodules do nit_module.imports.add mmod.name
                nit_module.imports.add first_mmodule.name
 
+               generated_actor_modules.add(module_name)
+               var idx = generated_actor_modules.index_of(module_name)
+               for i in [0..idx[ do nit_module.imports.add(generated_actor_modules[i])
+
                nit_module.content.add "####################### Redef classes #########################"
                for c in redef_classes do nit_module.content.add( c + "\n\n" )
 
@@ -241,10 +276,10 @@ end
                # Write support module
                nit_module.write_to_file module_path
 
-               actors = new Array[String]
-               messages = new Array[String]
-               proxys = new Array[String]
-               redef_classes = new Array[String]
+               actors.clear
+               messages.clear
+               proxys.clear
+               redef_classes.clear
 
                toolcontext.modelbuilder.inject_module_subimportation(first_mmodule, module_path)
        end
@@ -257,5 +292,5 @@ end
 
 redef class ToolContext
        # Generate actors
-       var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase])
+       var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase, modelize_property_phase])
 end
index 25463ad..643679a 100644 (file)
@@ -350,7 +350,6 @@ redef class TypeVisitor
                if dcp.is_disabled then return res
 
                var anchor = self.anchor
-               assert anchor != null
                var supx = sup
                var subx = sub
                var p = node.parent.as(not null)
index b94ca13..5276632 100644 (file)
@@ -54,8 +54,8 @@ private class ATypeCounterVisitor
                if n isa AAnnotation then return
 
                if n isa AType then
-                       var mclassdef = self.nclassdef.mclassdef
-                       var mtype = modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, n)
+                       var mclassdef = self.nclassdef.mclassdef.as(not null)
+                       var mtype = modelbuilder.resolve_mtype(mclassdef, n)
                        if mtype != null then
                                self.typecount.inc(mtype)
                        end
index 21b182e..af53e3d 100644 (file)
@@ -2430,11 +2430,9 @@ abstract class MPropDef
                                        res.append "::"
                                end
                        end
-                       if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
-                               # precise "B" only if not the same class than "A"
-                               res.append mproperty.intro_mclassdef.name
-                               res.append "::"
-                       end
+                       # precise "B" because it is not the same class than "A"
+                       res.append mproperty.intro_mclassdef.name
+                       res.append "::"
                        # Always use the property name "x"
                        res.append mproperty.name
                end
@@ -2452,10 +2450,8 @@ abstract class MPropDef
                                res.append mproperty.intro_mclassdef.mmodule.c_name
                                res.append "__"
                        end
-                       if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
-                               res.append mproperty.intro_mclassdef.name.to_cmangle
-                               res.append "__"
-                       end
+                       res.append mproperty.intro_mclassdef.name.to_cmangle
+                       res.append "__"
                        res.append mproperty.name.to_cmangle
                end
                return res.to_s
index 77ba0d8..0db4b4b 100644 (file)
@@ -260,18 +260,50 @@ class ModelBuilder
        end
 
        # Return the static type associated to the node `ntype`.
-       # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
+       #
+       # `mclassdef` is the context where the call is made (used to understand
+       # formal types).
+       # In case of problem, an error is displayed on `ntype` and null is returned.
+       #
+       # Same as `resolve_mtype_unchecked3`, but get the context (module, class and
+       # anchor) from `mclassdef`.
+       #
+       # SEE: `resolve_mtype`
+       # SEE: `resolve_mtype3_unchecked`
+       #
+       # FIXME: Find a better name for this method.
+       fun resolve_mtype_unchecked(mclassdef: MClassDef, ntype: AType, with_virtual: Bool): nullable MType
+       do
+               return resolve_mtype3_unchecked(
+                       mclassdef.mmodule,
+                       mclassdef.mclass,
+                       mclassdef.bound_mtype,
+                       ntype,
+                       with_virtual
+               )
+       end
+
+       # Return the static type associated to the node `ntype`.
+       #
+       # `mmodule`, `mclass` and `anchor` compose the context where the call is
+       # made (used to understand formal types).
        # In case of problem, an error is displayed on `ntype` and null is returned.
-       # FIXME: the name "resolve_mtype" is awful
-       fun resolve_mtype_unchecked(mmodule: MModule, mclassdef: nullable MClassDef, ntype: AType, with_virtual: Bool): nullable MType
+       #
+       # Note: The “3” is for 3 contextual parameters.
+       #
+       # SEE: `resolve_mtype`
+       # SEE: `resolve_mtype_unchecked`
+       #
+       # FIXME: Find a better name for this method.
+       fun resolve_mtype3_unchecked(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, ntype: AType, with_virtual: Bool): nullable MType
        do
                var qid = ntype.n_qid
                var name = qid.n_id.text
                var res: MType
 
                # Check virtual type
-               if mclassdef != null and with_virtual then
-                       var prop = try_get_mproperty_by_name(ntype, mclassdef, name).as(nullable MVirtualTypeProp)
+               if anchor != null and with_virtual then
+                       var prop = try_get_mproperty_by_name2(ntype, mmodule, anchor, name).as(nullable MVirtualTypeProp)
                        if prop != null then
                                if not ntype.n_types.is_empty then
                                        error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
@@ -284,8 +316,8 @@ class ModelBuilder
                end
 
                # Check parameter type
-               if mclassdef != null then
-                       for p in mclassdef.mclass.mparameters do
+               if mclass != null then
+                       for p in mclass.mparameters do
                                if p.name != name then continue
 
                                if not ntype.n_types.is_empty then
@@ -300,32 +332,32 @@ class ModelBuilder
                end
 
                # Check class
-               var mclass = try_get_mclass_by_qid(qid, mmodule)
-               if mclass != null then
+               var found_class = try_get_mclass_by_qid(qid, mmodule)
+               if found_class != null then
                        var arity = ntype.n_types.length
-                       if arity != mclass.arity then
+                       if arity != found_class.arity then
                                if arity == 0 then
-                                       error(ntype, "Type Error: `{mclass.signature_to_s}` is a generic class.")
-                               else if mclass.arity == 0 then
+                                       error(ntype, "Type Error: `{found_class.signature_to_s}` is a generic class.")
+                               else if found_class.arity == 0 then
                                        error(ntype, "Type Error: `{name}` is not a generic class.")
                                else
-                                       error(ntype, "Type Error: expected {mclass.arity} formal argument(s) for `{mclass.signature_to_s}`; got {arity}.")
+                                       error(ntype, "Type Error: expected {found_class.arity} formal argument(s) for `{found_class.signature_to_s}`; got {arity}.")
                                end
                                return null
                        end
                        if arity == 0 then
-                               res = mclass.mclass_type
+                               res = found_class.mclass_type
                                if ntype.n_kwnullable != null then res = res.as_nullable
                                ntype.mtype = res
                                return res
                        else
                                var mtypes = new Array[MType]
                                for nt in ntype.n_types do
-                                       var mt = resolve_mtype_unchecked(mmodule, mclassdef, nt, with_virtual)
+                                       var mt = resolve_mtype3_unchecked(mmodule, mclass, anchor, nt, with_virtual)
                                        if mt == null then return null # Forward error
                                        mtypes.add(mt)
                                end
-                               res = mclass.get_mtype(mtypes)
+                               res = found_class.get_mtype(mtypes)
                                if ntype.n_kwnullable != null then res = res.as_nullable
                                ntype.mtype = res
                                return res
@@ -415,27 +447,56 @@ class ModelBuilder
        private var bad_class_names = new MultiHashMap[MModule, String]
 
        # Return the static type associated to the node `ntype`.
-       # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
+       #
+       # `mclassdef` is the context where the call is made (used to understand
+       # formal types).
+       # In case of problem, an error is displayed on `ntype` and null is returned.
+       #
+       # Same as `resolve_mtype3`, but get the context (module, class and ) from
+       # `mclassdef`.
+       #
+       # SEE: `resolve_mtype3`
+       # SEE: `resolve_mtype_unchecked`
+       #
+       # FIXME: Find a better name for this method.
+       fun resolve_mtype(mclassdef: MClassDef, ntype: AType): nullable MType
+       do
+               return resolve_mtype3(
+                       mclassdef.mmodule,
+                       mclassdef.mclass,
+                       mclassdef.bound_mtype,
+                       ntype
+               )
+       end
+
+       # Return the static type associated to the node `ntype`.
+       #
+       # `mmodule`, `mclass` and `anchor` compose the context where the call is
+       # made (used to understand formal types).
        # In case of problem, an error is displayed on `ntype` and null is returned.
-       # FIXME: the name "resolve_mtype" is awful
-       fun resolve_mtype(mmodule: MModule, mclassdef: nullable MClassDef, ntype: AType): nullable MType
+       #
+       # Note: The “3” is for 3 contextual parameters.
+       #
+       # SEE: `resolve_mtype`
+       # SEE: `resolve_mtype_unchecked`
+       #
+       # FIXME: Find a better name for this method.
+       fun resolve_mtype3(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, ntype: AType): nullable MType
        do
                var mtype = ntype.mtype
-               if mtype == null then mtype = resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
+               if mtype == null then mtype = resolve_mtype3_unchecked(mmodule, mclass, anchor, ntype, true)
                if mtype == null then return null # Forward error
 
                if ntype.checked_mtype then return mtype
                if mtype isa MGenericType then
-                       var mclass = mtype.mclass
-                       for i in [0..mclass.arity[ do
-                               var intro = mclass.try_intro
+                       var found_class = mtype.mclass
+                       for i in [0..found_class.arity[ do
+                               var intro = found_class.try_intro
                                if intro == null then return null # skip error
                                var bound = intro.bound_mtype.arguments[i]
                                var nt = ntype.n_types[i]
-                               var mt = resolve_mtype(mmodule, mclassdef, nt)
+                               var mt = resolve_mtype3(mmodule, mclass, anchor, nt)
                                if mt == null then return null # forward error
-                               var anchor
-                               if mclassdef != null then anchor = mclassdef.bound_mtype else anchor = null
                                if not check_subtype(nt, mmodule, anchor, mt, bound) then
                                        error(nt, "Type Error: expected `{bound}`, got `{mt}`.")
                                        return null
index 8164668..7580dde 100644 (file)
@@ -179,7 +179,7 @@ redef class ModelBuilder
                                end
                                var nfdt = nfd.n_type
                                if nfdt != null then
-                                       var bound = resolve_mtype_unchecked(mmodule, null, nfdt, false)
+                                       var bound = resolve_mtype3_unchecked(mmodule, null, null, nfdt, false)
                                        if bound == null then return # Forward error
                                        if bound.need_anchor then
                                                # No F-bounds!
@@ -188,9 +188,6 @@ redef class ModelBuilder
                                                bounds.add(bound)
                                                nfd.bound = bound
                                        end
-                                       if bound isa MClassType and bound.mclass.kind == enum_kind then
-                                               warning(nfdt, "useless-bound", "Warning: useless formal parameter type since `{bound}` cannot have subclasses.")
-                                       end
                                else if mclass.mclassdefs.is_empty then
                                        if objectclass == null then
                                                error(nfd, "Error: formal parameter type `{pname}` unbounded but no `Object` class exists.")
@@ -256,7 +253,7 @@ redef class ModelBuilder
                        for nsc in nclassdef.n_superclasses do
                                specobject = false
                                var ntype = nsc.n_type
-                               var mtype = resolve_mtype_unchecked(mmodule, mclassdef, ntype, false)
+                               var mtype = resolve_mtype_unchecked(mclassdef, ntype, false)
                                if mtype == null then continue # Skip because of error
                                if not mtype isa MClassType then
                                        error(ntype, "Error: supertypes cannot be a formal type.")
@@ -364,11 +361,21 @@ redef class ModelBuilder
                for nclassdef in nmodule.n_classdefs do
                        if nclassdef isa AStdClassdef then
                                var mclassdef = nclassdef.mclassdef
+                               var mclass
+                               var anchor
+                               if mclassdef == null then
+                                       mclass = null
+                                       anchor = null
+                               else
+                                       mclass = mclassdef.mclass
+                                       anchor = mclassdef.bound_mtype
+                               end
+
                                # check bound of formal parameter
-                               for nfd in  nclassdef.n_formaldefs do
+                               for nfd in nclassdef.n_formaldefs do
                                        var nfdt = nfd.n_type
                                        if nfdt != null and nfdt.mtype != null then
-                                               var bound = resolve_mtype(mmodule, mclassdef, nfdt)
+                                               var bound = resolve_mtype3(mmodule, mclass, anchor, nfdt)
                                                if bound == null then return # Forward error
                                        end
                                end
@@ -376,7 +383,7 @@ redef class ModelBuilder
                                for nsc in nclassdef.n_superclasses do
                                        var ntype = nsc.n_type
                                        if ntype.mtype != null then
-                                               var mtype = resolve_mtype(mmodule, mclassdef, ntype)
+                                               var mtype = resolve_mtype3(mmodule, mclass, anchor, ntype)
                                                if mtype == null then return # Forward error
                                        end
                                end
index 44d551d..a7f75ff 100644 (file)
@@ -671,14 +671,13 @@ redef class ASignature
        # Visit and fill information about a signature
        private fun visit_signature(modelbuilder: ModelBuilder, mclassdef: MClassDef): Bool
        do
-               var mmodule = mclassdef.mmodule
                var param_names = self.param_names
                var param_types = self.param_types
                for np in self.n_params do
                        param_names.add(np.n_id.text)
                        var ntype = np.n_type
                        if ntype != null then
-                               var mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
+                               var mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, ntype, true)
                                if mtype == null then return false # Skip error
                                for i in [0..param_names.length-param_types.length[ do
                                        param_types.add(mtype)
@@ -695,7 +694,7 @@ redef class ASignature
                end
                var ntype = self.n_type
                if ntype != null then
-                       self.ret_type = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
+                       self.ret_type = modelbuilder.resolve_mtype_unchecked(mclassdef, ntype, true)
                        if self.ret_type == null then return false # Skip error
                end
 
@@ -709,14 +708,14 @@ redef class ASignature
                for np in self.n_params do
                        var ntype = np.n_type
                        if ntype != null then
-                               if modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, ntype) == null then
+                               if modelbuilder.resolve_mtype(mclassdef, ntype) == null then
                                        res = false
                                end
                        end
                end
                var ntype = self.n_type
                if ntype != null then
-                       if modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, ntype) == null then
+                       if modelbuilder.resolve_mtype(mclassdef, ntype) == null then
                                res = false
                        end
                end
@@ -1348,7 +1347,7 @@ redef class AAttrPropdef
 
                var ntype = self.n_type
                if ntype != null then
-                       mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
+                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, ntype, true)
                        if mtype == null then return
                end
 
@@ -1369,9 +1368,9 @@ redef class AAttrPropdef
                if mtype == null then
                        if nexpr != null then
                                if nexpr isa ANewExpr then
-                                       mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
+                                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
                                else if nexpr isa AAsCastExpr then
-                                       mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
+                                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
                                else if nexpr isa AIntegerExpr then
                                        var cla: nullable MClass = null
                                        if nexpr.value isa Int then
@@ -1432,7 +1431,7 @@ redef class AAttrPropdef
                        end
                else if ntype != null and inherited_type == mtype then
                        if nexpr isa ANewExpr then
-                               var xmtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
+                               var xmtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
                                if xmtype == mtype then
                                        modelbuilder.advice(ntype, "useless-type", "Warning: useless type definition")
                                end
@@ -1488,11 +1487,11 @@ redef class AAttrPropdef
 
                # Check types
                if ntype != null then
-                       if modelbuilder.resolve_mtype(mmodule, mclassdef, ntype) == null then return
+                       if modelbuilder.resolve_mtype(mclassdef, ntype) == null then return
                end
                var nexpr = n_expr
                if nexpr isa ANewExpr then
-                       if modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type) == null then return
+                       if modelbuilder.resolve_mtype(mclassdef, nexpr.n_type) == null then return
                end
 
                # Lookup for signature in the precursor
@@ -1645,11 +1644,10 @@ redef class ATypePropdef
                var mpropdef = self.mpropdef
                if mpropdef == null then return # Error thus skipped
                var mclassdef = mpropdef.mclassdef
-               var mmodule = mclassdef.mmodule
                var mtype: nullable MType = null
 
                var ntype = self.n_type
-               mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
+               mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, ntype, true)
                if mtype == null then return
 
                mpropdef.bound = mtype
@@ -1671,7 +1669,7 @@ redef class ATypePropdef
                var anchor = mclassdef.bound_mtype
 
                var ntype = self.n_type
-               if modelbuilder.resolve_mtype(mmodule, mclassdef, ntype) == null then
+               if modelbuilder.resolve_mtype(mclassdef, ntype) == null then
                        mpropdef.bound = null
                        return
                end
index 823f624..6d05196 100644 (file)
@@ -304,7 +304,7 @@ redef class AFullPropExternCall
                var mmodule = npropdef.mpropdef.mclassdef.mmodule
                var mclassdef = npropdef.mpropdef.mclassdef
                var mclass_type = mclassdef.bound_mtype
-               var mtype = toolcontext.modelbuilder.resolve_mtype(mmodule, mclassdef, n_type)
+               var mtype = toolcontext.modelbuilder.resolve_mtype(mclassdef, n_type)
 
                if mtype == null then return
 
@@ -337,7 +337,7 @@ redef class AInitPropExternCall
        do
                var mmodule = npropdef.mpropdef.mclassdef.mmodule
                var mclassdef = npropdef.mpropdef.mclassdef
-               var mtype = toolcontext.modelbuilder.resolve_mtype(mmodule, mclassdef, n_type)
+               var mtype = toolcontext.modelbuilder.resolve_mtype(mclassdef, n_type)
                if mtype == null then return
 
                if not mtype isa MClassType then
@@ -398,9 +398,8 @@ redef class ACastAsExternCall
        redef fun verify_and_collect(npropdef, callback_set, toolcontext)
        do
                var mclassdef = npropdef.mpropdef.mclassdef
-               var mmodule = mclassdef.mmodule
-               toolcontext.modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, n_from_type, true)
-               toolcontext.modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, n_to_type, true)
+               toolcontext.modelbuilder.resolve_mtype_unchecked(mclassdef, n_from_type, true)
+               toolcontext.modelbuilder.resolve_mtype_unchecked(mclassdef, n_to_type, true)
                super
        end
 end
@@ -412,8 +411,7 @@ redef class AAsNullableExternCall
        redef fun verify_and_collect(npropdef, callback_set, toolcontext)
        do
                var mclassdef = npropdef.mpropdef.mclassdef
-               var mmodule = mclassdef.mmodule
-               toolcontext.modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, n_type, true)
+               toolcontext.modelbuilder.resolve_mtype_unchecked(mclassdef, n_type, true)
                super
        end
 end
@@ -429,8 +427,7 @@ redef class AAsNotNullableExternCall
        redef fun verify_and_collect(npropdef, callback_set, toolcontext)
        do
                var mclassdef = npropdef.mpropdef.mclassdef
-               var mmodule = mclassdef.mmodule
-               toolcontext.modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, n_type, true)
+               toolcontext.modelbuilder.resolve_mtype_unchecked(mclassdef, n_type, true)
                super
        end
 end
index bcbacb3..dc3b1f7 100644 (file)
@@ -22,7 +22,7 @@ private import console
 
 # Root of the AST class-hierarchy
 abstract class ANode
-       # Location is set during AST building. Once built, location cannon be null.
+       # Location is set during AST building. Once built, location can not be null.
        # However, manual instantiated nodes may need more care.
        var location: Location is writable, noinit
 
@@ -534,7 +534,7 @@ class TKwinterface
        super TokenKeyword
 end
 
-# The keywords `enum` ane `universal`
+# The keywords `enum` and `universal`
 class TKwenum
        super TokenKeyword
 end
@@ -3023,7 +3023,8 @@ abstract class AAtid
        super Prod
 
        # The identifier of the annotation.
-       # Can be a TId of a keyword
+       #
+       # Can be a TId or a keyword.
        var n_id: Token is writable, noinit
 end
 
index 5340c14..8890ba9 100644 (file)
@@ -36,17 +36,17 @@ private class TypeVisitor
 
        # The module of the analysis
        # Used to correctly query the model
-       var mmodule: MModule
+       var mmodule: MModule is noinit
 
        # The static type of the receiver
        # Mainly used for type tests and type resolutions
-       var anchor: nullable MClassType = null
+       var anchor: MClassType is noinit
 
        # The analyzed mclassdef
-       var mclassdef: nullable MClassDef = null
+       var mclassdef: MClassDef is noinit
 
        # The analyzed property
-       var mpropdef: nullable MPropDef
+       var mpropdef: MPropDef
 
        var selfvariable = new Variable("self")
 
@@ -59,23 +59,20 @@ private class TypeVisitor
        init
        do
                var mpropdef = self.mpropdef
+               var mclassdef = mpropdef.mclassdef
+               mmodule = mclassdef.mmodule
+               self.mclassdef = mclassdef
+               self.anchor = mclassdef.bound_mtype
 
-               if mpropdef != null then
-                       self.mpropdef = mpropdef
-                       var mclassdef = mpropdef.mclassdef
-                       self.mclassdef = mclassdef
-                       self.anchor = mclassdef.bound_mtype
-
-                       var mclass = mclassdef.mclass
+               var mclass = mclassdef.mclass
 
-                       var selfvariable = new Variable("self")
-                       self.selfvariable = selfvariable
-                       selfvariable.declared_type = mclass.mclass_type
+               var selfvariable = new Variable("self")
+               self.selfvariable = selfvariable
+               selfvariable.declared_type = mclass.mclass_type
 
-                       var mprop = mpropdef.mproperty
-                       if mprop isa MMethod and mprop.is_new then
-                               is_toplevel_context = true
-                       end
+               var mprop = mpropdef.mproperty
+               if mprop isa MMethod and mprop.is_new then
+                       is_toplevel_context = true
                end
        end
 
@@ -278,7 +275,7 @@ private class TypeVisitor
 
        fun resolve_mtype(node: AType): nullable MType
        do
-               return self.modelbuilder.resolve_mtype(mmodule, mclassdef, node)
+               return self.modelbuilder.resolve_mtype(mclassdef, node)
        end
 
        fun try_get_mclass(node: ANode, name: String): nullable MClass
@@ -864,7 +861,7 @@ redef class AMethPropdef
                var mpropdef = self.mpropdef
                if mpropdef == null then return # skip error
 
-               var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
+               var v = new TypeVisitor(modelbuilder, mpropdef)
                self.selfvariable = v.selfvariable
 
                var mmethoddef = self.mpropdef.as(not null)
@@ -931,7 +928,7 @@ redef class AAttrPropdef
                var mpropdef = self.mreadpropdef
                if mpropdef == null or mpropdef.msignature == null then return # skip error
 
-               var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
+               var v = new TypeVisitor(modelbuilder, mpropdef)
                self.selfvariable = v.selfvariable
 
                var nexpr = self.n_expr
@@ -2142,7 +2139,6 @@ redef class ASuperExpr
        redef fun accept_typing(v)
        do
                var anchor = v.anchor
-               assert anchor != null
                var recvtype = v.get_variable(self, v.selfvariable)
                assert recvtype != null
                var mproperty = v.mpropdef.mproperty
@@ -2181,7 +2177,6 @@ redef class ASuperExpr
        private fun process_superinit(v: TypeVisitor)
        do
                var anchor = v.anchor
-               assert anchor != null
                var recvtype = v.get_variable(self, v.selfvariable)
                assert recvtype != null
                var mpropdef = v.mpropdef
index 8062768..265cadf 100644 (file)
@@ -21,6 +21,7 @@ import doc_down
 import popcorn
 import popcorn::pop_config
 import popcorn::pop_repos
+import popcorn::pop_json
 
 # Nitweb config file.
 class NitwebConfig
diff --git a/tests/sav/base_gen_final_bound.res b/tests/sav/base_gen_final_bound.res
deleted file mode 100644 (file)
index a0d7579..0000000
+++ /dev/null
@@ -1 +0,0 @@
-base_gen_final_bound.nit:23,16--18: Warning: useless formal parameter type since `Int` cannot have subclasses.
index 31fe7ed..ef0f1c9 100644 (file)
@@ -1,2 +1 @@
-alt/error_class_generic_alt2.nit:18,22--26: Warning: useless formal parameter type since `Float` cannot have subclasses.
 alt/error_class_generic_alt2.nit:25,8--12: Type Error: `Array[E: nullable Object]` is a generic class.
index a1e0a96..b48ebc3 100644 (file)
@@ -1,5 +1,3 @@
-test_gen_inh.nit:29,15--17: Warning: useless formal parameter type since `Int` cannot have subclasses.
-test_gen_inh.nit:34,15--17: Warning: useless formal parameter type since `Int` cannot have subclasses.
 11
 22
 33
index 78c1dcc..be0358a 100644 (file)
@@ -1,4 +1,3 @@
-test_super_gen.nit:27,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
 1
 0
 5
index 1fc76c7..b2f1f1e 100644 (file)
@@ -1,4 +1,2 @@
-test_super_gen.nit:27,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
-test_super_gen_raf.nit:19,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
 0
 20