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
24 private import annotation
26 redef class ToolContext
27 var cached_phase
: Phase = new CachedPhase(self, [modelize_property_phase
])
30 private class CachedPhase
33 init(toolcontext
, depends
)
35 # FIXME The phase has to be executed just after `modelize_property_phase`
36 # But there is no simple way to express this
37 # So, for the moment, I just looked at the linearization and see what phase is after `modelize_property_phase`
38 # And inserted before it
39 toolcontext
.phases
.add_edge
(toolcontext
.simple_misc_analysis_phase
, self)
42 redef fun process_annotated_node
(npropdef
, nat
)
44 # Skip if we are not interested
45 if nat
.name
!= "cached" then return
47 # Do some validity checks and print errors if the annotation is used incorrectly
48 var modelbuilder
= toolcontext
.modelbuilder
50 if not npropdef
isa AMethPropdef then
51 modelbuilder
.error
(npropdef
, "Syntax error: only a function can be cached.")
55 var mpropdef
= npropdef
.mpropdef
.as(not null)
57 var mtype
= mpropdef
.msignature
.return_mtype
59 modelbuilder
.error
(npropdef
, "Syntax error: only a function can be cached.")
63 if not npropdef
.n_signature
.n_params
.is_empty
then
64 modelbuilder
.error
(npropdef
, "Syntax error: only a function without arguments can be cached.")
68 # OK, let we do some meta-programming...
70 var location
= npropdef
.location
71 var name
= mpropdef
.mproperty
.name
72 var nclassdef
= npropdef
.parent
.as(AClassdef)
73 var mclassdef
= nclassdef
.mclassdef
.as(not null)
75 if not mclassdef
.mclass
.kind
.need_init
then
76 modelbuilder
.error
(npropdef
, "Error: only abstract and concrete classes can have cached functions.")
80 # Create a new private attribute to store the cache
81 var cache_mpropdef
= new MAttributeDef(mclassdef
, new MAttribute(mclassdef
, "@{name}<cache>", private_visibility
), location
)
82 cache_mpropdef
.static_mtype
= mtype
.as_nullable
84 # Create another new private attribute to store the boolean «is the function cached?»
85 # The point is to manage the case where `null` is a genuine return value of the method
86 var is_cached_mpropdef
= new MAttributeDef(mclassdef
, new MAttribute(mclassdef
, "@{name}<is_cached>", private_visibility
), location
)
87 is_cached_mpropdef
.static_mtype
= mclassdef
.mmodule
.get_primitive_class
("Bool").mclass_type
88 # FIXME? Because there is a default value ("false") a real propdef is required
89 var is_cached_npropdef
= toolcontext
.parse_propdef
("var is_cached = false").as(AAttrPropdef)
90 associate_propdef
(is_cached_mpropdef
, is_cached_npropdef
)
92 # Create a new private method to do the real work
93 var real_mpropdef
= new MMethodDef(mclassdef
, new MMethod(mclassdef
, "{name}<real>", private_visibility
), location
)
94 real_mpropdef
.msignature
= mpropdef
.msignature
95 # FIXME: Again, if the engine require a real propdef even if it is empty
96 var real_npropdef
= toolcontext
.parse_propdef
("fun real do end").as(AMethPropdef)
97 associate_propdef
(real_mpropdef
, real_npropdef
)
98 # Note: the body is set at the last line of this function
100 # Save the original body
101 var real_body
= npropdef
.n_block
.as(not null)
103 # Replace the original body with a new body that do the proxy'n'cache work
104 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")
105 real_body
.replace_with
(proxy_body
)
107 # Do some transformation on the identifiers used on the proxy body so that correct entities are designated
108 # FIXME: we just trick the following phases into associating by name some tokens with some model-entities
109 # But this is bad at at least two levels
110 # - we already know the real model-entities, so why doing latter the association and not now?
111 # - associating by names may cause a useless fragility (name-conflicts, etc.)
112 proxy_body
.collect_tokens_by_text
("_is_cached").first
.text
= is_cached_mpropdef
.mproperty
.name
113 proxy_body
.collect_tokens_by_text
("_is_cached_write").first
.text
= is_cached_mpropdef
.mproperty
.name
114 proxy_body
.collect_tokens_by_text
("_cache").first
.text
= cache_mpropdef
.mproperty
.name
115 proxy_body
.collect_tokens_by_text
("_cache_write").first
.text
= cache_mpropdef
.mproperty
.name
116 proxy_body
.collect_tokens_by_text
("call_real").first
.text
= real_mpropdef
.mproperty
.name
118 # FIXME a last transformation cannot be done yet. So, the call to `super` (`ASuperExpr`) is broken in cached methods.
120 # Give the original body to the private real methoddef
121 real_npropdef
.n_block
.replace_with
(real_body
)
124 # Detach `n` from its original AST and attach it to `m` (and its related AST)
125 # `n` must not be already attached to an existing model entity
126 # `m` must not be already attached to an existing AST node
127 fun associate_propdef
(m
: MPropDef, n
: APropdef)
129 # FIXME: the model-AST relations **must** be rationalized:
130 # * 1- fragility: the risk of inconsistencies is too hight
131 # * 2- complexity: there is too much paths to access the same things
134 assert n
.mpropdef
== null
137 # Required to so that look-for implementation works
138 assert not toolcontext
.modelbuilder
.mpropdef2npropdef
.has_key
(m
)
139 toolcontext
.modelbuilder
.mpropdef2npropdef
[m
] = n
141 var mclassdef
= m
.mclassdef
142 var nclassdef
= toolcontext
.modelbuilder
.mclassdef2nclassdef
[mclassdef
]
144 assert nclassdef
.mclassdef
== mclassdef
146 # Required so that propdef are visited in visitors
147 if not nclassdef
.n_propdefs
.has
(n
) then nclassdef
.n_propdefs
.add
(n
)