1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Implementation of the method-related annotation `cached`
17 # Note this module can be used as a reference on how to implements
18 # complex annotation that modify both the model and the AST of a Nit program
21 import modelize_property
23 import simple_misc_analysis
25 redef class ToolContext
26 var cached_phase
: Phase = new CachedPhase(self, [modelize_property_phase
])
29 private class CachedPhase
32 init(toolcontext
, depends
)
34 # FIXME The phase has to be executed just after `modelize_property_phase`
35 # But there is no simple way to express this
36 # So, for the moment, I just looked at the linearization and see what phase is after `modelize_property_phase`
37 # And inserted before it
38 toolcontext
.phases
.add_edge
(toolcontext
.simple_misc_analysis_phase
, self)
41 redef fun process_annotated_node
(npropdef
, nat
)
43 # Skip if we are not interested
44 if nat
.n_atid
.n_id
.text
!= "cached" then return
46 # Do some validity checks and print errors if the annotation is used incorrectly
47 var modelbuilder
= toolcontext
.modelbuilder
49 if not npropdef
isa AMethPropdef then
50 modelbuilder
.error
(npropdef
, "Syntax error: only a function can be cached.")
54 var mpropdef
= npropdef
.mpropdef
.as(not null)
56 var mtype
= mpropdef
.msignature
.return_mtype
58 modelbuilder
.error
(npropdef
, "Syntax error: only a function can be cached.")
62 if not npropdef
.n_signature
.n_params
.is_empty
then
63 modelbuilder
.error
(npropdef
, "Syntax error: only a function without arguments can be cached.")
67 # OK, let we do some meta-programming...
69 var location
= npropdef
.location
70 var name
= mpropdef
.mproperty
.name
71 var nclassdef
= npropdef
.parent
.as(AClassdef)
72 var mclassdef
= nclassdef
.mclassdef
.as(not null)
74 if not mclassdef
.mclass
.kind
.need_init
then
75 modelbuilder
.error
(npropdef
, "Error: only abstract and concrete classes can have cached functions.")
79 # Create a new private attribute to store the cache
80 var cache_mpropdef
= new MAttributeDef(mclassdef
, new MAttribute(mclassdef
, "@{name}<cache>", private_visibility
), location
)
81 cache_mpropdef
.static_mtype
= mtype
.as_nullable
83 # Create another new private attribute to store the boolean «is the function cached?»
84 # The point is to manage the case where `null` is a genuine return value of the method
85 var is_cached_mpropdef
= new MAttributeDef(mclassdef
, new MAttribute(mclassdef
, "@{name}<is_cached>", private_visibility
), location
)
86 is_cached_mpropdef
.static_mtype
= mclassdef
.mmodule
.get_primitive_class
("Bool").mclass_type
87 # FIXME? Because there is a default value ("false") a real propdef is required
88 var is_cached_npropdef
= toolcontext
.parse_propdef
("var is_cached = false").as(AAttrPropdef)
89 associate_propdef
(is_cached_mpropdef
, is_cached_npropdef
)
91 # Create a new private method to do the real work
92 var real_mpropdef
= new MMethodDef(mclassdef
, new MMethod(mclassdef
, "{name}<real>", private_visibility
), location
)
93 real_mpropdef
.msignature
= mpropdef
.msignature
94 # FIXME: Again, if the engine require a real propdef even if it is empty
95 var real_npropdef
= toolcontext
.parse_propdef
("fun real do end").as(AMethPropdef)
96 associate_propdef
(real_mpropdef
, real_npropdef
)
97 # Note: the body is set at the last line of this function
99 # Save the original body
100 var real_body
= npropdef
.n_block
.as(not null)
102 # Replace the original body with a new body that do the proxy'n'cache work
103 var proxy_body
= toolcontext
.parse_stmts
("if self._is_cached then return self._cache.as(not null)\nvar res = call_real\nself._cache_write = res\nself._is_cached_write = true\nreturn res")
104 real_body
.replace_with
(proxy_body
)
106 # Do some transformation on the identifiers used on the proxy body so that correct entities are designated
107 # FIXME: we just trick the following phases into associating by name some tokens with some model-entities
108 # But this is bad at at least two levels
109 # - we already know the real model-entities, so why doing latter the association and not now?
110 # - associating by names may cause a useless fragility (name-conflicts, etc.)
111 proxy_body
.collect_tokens_by_text
("_is_cached").first
.text
= is_cached_mpropdef
.mproperty
.name
112 proxy_body
.collect_tokens_by_text
("_is_cached_write").first
.text
= is_cached_mpropdef
.mproperty
.name
113 proxy_body
.collect_tokens_by_text
("_cache").first
.text
= cache_mpropdef
.mproperty
.name
114 proxy_body
.collect_tokens_by_text
("_cache_write").first
.text
= cache_mpropdef
.mproperty
.name
115 proxy_body
.collect_tokens_by_text
("call_real").first
.text
= real_mpropdef
.mproperty
.name
117 # FIXME a last transformation cannot be done yet. So, the call to `super` (`ASuperExpr`) is broken in cached methods.
119 # Give the original body to the private real methoddef
120 real_npropdef
.n_block
.replace_with
(real_body
)
123 # Detach `n` from its original AST and attach it to `m` (and its related AST)
124 # `n` must not be already attached to an existing model entity
125 # `m` must not be already attached to an existing AST node
126 fun associate_propdef
(m
: MPropDef, n
: APropdef)
128 # FIXME: the model-AST relations **must** be rationalized:
129 # * 1- fragility: the risk of inconsistencies is too hight
130 # * 2- complexity: there is too much paths to access the same things
133 assert n
.mpropdef
== null
136 # Required to so that look-for implementation works
137 assert not toolcontext
.modelbuilder
.mpropdef2npropdef
.has_key
(m
)
138 toolcontext
.modelbuilder
.mpropdef2npropdef
[m
] = n
140 var mclassdef
= m
.mclassdef
141 var nclassdef
= toolcontext
.modelbuilder
.mclassdef2nclassdef
[mclassdef
]
143 assert nclassdef
.mclassdef
== mclassdef
145 # Required so that propdef are visited in visitors
146 if not nclassdef
.n_propdefs
.has
(n
) then nclassdef
.n_propdefs
.add
(n
)