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 # Semantic analysis of a sablecc grammar file
16 # User to validate the file and generate the grammar and the nfa
18 # TODO: split in submodules
25 # Main visitor for the semantic analysis
27 # TODO clean-up the visitors
28 class CollectNameVisitor
31 var nprods
= new Array[Nprod]
33 # Symbol table to associate things (prods and exprs) with their name
34 var names
= new HashMap[String, Node]
36 # The associated grammar object
37 # Read the result of the visit here
40 # The associated NFA object
41 # Read the result of the visit here
42 var nfa
= new Automaton.empty
44 # Run the semantic analysis of the grammar
47 # First visit to collect names
50 # Second visit to use collectec names and build rhings
51 var v2
= new CheckNameVisitor(self)
55 gram
.inline
(v2
.quesizes
.values
)
56 # Inlile all the prods sufixed by '_imline' #TODO use a real keyword
57 for p
in gram
.prods
do
58 if not p
.name
.has_suffix
("_inline") then continue
63 # Build the NFA automaton
64 for t
in gram
.tokens
do
68 nfa2
= new Automaton.epsilon
69 for c
in t
.text
.as(not null) do
70 nfa2
.concat
(new Automaton.atom
(c
.ascii
))
73 nfa2
= nexpr
.build_nfa
75 nfa
.states
.add_all nfa2
.states
76 nfa
.start
.add_trans
(nfa2
.start
, null)
77 for s
in nfa2
.accept
do nfa
.add_tag
(s
, t
)
78 nfa
.accept
.add_all nfa2
.accept
82 redef fun visit
(n
) do n
.accept_collect_prod
(self)
85 private class CheckNameVisitor
88 # The collected altname, for the alternative
89 var altname
: nullable String = null
91 # The collected elements, for the alternative
92 var elems
= new Array[Element]
94 # The collected element names, for the alternative
95 var elems_names
= new Array[nullable String]
97 # The collected elementname, for the nelem
98 var elemname
: nullable String = null
100 # Is the alternative transformed, for the alternative
103 # Pool of elements that are modified with + (reuse them!)
104 private var plusizes
= new HashMap[Element, Production]
106 # Create a + version of an element
107 fun plusize
(e
: Element): Production
109 if plusizes
.has_key
(e
) then return plusizes
[e
]
111 var prod
= new Production(name
)
112 prod
.acname
= "Nodes[{e.acname}]"
113 v1
.gram
.prods
.add
(prod
)
114 prod
.new_alt
("{name}_one", e
)
115 prod
.new_alt
("{name}_more", prod
, e
)
120 # Pool of elements that are modified with ? (reuse them!)
121 private var quesizes
= new HashMap[Element, Production]
123 # Create a ? version of an element
124 fun quesize
(e
: Element): Production
126 if quesizes
.has_key
(e
) then return quesizes
[e
]
128 var prod
= new Production(name
)
129 prod
.acname
= "nullable {e.acname}"
130 v1
.gram
.prods
.add
(prod
)
131 var a1
= prod
.new_alt
("{name}_one", e
)
132 a1
.codes
= [new CodePop]
133 var a0
= prod
.new_alt0
("{name}_none")
134 a0
.codes
= [new CodeNull]
139 # The current nexpr, used to track dependency on named expressions (see `Nexpr::precs`)
140 var nexpr
: nullable Nexpr
142 # The current production, used to initialize alternatives
143 var prod
: nullable Production
145 # The main visitor, used to access the grammar of the symbol table
146 var v1
: CollectNameVisitor
147 init(v1
: CollectNameVisitor) do self.v1
= v1
149 redef fun visit
(n
) do n
.accept_check_name_visitor
(self)
153 private fun accept_collect_prod
(v
: CollectNameVisitor) do visit_children
(v
)
154 private fun accept_check_name_visitor
(v
: CheckNameVisitor) do visit_children
(v
)
158 # The associated token
159 var token
: nullable Token
161 # The associated name
162 var name
: nullable String
164 # The required expression (Nexpr that are used inside)
165 var precs
= new ArraySet[Nexpr]
167 redef fun accept_collect_prod
(v
) do
168 var id
= children
.first
.as(Nid)
173 redef fun accept_check_name_visitor
(v
) do
179 # Flag to track circular regular expression
180 var is_building
= false
182 # The associated NFA (cached, see `build_nfa`)
183 private var nfa
: nullable Automaton
185 # Build the NFA, possibily building the NFA of required expressions
186 # Print an error if there is a circular dependency
187 # The result is cached
188 fun build_nfa
: Automaton do
190 if res
!= null then return res
192 print
"Error: circular regular expression {name.to_s}."
201 var nre
= self.children
[2]
209 # The named expression
210 var nexpr
: nullable Nexpr
212 redef fun accept_check_name_visitor
(v
) do
213 var id
= children
.first
.as(Nid)
215 if not v
.v1
.names
.has_key
(name
) then
216 print
"Error: unknown name {name}"
220 var node
= v
.v1
.names
[name
]
221 if node
isa Nprod then
222 print
"Error: cannot use production {name} in a regular expression"
225 else if not node
isa Nexpr then
229 v
.nexpr
.precs
.add
(node
)
240 var elem
: nullable Element
242 redef fun accept_check_name_visitor
(v
) do
243 var id
= children
[1].as(Nid)
245 if not v
.v1
.names
.has_key
(name
) then
246 print
"Error: unknown name {name}"
250 var node
= v
.v1
.names
[name
]
251 var elem
: nullable Element
252 if node
isa Nprod then
253 print
"Error cannot ignore a production"
256 else if node
isa Nexpr then
259 elem
= new Token("Ignored")
261 v
.v1
.gram
.tokens
.add
(elem
)
272 # The associated production
273 var prod
: nullable Production
275 redef fun accept_collect_prod
(v
) do
276 var id
= children
.first
.as(Nid)
280 prod
= new Production(name
)
281 v
.gram
.prods
.add
(prod
.as(not null))
283 redef fun accept_check_name_visitor
(v
) do
291 redef fun accept_check_name_visitor
(v
) do
292 var id
= children
[2].as(Nid)
295 var node
= v
.v1
.names
[name
]
296 if node
isa Nprod then
297 v
.prod
.spe
= node
.prod
.as(not null)
298 if v
.prod
.spe
.spe
!= null then
299 print
"Cannot transform into {name}, {name} is already transformed."
303 else if node
isa Nexpr then
304 print
"Cannot transform into {name}, {name} is a token."
312 redef fun accept_check_name_visitor
(v
) do
313 var id
= children
[2].as(Nid)
321 # The associated alternative
322 var alt
: nullable Alternative
324 redef fun accept_check_name_visitor
(v
)
328 v
.elems
= new Array[Element]
329 v
.elems_names
= new Array[nullable String]
331 var prod
= v
.prod
.as(not null)
332 var prodabs
= prod
.spe
333 if prodabs
== null then prodabs
= prod
337 name
= prod
.name
+ "_" + prod
.alts
.length
.to_s
338 else if prod
.spe
== null and prod
.alts
.is_empty
then
342 if prodabs
.altone
then
343 prodabs
.altone
= false
344 prodabs
.alts
.first
.name
= prodabs
.name
+ "_0"
346 name
= prod
.name
+ "_" + prod
.alts
.length
.to_s
349 if prodabs
.altone
then
350 prodabs
.altone
= false
351 prodabs
.alts
.first
.name
= prodabs
.name
+ "_0"
353 name
= prodabs
.name
+ "_" + name
355 var alt
= prod
.new_alt2
(name
, v
.elems
)
356 alt
.elems_names
.add_all
(v
.elems_names
)
360 alt
.codes
= [new CodePop]
366 redef fun accept_check_name_visitor
(v
)
368 var id
= children
[1].as(Nid)
375 # The associated element
376 var elem
: nullable Element
380 # The associated expression (if any, ie defined in the lexer part)
381 var nexpr
: nullable Nexpr
382 # The associated text (if any, ie defined in the parser part)
383 var text
: nullable String
387 redef fun accept_check_name_visitor
(v
)
391 var elemname
= v
.elemname
392 if elemname
!= null and v
.elems_names
.has
(elemname
) then
394 while v
.elems_names
.has
(elemname
+i
.to_s
) do i
+= 1
395 elemname
= elemname
+i
.to_s
397 v
.elems_names
.add elemname
402 redef fun accept_check_name_visitor
(v
)
404 var id
= children
[1].as(Nid)
411 redef fun accept_check_name_visitor
(v
) do
412 var id
= children
.first
.as(Nid)
414 if not v
.v1
.names
.has_key
(name
) then
415 print
"Error: unknown name {name}"
419 var node
= v
.v1
.names
[name
]
420 var elem
: nullable Element
421 if node
isa Nprod then
423 else if node
isa Nexpr then
426 elem
= new Token(name
)
428 v
.v1
.gram
.tokens
.add
(elem
)
437 if v
.elemname
== null then v
.elemname
= name
441 redef class Nelem_str
442 redef fun accept_check_name_visitor
(v
) do
443 var str
= children
.first
.as(Nstr)
446 var elem
: nullable Element
447 if v
.v1
.names
.has_key
(name
) then
448 elem
= v
.v1
.names
[name
].as(Nelem_str).elem
451 elem
= new Token(name
)
453 v
.v1
.gram
.tokens
.add
(elem
)
454 v
.v1
.names
[name
] = self
461 redef class Nelem_star
462 redef fun accept_check_name_visitor
(v
) do
464 var elem
= v
.elems
.pop
465 elem
= v
.plusize
(elem
)
466 elem
= v
.quesize
(elem
)
471 redef class Nelem_ques
472 redef fun accept_check_name_visitor
(v
) do
474 var elem
= v
.elems
.pop
475 elem
= v
.quesize
(elem
)
480 redef class Nelem_plus
481 redef fun accept_check_name_visitor
(v
) do
483 var elem
= v
.elems
.pop
484 elem
= v
.plusize
(elem
)