]> git.proxmox.com Git - mirror_qemu.git/blob - scripts/minikconf.py
minikconfig: add parser skeleton
[mirror_qemu.git] / scripts / minikconf.py
1 #
2 # Mini-Kconfig parser
3 #
4 # Copyright (c) 2015 Red Hat Inc.
5 #
6 # Authors:
7 # Paolo Bonzini <pbonzini@redhat.com>
8 #
9 # This work is licensed under the terms of the GNU GPL, version 2
10 # or, at your option, any later version. See the COPYING file in
11 # the top-level directory.
12
13 from __future__ import print_function
14 import os
15 import sys
16
17 __all__ = [ 'KconfigParserError', 'KconfigData', 'KconfigParser' ]
18
19 def debug_print(*args):
20 #print('# ' + (' '.join(str(x) for x in args)))
21 pass
22
23 # -------------------------------------------
24 # KconfigData implements the Kconfig semantics. For now it can only
25 # detect undefined symbols, i.e. symbols that were referenced in
26 # assignments or dependencies but were not declared with "config FOO".
27 #
28 # Semantic actions are represented by methods called do_*. The do_var
29 # method return the semantic value of a variable (which right now is
30 # just its name).
31 # -------------------------------------------
32
33 class KconfigData:
34 def __init__(self):
35 self.previously_included = []
36 self.incl_info = None
37 self.defined_vars = set()
38 self.referenced_vars = set()
39
40 # semantic analysis -------------
41
42 def check_undefined(self):
43 undef = False
44 for i in self.referenced_vars:
45 if not (i in self.defined_vars):
46 print("undefined symbol %s" % (i), file=sys.stderr)
47 undef = True
48 return undef
49
50 # semantic actions -------------
51
52 def do_declaration(self, var):
53 if (var in self.defined_vars):
54 raise Exception('variable "' + var + '" defined twice')
55
56 self.defined_vars.add(var)
57
58 # var is a string with the variable's name.
59 #
60 # For now this just returns the variable's name itself.
61 def do_var(self, var):
62 self.referenced_vars.add(var)
63 return var
64
65 def do_assignment(self, var, val):
66 pass
67
68 def do_default(self, var, val, cond=None):
69 pass
70
71 def do_depends_on(self, var, expr):
72 pass
73
74 def do_select(self, var, symbol, cond=None):
75 pass
76
77 def do_imply(self, var, symbol, cond=None):
78 pass
79
80 # -------------------------------------------
81 # KconfigParser implements a recursive descent parser for (simplified)
82 # Kconfig syntax.
83 # -------------------------------------------
84
85 # tokens table
86 TOKENS = {}
87 TOK_NONE = -1
88 TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("';
89 TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"';
90 TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="';
91 TOK_AND = 3; TOKENS[TOK_AND] = '"&&"';
92 TOK_OR = 4; TOKENS[TOK_OR] = '"||"';
93 TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"';
94 TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"';
95 TOK_ON = 7; TOKENS[TOK_ON] = '"on"';
96 TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"';
97 TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"';
98 TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"';
99 TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"';
100 TOK_Y = 12; TOKENS[TOK_Y] = '"y"';
101 TOK_N = 13; TOKENS[TOK_N] = '"n"';
102 TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"';
103 TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"';
104 TOK_IF = 16; TOKENS[TOK_IF] = '"if"';
105 TOK_ID = 17; TOKENS[TOK_ID] = 'identifier';
106 TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file';
107
108 class KconfigParserError(Exception):
109 def __init__(self, parser, msg, tok=None):
110 self.loc = parser.location()
111 tok = tok or parser.tok
112 if tok != TOK_NONE:
113 location = TOKENS.get(tok, None) or ('"%s"' % tok)
114 msg = '%s before %s' % (msg, location)
115 self.msg = msg
116
117 def __str__(self):
118 return "%s: %s" % (self.loc, self.msg)
119
120 class KconfigParser:
121 @classmethod
122 def parse(self, fp):
123 data = KconfigData()
124 parser = KconfigParser(data)
125 parser.parse_file(fp)
126 if data.check_undefined():
127 raise KconfigParserError(parser, "there were undefined symbols")
128
129 return data
130
131 def __init__(self, data):
132 self.data = data
133
134 def parse_file(self, fp):
135 self.abs_fname = os.path.abspath(fp.name)
136 self.fname = fp.name
137 self.data.previously_included.append(self.abs_fname)
138 self.src = fp.read()
139 if self.src == '' or self.src[-1] != '\n':
140 self.src += '\n'
141 self.cursor = 0
142 self.line = 1
143 self.line_pos = 0
144 self.get_token()
145 self.parse_config()
146
147 # file management -----
148
149 def error_path(self):
150 inf = self.data.incl_info
151 res = ""
152 while inf:
153 res = ("In file included from %s:%d:\n" % (inf['file'],
154 inf['line'])) + res
155 inf = inf['parent']
156 return res
157
158 def location(self):
159 col = 1
160 for ch in self.src[self.line_pos:self.pos]:
161 if ch == '\t':
162 col += 8 - ((col - 1) % 8)
163 else:
164 col += 1
165 return '%s%s:%d:%d' %(self.error_path(), self.fname, self.line, col)
166
167 def do_include(self, include):
168 incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
169 include)
170 # catch inclusion cycle
171 inf = self.data.incl_info
172 while inf:
173 if incl_abs_fname == os.path.abspath(inf['file']):
174 raise KconfigParserError(self, "Inclusion loop for %s"
175 % include)
176 inf = inf['parent']
177
178 # skip multiple include of the same file
179 if incl_abs_fname in self.data.previously_included:
180 return
181 try:
182 fp = open(incl_abs_fname, 'r')
183 except IOError as e:
184 raise KconfigParserError(self,
185 '%s: %s' % (e.strerror, include))
186
187 inf = self.data.incl_info
188 self.data.incl_info = { 'file': self.fname, 'line': self.line,
189 'parent': inf }
190 KconfigParser(self.data).parse_file(fp)
191 self.data.incl_info = inf
192
193 # recursive descent parser -----
194
195 # y_or_n: Y | N
196 def parse_y_or_n(self):
197 if self.tok == TOK_Y:
198 self.get_token()
199 return True
200 if self.tok == TOK_N:
201 self.get_token()
202 return False
203 raise KconfigParserError(self, 'Expected "y" or "n"')
204
205 # var: ID
206 def parse_var(self):
207 if self.tok == TOK_ID:
208 val = self.val
209 self.get_token()
210 return self.data.do_var(val)
211 else:
212 raise KconfigParserError(self, 'Expected identifier')
213
214 # assignment_var: ID (starting with "CONFIG_")
215 def parse_assignment_var(self):
216 if self.tok == TOK_ID:
217 val = self.val
218 if not val.startswith("CONFIG_"):
219 raise KconfigParserError(self,
220 'Expected identifier starting with "CONFIG_"', TOK_NONE)
221 self.get_token()
222 return self.data.do_var(val[7:])
223 else:
224 raise KconfigParserError(self, 'Expected identifier')
225
226 # assignment: var EQUAL y_or_n
227 def parse_assignment(self):
228 var = self.parse_assignment_var()
229 if self.tok != TOK_EQUAL:
230 raise KconfigParserError(self, 'Expected "="')
231 self.get_token()
232 self.data.do_assignment(var, self.parse_y_or_n())
233
234 # primary: NOT primary
235 # | LPAREN expr RPAREN
236 # | var
237 def parse_primary(self):
238 if self.tok == TOK_NOT:
239 self.get_token()
240 self.parse_primary()
241 elif self.tok == TOK_LPAREN:
242 self.get_token()
243 self.parse_expr()
244 if self.tok != TOK_RPAREN:
245 raise KconfigParserError(self, 'Expected ")"')
246 self.get_token()
247 elif self.tok == TOK_ID:
248 self.parse_var()
249 else:
250 raise KconfigParserError(self, 'Expected "!" or "(" or identifier')
251
252 # disj: primary (OR primary)*
253 def parse_disj(self):
254 self.parse_primary()
255 while self.tok == TOK_OR:
256 self.get_token()
257 self.parse_primary()
258
259 # expr: disj (AND disj)*
260 def parse_expr(self):
261 self.parse_disj()
262 while self.tok == TOK_AND:
263 self.get_token()
264 self.parse_disj()
265
266 # condition: IF expr
267 # | empty
268 def parse_condition(self):
269 if self.tok == TOK_IF:
270 self.get_token()
271 return self.parse_expr()
272 else:
273 return None
274
275 # property: DEFAULT y_or_n condition
276 # | DEPENDS ON expr
277 # | SELECT var condition
278 # | BOOL
279 def parse_property(self, var):
280 if self.tok == TOK_DEFAULT:
281 self.get_token()
282 val = self.parse_y_or_n()
283 cond = self.parse_condition()
284 self.data.do_default(var, val, cond)
285 elif self.tok == TOK_DEPENDS:
286 self.get_token()
287 if self.tok != TOK_ON:
288 raise KconfigParserError(self, 'Expected "on"')
289 self.get_token()
290 self.data.do_depends_on(var, self.parse_expr())
291 elif self.tok == TOK_SELECT:
292 self.get_token()
293 symbol = self.parse_var()
294 cond = self.parse_condition()
295 self.data.do_select(var, symbol, cond)
296 elif self.tok == TOK_IMPLY:
297 self.get_token()
298 symbol = self.parse_var()
299 cond = self.parse_condition()
300 self.data.do_imply(var, symbol, cond)
301 elif self.tok == TOK_BOOL:
302 self.get_token()
303 else:
304 raise KconfigParserError(self, 'Error in recursive descent?')
305
306 # properties: properties property
307 # | /* empty */
308 def parse_properties(self, var):
309 had_default = False
310 while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
311 self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
312 self.tok == TOK_IMPLY:
313 self.parse_property(var)
314 self.data.do_default(var, False)
315
316 # for nicer error message
317 if self.tok != TOK_SOURCE and self.tok != TOK_CONFIG and \
318 self.tok != TOK_ID and self.tok != TOK_EOF:
319 raise KconfigParserError(self, 'expected "source", "config", identifier, '
320 + '"default", "depends on", "imply" or "select"')
321
322 # declaration: config var properties
323 def parse_declaration(self):
324 if self.tok == TOK_CONFIG:
325 self.get_token()
326 var = self.parse_var()
327 self.data.do_declaration(var)
328 self.parse_properties(var)
329 else:
330 raise KconfigParserError(self, 'Error in recursive descent?')
331
332 # clause: SOURCE
333 # | declaration
334 # | assignment
335 def parse_clause(self):
336 if self.tok == TOK_SOURCE:
337 val = self.val
338 self.get_token()
339 self.do_include(val)
340 elif self.tok == TOK_CONFIG:
341 self.parse_declaration()
342 elif self.tok == TOK_ID:
343 self.parse_assignment()
344 else:
345 raise KconfigParserError(self, 'expected "source", "config" or identifier')
346
347 # config: clause+ EOF
348 def parse_config(self):
349 while self.tok != TOK_EOF:
350 self.parse_clause()
351 return self.data
352
353 # scanner -----
354
355 def get_token(self):
356 while True:
357 self.tok = self.src[self.cursor]
358 self.pos = self.cursor
359 self.cursor += 1
360
361 self.val = None
362 self.tok = self.scan_token()
363 if self.tok is not None:
364 return
365
366 def check_keyword(self, rest):
367 if not self.src.startswith(rest, self.cursor):
368 return False
369 length = len(rest)
370 if self.src[self.cursor + length].isalnum() or self.src[self.cursor + length] == '|':
371 return False
372 self.cursor += length
373 return True
374
375 def scan_token(self):
376 if self.tok == '#':
377 self.cursor = self.src.find('\n', self.cursor)
378 return None
379 elif self.tok == '=':
380 return TOK_EQUAL
381 elif self.tok == '(':
382 return TOK_LPAREN
383 elif self.tok == ')':
384 return TOK_RPAREN
385 elif self.tok == '&' and self.src[self.pos+1] == '&':
386 self.cursor += 1
387 return TOK_AND
388 elif self.tok == '|' and self.src[self.pos+1] == '|':
389 self.cursor += 1
390 return TOK_OR
391 elif self.tok == '!':
392 return TOK_NOT
393 elif self.tok == 'd' and self.check_keyword("epends"):
394 return TOK_DEPENDS
395 elif self.tok == 'o' and self.check_keyword("n"):
396 return TOK_ON
397 elif self.tok == 's' and self.check_keyword("elect"):
398 return TOK_SELECT
399 elif self.tok == 'i' and self.check_keyword("mply"):
400 return TOK_IMPLY
401 elif self.tok == 'c' and self.check_keyword("onfig"):
402 return TOK_CONFIG
403 elif self.tok == 'd' and self.check_keyword("efault"):
404 return TOK_DEFAULT
405 elif self.tok == 'b' and self.check_keyword("ool"):
406 return TOK_BOOL
407 elif self.tok == 'i' and self.check_keyword("f"):
408 return TOK_IF
409 elif self.tok == 'y' and self.check_keyword(""):
410 return TOK_Y
411 elif self.tok == 'n' and self.check_keyword(""):
412 return TOK_N
413 elif (self.tok == 's' and self.check_keyword("ource")) or \
414 self.tok == 'i' and self.check_keyword("nclude"):
415 # source FILENAME
416 # include FILENAME
417 while self.src[self.cursor].isspace():
418 self.cursor += 1
419 start = self.cursor
420 self.cursor = self.src.find('\n', self.cursor)
421 self.val = self.src[start:self.cursor]
422 return TOK_SOURCE
423 elif self.tok.isalpha():
424 # identifier
425 while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_':
426 self.cursor += 1
427 self.val = self.src[self.pos:self.cursor]
428 return TOK_ID
429 elif self.tok == '\n':
430 if self.cursor == len(self.src):
431 return TOK_EOF
432 self.line += 1
433 self.line_pos = self.cursor
434 elif not self.tok.isspace():
435 raise KconfigParserError(self, 'invalid input')
436
437 return None
438
439 if __name__ == '__main__':
440 fname = len(sys.argv) > 1 and sys.argv[1] or 'Kconfig.test'
441 KconfigParser.parse(open(fname, 'r'))