Merge: Nit Actor Model, with some examples
[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 modelize
19 import gen_nit
20 import modelbuilder
21
22 private class ActorPhase
23 super Phase
24
25 # Source code of the actor classes to generate
26 var actors = new Array[String]
27
28 # Source code of the message classes to generate
29 var messages = new Array[String]
30
31 # Source code of the proxy classes to generate
32 var proxys = new Array[String]
33
34 # Redefinitions of annotated classes
35 var redef_classes = new Array[String]
36
37 redef fun process_annotated_node(nclass, nat)
38 do
39 if nat.n_atid.n_id.text != "actor" then return
40
41 if not nclass isa AStdClassdef then
42 toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
43 return
44 end
45
46 # Get the module associated with this class
47 var mclassdef = nclass.mclassdef
48 assert mclassdef != null
49
50 var mmod = mclassdef.mmodule
51 if not mmod.generate_actor_submodule then mmod.generate_actor_submodule = true
52
53 # Get the name of the annotated class
54 var classname = mclassdef.name
55
56 # Generate the actor class
57 actors.add(
58 """
59 class Actor{{{classname}}}
60 super Actor
61 redef type E: nullable {{{classname}}}
62 end
63 """)
64 ######## Generate the Messages classes ########
65
66 # Get all the methods definitions
67 var propdefs = mclassdef.mpropdefs
68 var methods = new Array[MMethodDef]
69 for p in propdefs do
70 if p isa MMethodDef then
71 # TODO: find a better way to exclude constructors,
72 # getters/setters and the "async" (the actor)
73 if p.name.has("=") or p.name.has("async") or p.mproperty.is_init then continue
74 methods.add(p)
75 end
76 end
77
78 # Generate the superclass for all Messages classes (each actor has its own Message super class)
79 var msg_class_name = "Message" + classname
80 messages.add(
81 """
82 class {{{msg_class_name}}}
83 super Message
84 redef type E: {{{classname}}}
85 end
86 """)
87 # Generate every Message class based on the methods of the annotated class
88 var proxy_calls = new Array[String]
89 for m in methods do
90 # Signature of the method
91 var signature = m.msignature
92
93 # Name of the method
94 var method_name = m.name
95
96 # Attributes of the `Message` class if needed
97 # Corresponds to the parameters of the proxied method
98 var msg_attributes = new Array[String]
99
100 # Signature of the proxy corresponding method
101 var proxy_sign = ""
102
103 # Values for the body of the `invoke` method of the generated Message class
104 # Used if the call must return a value
105 var return_value = ""
106 var return_parenthesis = ""
107
108 # Params to send to `instance` in the `invoke` method
109 var params = ""
110
111 # Values for the generated proxy method
112 var return_signature = ""
113 var return_statement = ""
114
115 if signature != null then
116 var proxy_params= new Array[String]
117
118 # Deal with parameters
119 var mparameters = signature.mparameters
120 if mparameters.length > 0 then
121 var parameters = new Array[String]
122 proxy_sign += "("
123 for p in mparameters do
124 var n = p.name
125 var t = p.mtype.name
126 msg_attributes.add("var " + n + ": " + t)
127 proxy_params.add(n + ": " + t)
128 parameters.add(n)
129 end
130 proxy_sign += proxy_params.join(", ") + ")"
131 params = "(" + parameters.join(", ") + ")"
132 end
133
134 # Deal with the return if any
135 var ret_type = signature.return_mtype
136 if ret_type != null then
137 msg_attributes.add("var ret = new Future[{ret_type.name}]")
138 return_value = "ret.set_value("
139 return_parenthesis = ")"
140 return_signature = ": Future[{ret_type.name}]"
141 return_statement = "return msg.ret"
142 end
143 end
144
145 # Name of the Message class
146 var name = classname + "Message" + method_name
147
148 # The effective Message Class
149 messages.add(
150 """
151 class {{{name}}}
152 super {{{msg_class_name}}}
153
154 {{{msg_attributes.join("\n")}}}
155
156 redef fun invoke(instance) do {{{return_value}}}instance.{{{method_name}}}{{{params}}}{{{return_parenthesis}}}
157 end
158 """)
159
160
161 # The actual proxy call
162 proxy_calls.add(
163 """
164 redef fun {{{method_name}}}{{{proxy_sign}}}{{{return_signature}}} do
165 var msg = new {{{name}}}{{{params}}}
166 actor.mailbox.push(msg)
167 {{{return_statement}}}
168 end
169 """)
170 end
171
172 # At this point, all msg classes should be good
173 # All of the functions of the proxy too
174
175 # Let's generate the proxy class then
176 proxys.add(
177 """
178 redef class Proxy{{{classname}}}
179
180 redef type E: Actor{{{classname}}}
181 #var actor: Actor{{{classname}}} is noinit
182
183 init proxy(base_class: {{{classname}}}) do
184 actor = new Actor{{{classname}}}(base_class)
185 actor.start
186 end
187
188 {{{proxy_calls.join("\n\n")}}}
189 end
190 """)
191
192 redef_classes.add(
193 """
194 redef class {{{classname}}}
195 redef var async is lazy do return new Proxy{{{classname}}}.proxy(self)
196 end
197 """)
198 end
199
200 redef fun process_nmodule_after(nmodule) do
201 var first_mmodule = nmodule.mmodule
202 if first_mmodule == null then return
203
204 # Be careful not to generate useless submodules !
205 if not first_mmodule.generate_actor_submodule then return
206
207 # Name of the support module
208 var module_name
209
210 # Path to the support module
211 module_name = "actors_{first_mmodule.name}"
212
213 # We assume a module using actors has a `filepath` not null
214 var mmodule_path = first_mmodule.filepath.as(not null).dirname
215
216 var module_path = "{mmodule_path}/{module_name}.nit"
217
218 var nit_module = new NitModule(module_name)
219 nit_module.annotations.add "no_warning(\"missing-doc\")"
220
221 nit_module.header = """
222 # This file is generated by nitactors (threaded version)
223 # Do not modify, instead use the generated services.
224 """
225
226 # for mmod in mmodules do nit_module.imports.add mmod.name
227 nit_module.imports.add first_mmodule.name
228
229 nit_module.content.add "####################### Redef classes #########################"
230 for c in redef_classes do nit_module.content.add( c + "\n\n" )
231
232 nit_module.content.add "####################### Actor classes #########################"
233 for c in actors do nit_module.content.add( c + "\n\n" )
234
235 nit_module.content.add "####################### Messages classes ######################"
236 for c in messages do nit_module.content.add( c + "\n\n" )
237
238 nit_module.content.add "####################### Proxy classes #########################"
239 for c in proxys do nit_module.content.add( c + "\n\n" )
240
241 # Write support module
242 nit_module.write_to_file module_path
243
244 actors = new Array[String]
245 messages = new Array[String]
246 proxys = new Array[String]
247 redef_classes = new Array[String]
248
249 toolcontext.modelbuilder.inject_module_subimportation(first_mmodule, module_path)
250 end
251 end
252
253 redef class MModule
254 # Do we need to generate the actor submodule ?
255 var generate_actor_submodule = false
256 end
257
258 redef class ToolContext
259 # Generate actors
260 var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase])
261 end