Merge: parser_util: drop injected tokens in `parse_someting`
[nit.git] / src / parser_util.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Utils and tools related to parsers and AST
16 module parser_util
17
18 intrude import parser
19 import toolcontext
20
21 redef class ToolContext
22 # Parse a full module given as a string
23 # Fatal error if the `string` is not a syntactically correct module
24 fun parse_module(string: String): AModule
25 do
26 var source = new SourceFile.from_string("", string)
27 var lexer = new Lexer(source)
28 var parser = new Parser(lexer)
29 var tree = parser.parse
30
31 var eof = tree.n_eof
32 if eof isa AError then
33 self.fatal_error(null, "Fatal Error: {eof.message}")
34 abort
35 end
36 return tree.n_base.as(not null)
37 end
38
39 # Parse a full classdef given as a string
40 # Fatal error if the `string` is not a syntactically correct class definition
41 fun parse_classdef(string: String): AClassdef
42 do
43 var nmodule = parse_module(string)
44 var nclassdefs = nmodule.n_classdefs
45 if nclassdefs.length != 1 then
46 self.fatal_error(null, "Fatal Error: not a classdef")
47 abort
48 end
49 return nclassdefs.first
50 end
51
52 # Parse a full propdef given as a string
53 # Fatal error if the `string` is not a syntactically correct property definition
54 fun parse_propdef(string: String): APropdef
55 do
56 var mod_string = "class Dummy\n{string}\nend"
57 var nclassdef = parse_classdef(mod_string)
58 var npropdefs = nclassdef.n_propdefs
59 if npropdefs.length != 1 then
60 self.fatal_error(null, "Fatal Error: not a propdef")
61 abort
62 end
63 return npropdefs.first
64 end
65
66 # Parse a full statement block given as a string
67 # Fatal error if the `string` is not a syntactically correct statement block
68 fun parse_stmts(string: String): AExpr
69 do
70 var mod_string = "do\n{string}\nend"
71 var nmodule = parse_module(mod_string)
72 var nblock = nmodule.n_classdefs.first.n_propdefs.first.as(AMethPropdef).n_block.as(ABlockExpr).n_expr.first.as(ADoExpr).n_block.as(not null)
73 return nblock
74 end
75
76 # Parse a full expression given as a string
77 # Fatal error if the `string` is not a syntactically correct expression
78 fun parse_expr(string: String): AExpr
79 do
80 var mod_string = "var dummy = \n{string}"
81 var nmodule = parse_module(mod_string)
82 var nexpr = nmodule.n_classdefs.first.n_propdefs.first.as(AMethPropdef).n_block.as(ABlockExpr).n_expr.first.as(AVardeclExpr).n_expr.as(not null)
83 return nexpr
84 end
85
86 # Try to parse the `string` as something
87 #
88 # Returns the first possible syntacticaly correct type among:
89 #
90 # - a type `AType`
91 # - a single `Token`
92 # - an expression `AExpr`
93 # - a block of statements `ABlockExpr`
94 # - a full module `AModule`
95 # - a `AError` if nothing else matches
96 #
97 # var tc = new ToolContext
98 # assert tc.parse_something("foo") isa TId
99 # assert tc.parse_something("foo[bar]") isa AExpr
100 # assert tc.parse_something("Foo[Bar]") isa AType
101 # assert tc.parse_something("foo\nbar") isa ABlockExpr
102 # assert tc.parse_something("fun foo do bar\nfoo") isa AModule
103 # assert tc.parse_something("fun fun") isa AParserError
104 # assert tc.parse_something("?%^&") isa ALexerError
105 fun parse_something(string: String): ANode
106 do
107 var source = new SourceFile.from_string("", string)
108 var error
109 var tree
110 var eof
111 var lexer
112
113 lexer = new InjectedLexer(source)
114 lexer.injected_before.add new TKwvar
115 lexer.injected_before.add new TId
116 lexer.injected_before.add new TColumn
117 lexer.injected_before.add new TClassid
118 lexer.injected_before.add new TObra
119 lexer.injected_after.add new TCbra
120 tree = (new Parser(lexer)).parse
121 eof = tree.n_eof
122 if not eof isa AError then
123 var ntype = tree.n_base.n_classdefs.first.n_propdefs.first.as(AMethPropdef).n_block.as(ABlockExpr).n_expr.first.as(AVardeclExpr).n_type.n_types.first
124 return ntype
125 end
126 error = eof
127
128 lexer = new Lexer(source)
129 var first = lexer.next
130 if not first isa EOF then
131 var second = lexer.next
132 if second isa EOF and not second isa AError then
133 return first
134 end
135 end
136
137 lexer = new InjectedLexer(source)
138 lexer.injected_before.add new TKwvar
139 lexer.injected_before.add new TId
140 lexer.injected_before.add new TAssign
141 lexer.injected_before.add new TOpar
142 lexer.injected_after.add new TCpar
143 tree = (new Parser(lexer)).parse
144 eof = tree.n_eof
145 if not eof isa AError then
146 var nexpr = tree.n_base.n_classdefs.first.n_propdefs.first.as(AMethPropdef).n_block.as(ABlockExpr).n_expr.first.as(AVardeclExpr).n_expr.as(AParExpr).n_expr
147 return nexpr
148 end
149 if eof.location > error.location then error = eof
150
151 lexer = new InjectedLexer(source)
152 lexer.injected_before.add new TKwdo
153 lexer.injected_before.add new TEol
154 lexer.injected_after.add new TEol
155 lexer.injected_after.add new TKwend
156 tree = (new Parser(lexer)).parse
157 eof = tree.n_eof
158 if not eof isa AError then
159 var nblock = tree.n_base.n_classdefs.first.n_propdefs.first.as(AMethPropdef).n_block.as(ABlockExpr).n_expr.first.as(ADoExpr).n_block.as(ABlockExpr)
160 nblock.n_kwend = null # drop injected token
161 return nblock
162 end
163 if eof.location > error.location then error = eof
164
165 lexer = new Lexer(source)
166 tree = (new Parser(lexer)).parse
167 eof = tree.n_eof
168 if not eof isa AError then
169 return tree.n_base.as(not null)
170 end
171 if eof.location > error.location then error = eof
172
173 return error
174 end
175
176 # Parse the input of the user as something
177 fun interactive_parse(prompt: String): ANode
178 do
179 var oldtext = ""
180
181 loop
182 printn prompt
183 printn " "
184 var s = sys.stdin.read_line
185 if s == "" then continue
186 if s.chars.first == ':' then
187 var res = new TString
188 res.text = s
189 return res
190 end
191
192 var text = oldtext + s + "\n"
193 oldtext = ""
194 var n = parse_something(text)
195
196 if n isa AParserError and n.token isa EOF then
197 # Unexpected end of file, thus continuing
198 if oldtext == "" then prompt = "." * prompt.length
199 oldtext = text
200 continue
201 end
202
203 return n
204 end
205 end
206 end
207
208 class InjectedLexer
209 super Lexer
210
211 var injected_before = new List[Token]
212 var injected_after = new List[Token]
213 private var is_finished = false
214
215 redef fun get_token
216 do
217 if not injected_before.is_empty then
218 var tok = injected_before.shift
219 if tok._location == null then tok._location = new Location(file, 1, 1, 1, 0)
220 return tok
221 end
222 if not is_finished then
223 var next = super
224 if not next isa EOF then return next
225 injected_after.push(next)
226 is_finished = true
227 end
228
229 var tok = injected_after.shift
230 if tok._location == null then tok._location = new Location(file, 1, 1, 1, 0)
231 return tok
232 end
233 end
234
235 redef class ANode
236 # Return an array of tokens that match a given text
237 fun collect_tokens_by_text(text: String): Array[Token]
238 do
239 var v = new CollectTokensByTextVisitor(text)
240 v.enter_visit(self)
241 return v.result
242 end
243
244 # Return an array of node that are annotated
245 # The attached node can be retrieved by two invocation of parent
246 fun collect_annotations_by_name(name: String): Array[AAnnotation]
247 do
248 var v = new CollectAnnotationsByNameVisitor(name)
249 v.enter_visit(self)
250 return v.result
251 end
252 end
253
254 private class CollectTokensByTextVisitor
255 super Visitor
256 var text: String
257 init(text: String) do self.text = text
258 var result = new Array[Token]
259 redef fun visit(node)
260 do
261 node.visit_all(self)
262 if node isa Token and node.text == text then result.add(node)
263 end
264 end
265
266 private class CollectAnnotationsByNameVisitor
267 super Visitor
268 var name: String
269 init(name: String) do self.name = name
270 var result = new Array[AAnnotation]
271 redef fun visit(node)
272 do
273 node.visit_all(self)
274 if node isa AAnnotation and node.n_atid.n_id.text == name then result.add(node)
275 end
276 end