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>
# 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
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
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
# 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
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]
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
module shibuqamoauth
import popcorn
+import popcorn::pop_json
import shibuqam
import json
Ni 1.000000
d 1.000000
illum 2
-map_Kd textures/tread.jpg
+map_Kd textures/TTread.jpg
newmtl Treads
Ns 178.431373
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
# 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
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
# 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
# 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
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
# 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
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
# 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
client.writer.serialize game
client.writer.serialize client.player
client.socket.flush
-
- clients.add client
end
if dedicated and clients.is_empty then
new_annotation actor
end
-import pthreads::concurrent_collections
+intrude import pthreads::concurrent_collections
intrude import pthreads
intrude import pthreads::extra
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
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`
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
--- /dev/null
+# 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
--- /dev/null
+# 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
for c in creatures do c.async.run
- active_actors.is_empty
+ active_actors.wait
var total = 0
for c in creatures do
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')
# 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
# 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.
# 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
# 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
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
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)
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)
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 = """
// 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
// 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;
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
# 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
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;
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
# 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
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
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
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
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
# 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`
# 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]
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
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
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
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
if material == null then material = new Material
var model = new LeafModel(mesh, material)
- array.add model
+ leaves.add model
end
end
# 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]
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
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
# 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
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
# 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
# ~~~
module client
-import common
+intrude import common
# Information of the remove server
class RemoteServerConfig
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
#
# 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
# 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!"
# 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
#
# # `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
# 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]
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
# 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
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
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
--- /dev/null
+# 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
--- /dev/null
+# 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
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
# limitations under the License.
import popcorn
+import popcorn::pop_json
class CounterAPI
super Handler
# ~~~
module pop_auth
+import pop_json
import pop_sessions
import github
module pop_handlers
import pop_routes
-import json::static
-import json
import csv
# Class handler for a route.
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)
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
--- /dev/null
+# 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
#
# import popcorn
# import popcorn::pop_repos
+# import popcorn::pop_json
#
# # Serializable book representation.
# class Book
# ~~~
# import popcorn
# import popcorn::pop_repos
+# import popcorn::pop_json
#
# # First, let's create a User abstraction:
#
module pop_templates
import popcorn::pop_handlers
+import popcorn::pop_json
import template::macro
redef class HttpResponse
import popcorn
import popcorn::pop_config
import popcorn::pop_logging
+import popcorn::pop_json
import popcorn::pop_repos
redef class AppConfig
#
# ~~~
# import popcorn
+# import popcorn::pop_json
# import serialization
#
# # Serializable book representation.
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
# 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
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]
# 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
######## 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
# 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
# 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)
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
# 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" )
# 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
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
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)
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
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
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
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.")
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
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
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
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!
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.")
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.")
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
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
# 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)
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
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
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
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
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
# 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
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
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
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
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
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
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
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
# 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
super TokenKeyword
end
-# The keywords `enum` ane `universal`
+# The keywords `enum` and `universal`
class TKwenum
super TokenKeyword
end
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
# 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")
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
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
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)
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
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
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
import popcorn
import popcorn::pop_config
import popcorn::pop_repos
+import popcorn::pop_json
# Nitweb config file.
class NitwebConfig
+++ /dev/null
-base_gen_final_bound.nit:23,16--18: Warning: useless formal parameter type since `Int` cannot have subclasses.
-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.
-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
-test_super_gen.nit:27,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
1
0
5
-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