1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 # Generate a support module for each module that contain a class annotated with `is actor`
15 # See `nit/lib/actors/actors.nit` for the abstraction on which the generated classes are based
16 module actors_generation_phase
18 import actors_injection_phase
23 private class ActorPhase
26 var generated_actor_modules
= new Array[String]
28 # Source code of the actor classes to generate
29 var actors
= new Array[String]
31 # Source code of the message classes to generate
32 var messages
= new Array[String]
34 # Source code of the proxy classes to generate
35 var proxys
= new Array[String]
37 # Redefinitions of annotated classes
38 var redef_classes
= new Array[String]
40 fun generate_actor_classes
(mclassdef
: MClassDef, mmod
: MModule) do
41 if not mmod
.generate_actor_submodule
then mmod
.generate_actor_submodule
= true
43 # Get the name of the annotated class
44 var classname
= mclassdef
.name
46 # Generate the actor class
47 if mclassdef
.is_intro
then actors
.add
(
49 class Actor{{{classname}}}
51 redef type E: nullable {{{classname}}}
54 ######## Generate the Messages classes ########
56 # Get all the methods definitions
57 var propdefs
= new Array[MPropDef]
58 for propdef
in mclassdef
.mpropdefs
do
59 if propdef
.is_intro
then propdefs
.add
(propdef
)
62 var methods
= new Array[MMethodDef]
64 if p
isa MMethodDef then
65 # TODO: find a better way to exclude constructors,
66 # getters/setters and the "async" (the actor)
67 if p
.name
.has
("=") or p
.name
.has
("async") or p
.mproperty
.is_init
then continue
72 # Generate the superclass for all Messages classes (each actor has its own Message super class)
73 var msg_class_name
= "Message" + classname
75 if mclassdef
.is_intro
then messages
.add
(
77 class {{{msg_class_name}}}
79 redef type E: {{{classname}}}
82 # Generate every Message class based on the methods of the annotated class
83 var proxy_calls
= new Array[String]
85 # Signature of the method
86 var signature
= m
.msignature
89 var method_name
= m
.name
91 # Attributes of the `Message` class if needed
92 # Corresponds to the parameters of the proxied method
93 var msg_attributes
= new Array[String]
95 # Signature of the proxy corresponding method
98 # Values for the body of the `invoke` method of the generated Message class
99 # Used if the call must return a value
100 var return_value
= ""
101 var return_parenthesis
= ""
103 # Params to send to `instance` in the `invoke` method
106 # Values for the generated proxy method
107 var return_signature
= ""
108 var return_statement
= ""
110 if signature
!= null then
111 var proxy_params
= new Array[String]
113 # Deal with parameters
114 var mparameters
= signature
.mparameters
115 if mparameters
.length
> 0 then
116 var parameters
= new Array[String]
118 for p
in mparameters
do
121 msg_attributes
.add
("var " + n
+ ": " + t
)
122 proxy_params
.add
(n
+ ": " + t
)
125 proxy_sign
+= proxy_params
.join
(", ") + ")"
126 params
= "(" + parameters
.join
(", ") + ")"
129 # Deal with the return if any
130 var ret_type
= signature
.return_mtype
131 if ret_type
!= null then
132 msg_attributes
.add
("var ret = new Future[{ret_type.name}]")
133 return_value
= "ret.set_value("
134 return_parenthesis
= ")"
135 return_signature
= ": Future[{ret_type.name}]"
136 return_statement
= "return msg.ret"
140 # Name of the Message class
141 var name
= classname
+ "Message" + method_name
143 # The effective Message Class
147 super {{{msg_class_name}}}
149 {{{msg_attributes.join("\n")}}}
151 redef fun invoke(instance) do {{{return_value}}}instance.{{{method_name}}}{{{params}}}{{{return_parenthesis}}}
156 # The actual proxy call
159 redef fun {{{method_name}}}{{{proxy_sign}}}{{{return_signature}}} do
160 var msg = new {{{name}}}{{{params}}}
161 actor.mailbox.push(msg)
162 {{{return_statement}}}
167 # At this point, all msg classes should be good
168 # All of the functions of the proxy too
170 # Let's generate the proxy class then
172 var redef_virtual_type
= ""
173 if mclassdef
.is_intro
then redef_virtual_type
= "redef type E: Actor{classname}"
176 redef class Proxy{{{classname}}}
178 {{{redef_virtual_type}}}
180 init proxy(base_class: {{{classname}}}) do
181 actor = new Actor{{{classname}}}(base_class)
185 {{{proxy_calls.join("\n\n")}}}
189 if mclassdef
.is_intro
then redef_classes
.add
(
191 redef class {{{classname}}}
193 var lazy_proxy: Proxy{{{classname}}} is lazy do return new Proxy{{{classname}}}.proxy(self)
195 redef fun async: Proxy{{{classname}}} do
206 redef fun process_nmodule
(nmodule
) do
207 var mmod
= nmodule
.mmodule
208 if mmod
== null then return
210 if generated_actor_modules
.has
(mmod
.name
) then return
212 var mclasses_defs
= mmod
.mclassdefs
213 for mclass_def
in mclasses_defs
do
214 var mclass
= mclass_def
.mclass
215 var actor
= mclass
.actor
216 if actor
!= null then generate_actor_classes
(mclass_def
, mmod
)
221 redef fun process_annotated_node
(nclass
, nat
)
223 if nat
.n_atid
.n_id
.text
!= "actor" then return
225 if not nclass
isa AStdClassdef then
226 toolcontext
.error
(nat
.location
, "Syntax Error: only a class can be annotated as an actor.")
231 redef fun process_nmodule_after
(nmodule
) do
232 var first_mmodule
= nmodule
.mmodule
233 if first_mmodule
== null then return
235 # Be careful not to generate useless submodules !
236 if not first_mmodule
.generate_actor_submodule
then return
238 # Name of the support module
241 # Path to the support module
242 module_name
= "actors_{first_mmodule.name}"
244 # We assume a module using actors has a `filepath` not null
245 var mmodule_path
= first_mmodule
.filepath
.as(not null).dirname
247 var module_path
= "{mmodule_path}/{module_name}.nit"
249 var nit_module
= new NitModule(module_name
)
250 nit_module
.annotations
.add
"no_warning(\"missing-doc\
")"
252 nit_module
.header
= """
253 # This file is generated by nitactors (threaded version)
254 # Do not modify, instead use the generated services.
257 # for mmod in mmodules do nit_module.imports.add mmod.name
258 nit_module
.imports
.add first_mmodule
.name
260 generated_actor_modules
.add
(module_name
)
261 var idx
= generated_actor_modules
.index_of
(module_name
)
262 for i
in [0..idx
[ do nit_module
.imports
.add
(generated_actor_modules
[i
])
264 nit_module
.content
.add
"####################### Redef classes #########################"
265 for c
in redef_classes
do nit_module
.content
.add
( c
+ "\n\n" )
267 nit_module
.content
.add
"####################### Actor classes #########################"
268 for c
in actors
do nit_module
.content
.add
( c
+ "\n\n" )
270 nit_module
.content
.add
"####################### Messages classes ######################"
271 for c
in messages
do nit_module
.content
.add
( c
+ "\n\n" )
273 nit_module
.content
.add
"####################### Proxy classes #########################"
274 for c
in proxys
do nit_module
.content
.add
( c
+ "\n\n" )
276 # Write support module
277 nit_module
.write_to_file module_path
284 toolcontext
.modelbuilder
.inject_module_subimportation
(first_mmodule
, module_path
)
289 # Do we need to generate the actor submodule ?
290 var generate_actor_submodule
= false
293 redef class ToolContext
295 var actor_phase
: Phase = new ActorPhase(self, [modelize_class_phase
, modelize_property_phase
])