Merge: doc: fixed some typos and other misc. corrections
[nit.git] / src / frontend / actors_generation_phase.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
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.
13
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
17
18 import actors_injection_phase
19 import modelize
20 import gen_nit
21 import modelbuilder
22
23 private class ActorPhase
24 super Phase
25
26 var generated_actor_modules = new Array[String]
27
28 # Source code of the actor classes to generate
29 var actors = new Array[String]
30
31 # Source code of the message classes to generate
32 var messages = new Array[String]
33
34 # Source code of the proxy classes to generate
35 var proxys = new Array[String]
36
37 # Redefinitions of annotated classes
38 var redef_classes = new Array[String]
39
40 fun generate_actor_classes(mclassdef: MClassDef, mmod: MModule) do
41 if not mmod.generate_actor_submodule then mmod.generate_actor_submodule = true
42
43 # Get the name of the annotated class
44 var classname = mclassdef.name
45
46 # Generate the actor class
47 if mclassdef.is_intro then actors.add(
48 """
49 class Actor{{{classname}}}
50 super Actor
51 redef type E: nullable {{{classname}}}
52 end
53 """)
54 ######## Generate the Messages classes ########
55
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)
60 end
61
62 var methods = new Array[MMethodDef]
63 for p in propdefs do
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
68 methods.add(p)
69 end
70 end
71
72 # Generate the superclass for all Messages classes (each actor has its own Message super class)
73 var msg_class_name = "Message" + classname
74
75 if mclassdef.is_intro then messages.add(
76 """
77 class {{{msg_class_name}}}
78 super Message
79 redef type E: {{{classname}}}
80 end
81 """)
82 # Generate every Message class based on the methods of the annotated class
83 var proxy_calls = new Array[String]
84 for m in methods do
85 # Signature of the method
86 var signature = m.msignature
87
88 # Name of the method
89 var method_name = m.name
90
91 # Attributes of the `Message` class if needed
92 # Corresponds to the parameters of the proxied method
93 var msg_attributes = new Array[String]
94
95 # Signature of the proxy corresponding method
96 var proxy_sign = ""
97
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 = ""
102
103 # Params to send to `instance` in the `invoke` method
104 var params = ""
105
106 # Values for the generated proxy method
107 var return_signature = ""
108 var return_statement = ""
109
110 if signature != null then
111 var proxy_params= new Array[String]
112
113 # Deal with parameters
114 var mparameters = signature.mparameters
115 if mparameters.length > 0 then
116 var parameters = new Array[String]
117 proxy_sign += "("
118 for p in mparameters do
119 var n = p.name
120 var t = p.mtype.name
121 msg_attributes.add("var " + n + ": " + t)
122 proxy_params.add(n + ": " + t)
123 parameters.add(n)
124 end
125 proxy_sign += proxy_params.join(", ") + ")"
126 params = "(" + parameters.join(", ") + ")"
127 end
128
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"
137 end
138 end
139
140 # Name of the Message class
141 var name = classname + "Message" + method_name
142
143 # The effective Message Class
144 messages.add(
145 """
146 class {{{name}}}
147 super {{{msg_class_name}}}
148
149 {{{msg_attributes.join("\n")}}}
150
151 redef fun invoke(instance) do {{{return_value}}}instance.{{{method_name}}}{{{params}}}{{{return_parenthesis}}}
152 end
153 """)
154
155
156 # The actual proxy call
157 proxy_calls.add(
158 """
159 redef fun {{{method_name}}}{{{proxy_sign}}}{{{return_signature}}} do
160 var msg = new {{{name}}}{{{params}}}
161 actor.mailbox.push(msg)
162 {{{return_statement}}}
163 end
164 """)
165 end
166
167 # At this point, all msg classes should be good
168 # All of the functions of the proxy too
169
170 # Let's generate the proxy class then
171
172 var redef_virtual_type = ""
173 if mclassdef.is_intro then redef_virtual_type = "redef type E: Actor{classname}"
174 proxys.add(
175 """
176 redef class Proxy{{{classname}}}
177
178 {{{redef_virtual_type}}}
179
180 init proxy(base_class: {{{classname}}}) do
181 actor = new Actor{{{classname}}}(base_class)
182 actor.start
183 end
184
185 {{{proxy_calls.join("\n\n")}}}
186 end
187 """)
188
189 if mclassdef.is_intro then redef_classes.add(
190 """
191 redef class {{{classname}}}
192 var m = new Mutex
193 var lazy_proxy: Proxy{{{classname}}} is lazy do return new Proxy{{{classname}}}.proxy(self)
194
195 redef fun async: Proxy{{{classname}}} do
196 m.lock
197 var p = lazy_proxy
198 m.unlock
199 return p
200 end
201 end
202 """)
203
204 end
205
206 redef fun process_nmodule(nmodule) do
207 var mmod = nmodule.mmodule
208 if mmod == null then return
209
210 if generated_actor_modules.has(mmod.name) then return
211
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)
217 end
218
219 end
220
221 redef fun process_annotated_node(nclass, nat)
222 do
223 if nat.n_atid.n_id.text != "actor" then return
224
225 if not nclass isa AStdClassdef then
226 toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
227 return
228 end
229 end
230
231 redef fun process_nmodule_after(nmodule) do
232 var first_mmodule = nmodule.mmodule
233 if first_mmodule == null then return
234
235 # Be careful not to generate useless submodules !
236 if not first_mmodule.generate_actor_submodule then return
237
238 # Name of the support module
239 var module_name
240
241 # Path to the support module
242 module_name = "actors_{first_mmodule.name}"
243
244 # We assume a module using actors has a `filepath` not null
245 var mmodule_path = first_mmodule.filepath.as(not null).dirname
246
247 var module_path = "{mmodule_path}/{module_name}.nit"
248
249 var nit_module = new NitModule(module_name)
250 nit_module.annotations.add "no_warning(\"missing-doc\")"
251
252 nit_module.header = """
253 # This file is generated by nitactors (threaded version)
254 # Do not modify, instead use the generated services.
255 """
256
257 # for mmod in mmodules do nit_module.imports.add mmod.name
258 nit_module.imports.add first_mmodule.name
259
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])
263
264 nit_module.content.add "####################### Redef classes #########################"
265 for c in redef_classes do nit_module.content.add( c + "\n\n" )
266
267 nit_module.content.add "####################### Actor classes #########################"
268 for c in actors do nit_module.content.add( c + "\n\n" )
269
270 nit_module.content.add "####################### Messages classes ######################"
271 for c in messages do nit_module.content.add( c + "\n\n" )
272
273 nit_module.content.add "####################### Proxy classes #########################"
274 for c in proxys do nit_module.content.add( c + "\n\n" )
275
276 # Write support module
277 nit_module.write_to_file module_path
278
279 actors.clear
280 messages.clear
281 proxys.clear
282 redef_classes.clear
283
284 toolcontext.modelbuilder.inject_module_subimportation(first_mmodule, module_path)
285 end
286 end
287
288 redef class MModule
289 # Do we need to generate the actor submodule ?
290 var generate_actor_submodule = false
291 end
292
293 redef class ToolContext
294 # Generate actors
295 var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase, modelize_property_phase])
296 end