blob: f0cc3b9fb9871c258f226c1cb276794a1b2052ac [file] [log] [blame]
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +08001#
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
13from __future__ import print_function
14import os
15import sys
16
17__all__ = [ 'KconfigParserError', 'KconfigData', 'KconfigParser' ]
18
19def 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
33class KconfigData:
Paolo Bonzini53167f52019-01-23 14:55:57 +080034 class Expr:
35 def __and__(self, rhs):
36 return KconfigData.AND(self, rhs)
37 def __or__(self, rhs):
38 return KconfigData.OR(self, rhs)
39 def __invert__(self):
40 return KconfigData.NOT(self)
41
42 class AND(Expr):
43 def __init__(self, lhs, rhs):
44 self.lhs = lhs
45 self.rhs = rhs
46 def __str__(self):
47 return "(%s && %s)" % (self.lhs, self.rhs)
48
49 class OR(Expr):
50 def __init__(self, lhs, rhs):
51 self.lhs = lhs
52 self.rhs = rhs
53 def __str__(self):
54 return "(%s || %s)" % (self.lhs, self.rhs)
55
56 class NOT(Expr):
57 def __init__(self, lhs):
58 self.lhs = lhs
59 def __str__(self):
60 return "!%s" % (self.lhs)
61
62 class Var(Expr):
63 def __init__(self, name):
64 self.name = name
65 self.value = None
66 def __str__(self):
67 return self.name
68
69 class Clause:
70 def __init__(self, dest):
71 self.dest = dest
72
73 class AssignmentClause(Clause):
74 def __init__(self, dest, value):
75 KconfigData.Clause.__init__(self, dest)
76 self.value = value
77 def __str__(self):
78 return "%s=%s" % (self.dest, 'y' if self.value else 'n')
79
80 class DefaultClause(Clause):
81 def __init__(self, dest, value, cond=None):
82 KconfigData.Clause.__init__(self, dest)
83 self.value = value
84 self.cond = cond
85 def __str__(self):
86 value = 'y' if self.value else 'n'
87 if self.cond is None:
88 return "config %s default %s" % (self.dest, value)
89 else:
90 return "config %s default %s if %s" % (self.dest, value, self.cond)
91
92 class DependsOnClause(Clause):
93 def __init__(self, dest, expr):
94 KconfigData.Clause.__init__(self, dest)
95 self.expr = expr
96 def __str__(self):
97 return "config %s depends on %s" % (self.dest, self.expr)
98
99 class SelectClause(Clause):
100 def __init__(self, dest, cond):
101 KconfigData.Clause.__init__(self, dest)
102 self.cond = cond
103 def __str__(self):
104 return "select %s if %s" % (self.dest, self.cond)
105
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800106 def __init__(self):
107 self.previously_included = []
108 self.incl_info = None
109 self.defined_vars = set()
Paolo Bonzini53167f52019-01-23 14:55:57 +0800110 self.referenced_vars = dict()
111 self.clauses = list()
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800112
113 # semantic analysis -------------
114
115 def check_undefined(self):
116 undef = False
117 for i in self.referenced_vars:
118 if not (i in self.defined_vars):
119 print("undefined symbol %s" % (i), file=sys.stderr)
120 undef = True
121 return undef
122
123 # semantic actions -------------
124
125 def do_declaration(self, var):
126 if (var in self.defined_vars):
127 raise Exception('variable "' + var + '" defined twice')
128
Paolo Bonzini53167f52019-01-23 14:55:57 +0800129 self.defined_vars.add(var.name)
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800130
131 # var is a string with the variable's name.
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800132 def do_var(self, var):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800133 if (var in self.referenced_vars):
134 return self.referenced_vars[var]
135
136 var_obj = self.referenced_vars[var] = KconfigData.Var(var)
137 return var_obj
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800138
139 def do_assignment(self, var, val):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800140 self.clauses.append(KconfigData.AssignmentClause(var, val))
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800141
142 def do_default(self, var, val, cond=None):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800143 self.clauses.append(KconfigData.DefaultClause(var, val, cond))
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800144
145 def do_depends_on(self, var, expr):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800146 self.clauses.append(KconfigData.DependsOnClause(var, expr))
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800147
148 def do_select(self, var, symbol, cond=None):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800149 cond = (cond & var) if cond is not None else var
150 self.clauses.append(KconfigData.SelectClause(symbol, cond))
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800151
152 def do_imply(self, var, symbol, cond=None):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800153 # "config X imply Y [if COND]" is the same as
154 # "config Y default y if X [&& COND]"
155 cond = (cond & var) if cond is not None else var
156 self.do_default(symbol, True, cond)
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800157
158# -------------------------------------------
159# KconfigParser implements a recursive descent parser for (simplified)
160# Kconfig syntax.
161# -------------------------------------------
162
163# tokens table
164TOKENS = {}
165TOK_NONE = -1
166TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("';
167TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"';
168TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="';
169TOK_AND = 3; TOKENS[TOK_AND] = '"&&"';
170TOK_OR = 4; TOKENS[TOK_OR] = '"||"';
171TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"';
172TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"';
173TOK_ON = 7; TOKENS[TOK_ON] = '"on"';
174TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"';
175TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"';
176TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"';
177TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"';
178TOK_Y = 12; TOKENS[TOK_Y] = '"y"';
179TOK_N = 13; TOKENS[TOK_N] = '"n"';
180TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"';
181TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"';
182TOK_IF = 16; TOKENS[TOK_IF] = '"if"';
183TOK_ID = 17; TOKENS[TOK_ID] = 'identifier';
184TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file';
185
186class KconfigParserError(Exception):
187 def __init__(self, parser, msg, tok=None):
188 self.loc = parser.location()
189 tok = tok or parser.tok
190 if tok != TOK_NONE:
191 location = TOKENS.get(tok, None) or ('"%s"' % tok)
192 msg = '%s before %s' % (msg, location)
193 self.msg = msg
194
195 def __str__(self):
196 return "%s: %s" % (self.loc, self.msg)
197
198class KconfigParser:
199 @classmethod
200 def parse(self, fp):
201 data = KconfigData()
202 parser = KconfigParser(data)
203 parser.parse_file(fp)
204 if data.check_undefined():
205 raise KconfigParserError(parser, "there were undefined symbols")
206
207 return data
208
209 def __init__(self, data):
210 self.data = data
211
212 def parse_file(self, fp):
213 self.abs_fname = os.path.abspath(fp.name)
214 self.fname = fp.name
215 self.data.previously_included.append(self.abs_fname)
216 self.src = fp.read()
217 if self.src == '' or self.src[-1] != '\n':
218 self.src += '\n'
219 self.cursor = 0
220 self.line = 1
221 self.line_pos = 0
222 self.get_token()
223 self.parse_config()
224
225 # file management -----
226
227 def error_path(self):
228 inf = self.data.incl_info
229 res = ""
230 while inf:
231 res = ("In file included from %s:%d:\n" % (inf['file'],
232 inf['line'])) + res
233 inf = inf['parent']
234 return res
235
236 def location(self):
237 col = 1
238 for ch in self.src[self.line_pos:self.pos]:
239 if ch == '\t':
240 col += 8 - ((col - 1) % 8)
241 else:
242 col += 1
243 return '%s%s:%d:%d' %(self.error_path(), self.fname, self.line, col)
244
245 def do_include(self, include):
246 incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
247 include)
248 # catch inclusion cycle
249 inf = self.data.incl_info
250 while inf:
251 if incl_abs_fname == os.path.abspath(inf['file']):
252 raise KconfigParserError(self, "Inclusion loop for %s"
253 % include)
254 inf = inf['parent']
255
256 # skip multiple include of the same file
257 if incl_abs_fname in self.data.previously_included:
258 return
259 try:
260 fp = open(incl_abs_fname, 'r')
261 except IOError as e:
262 raise KconfigParserError(self,
263 '%s: %s' % (e.strerror, include))
264
265 inf = self.data.incl_info
266 self.data.incl_info = { 'file': self.fname, 'line': self.line,
267 'parent': inf }
268 KconfigParser(self.data).parse_file(fp)
269 self.data.incl_info = inf
270
271 # recursive descent parser -----
272
273 # y_or_n: Y | N
274 def parse_y_or_n(self):
275 if self.tok == TOK_Y:
276 self.get_token()
277 return True
278 if self.tok == TOK_N:
279 self.get_token()
280 return False
281 raise KconfigParserError(self, 'Expected "y" or "n"')
282
283 # var: ID
284 def parse_var(self):
285 if self.tok == TOK_ID:
286 val = self.val
287 self.get_token()
288 return self.data.do_var(val)
289 else:
290 raise KconfigParserError(self, 'Expected identifier')
291
292 # assignment_var: ID (starting with "CONFIG_")
293 def parse_assignment_var(self):
294 if self.tok == TOK_ID:
295 val = self.val
296 if not val.startswith("CONFIG_"):
297 raise KconfigParserError(self,
298 'Expected identifier starting with "CONFIG_"', TOK_NONE)
299 self.get_token()
300 return self.data.do_var(val[7:])
301 else:
302 raise KconfigParserError(self, 'Expected identifier')
303
304 # assignment: var EQUAL y_or_n
305 def parse_assignment(self):
306 var = self.parse_assignment_var()
307 if self.tok != TOK_EQUAL:
308 raise KconfigParserError(self, 'Expected "="')
309 self.get_token()
310 self.data.do_assignment(var, self.parse_y_or_n())
311
312 # primary: NOT primary
313 # | LPAREN expr RPAREN
314 # | var
315 def parse_primary(self):
316 if self.tok == TOK_NOT:
317 self.get_token()
Paolo Bonzini53167f52019-01-23 14:55:57 +0800318 val = ~self.parse_primary()
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800319 elif self.tok == TOK_LPAREN:
320 self.get_token()
Paolo Bonzini53167f52019-01-23 14:55:57 +0800321 val = self.parse_expr()
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800322 if self.tok != TOK_RPAREN:
323 raise KconfigParserError(self, 'Expected ")"')
324 self.get_token()
325 elif self.tok == TOK_ID:
Paolo Bonzini53167f52019-01-23 14:55:57 +0800326 val = self.parse_var()
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800327 else:
328 raise KconfigParserError(self, 'Expected "!" or "(" or identifier')
Paolo Bonzini53167f52019-01-23 14:55:57 +0800329 return val
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800330
331 # disj: primary (OR primary)*
332 def parse_disj(self):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800333 lhs = self.parse_primary()
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800334 while self.tok == TOK_OR:
335 self.get_token()
Paolo Bonzini53167f52019-01-23 14:55:57 +0800336 lhs = lhs | self.parse_primary()
337 return lhs
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800338
339 # expr: disj (AND disj)*
340 def parse_expr(self):
Paolo Bonzini53167f52019-01-23 14:55:57 +0800341 lhs = self.parse_disj()
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800342 while self.tok == TOK_AND:
343 self.get_token()
Paolo Bonzini53167f52019-01-23 14:55:57 +0800344 lhs = lhs & self.parse_disj()
345 return lhs
Paolo Bonzinid4fdcca2019-01-23 14:55:56 +0800346
347 # condition: IF expr
348 # | empty
349 def parse_condition(self):
350 if self.tok == TOK_IF:
351 self.get_token()
352 return self.parse_expr()
353 else:
354 return None
355
356 # property: DEFAULT y_or_n condition
357 # | DEPENDS ON expr
358 # | SELECT var condition
359 # | BOOL
360 def parse_property(self, var):
361 if self.tok == TOK_DEFAULT:
362 self.get_token()
363 val = self.parse_y_or_n()
364 cond = self.parse_condition()
365 self.data.do_default(var, val, cond)
366 elif self.tok == TOK_DEPENDS:
367 self.get_token()
368 if self.tok != TOK_ON:
369 raise KconfigParserError(self, 'Expected "on"')
370 self.get_token()
371 self.data.do_depends_on(var, self.parse_expr())
372 elif self.tok == TOK_SELECT:
373 self.get_token()
374 symbol = self.parse_var()
375 cond = self.parse_condition()
376 self.data.do_select(var, symbol, cond)
377 elif self.tok == TOK_IMPLY:
378 self.get_token()
379 symbol = self.parse_var()
380 cond = self.parse_condition()
381 self.data.do_imply(var, symbol, cond)
382 elif self.tok == TOK_BOOL:
383 self.get_token()
384 else:
385 raise KconfigParserError(self, 'Error in recursive descent?')
386
387 # properties: properties property
388 # | /* empty */
389 def parse_properties(self, var):
390 had_default = False
391 while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
392 self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
393 self.tok == TOK_IMPLY:
394 self.parse_property(var)
395 self.data.do_default(var, False)
396
397 # for nicer error message
398 if self.tok != TOK_SOURCE and self.tok != TOK_CONFIG and \
399 self.tok != TOK_ID and self.tok != TOK_EOF:
400 raise KconfigParserError(self, 'expected "source", "config", identifier, '
401 + '"default", "depends on", "imply" or "select"')
402
403 # declaration: config var properties
404 def parse_declaration(self):
405 if self.tok == TOK_CONFIG:
406 self.get_token()
407 var = self.parse_var()
408 self.data.do_declaration(var)
409 self.parse_properties(var)
410 else:
411 raise KconfigParserError(self, 'Error in recursive descent?')
412
413 # clause: SOURCE
414 # | declaration
415 # | assignment
416 def parse_clause(self):
417 if self.tok == TOK_SOURCE:
418 val = self.val
419 self.get_token()
420 self.do_include(val)
421 elif self.tok == TOK_CONFIG:
422 self.parse_declaration()
423 elif self.tok == TOK_ID:
424 self.parse_assignment()
425 else:
426 raise KconfigParserError(self, 'expected "source", "config" or identifier')
427
428 # config: clause+ EOF
429 def parse_config(self):
430 while self.tok != TOK_EOF:
431 self.parse_clause()
432 return self.data
433
434 # scanner -----
435
436 def get_token(self):
437 while True:
438 self.tok = self.src[self.cursor]
439 self.pos = self.cursor
440 self.cursor += 1
441
442 self.val = None
443 self.tok = self.scan_token()
444 if self.tok is not None:
445 return
446
447 def check_keyword(self, rest):
448 if not self.src.startswith(rest, self.cursor):
449 return False
450 length = len(rest)
451 if self.src[self.cursor + length].isalnum() or self.src[self.cursor + length] == '|':
452 return False
453 self.cursor += length
454 return True
455
456 def scan_token(self):
457 if self.tok == '#':
458 self.cursor = self.src.find('\n', self.cursor)
459 return None
460 elif self.tok == '=':
461 return TOK_EQUAL
462 elif self.tok == '(':
463 return TOK_LPAREN
464 elif self.tok == ')':
465 return TOK_RPAREN
466 elif self.tok == '&' and self.src[self.pos+1] == '&':
467 self.cursor += 1
468 return TOK_AND
469 elif self.tok == '|' and self.src[self.pos+1] == '|':
470 self.cursor += 1
471 return TOK_OR
472 elif self.tok == '!':
473 return TOK_NOT
474 elif self.tok == 'd' and self.check_keyword("epends"):
475 return TOK_DEPENDS
476 elif self.tok == 'o' and self.check_keyword("n"):
477 return TOK_ON
478 elif self.tok == 's' and self.check_keyword("elect"):
479 return TOK_SELECT
480 elif self.tok == 'i' and self.check_keyword("mply"):
481 return TOK_IMPLY
482 elif self.tok == 'c' and self.check_keyword("onfig"):
483 return TOK_CONFIG
484 elif self.tok == 'd' and self.check_keyword("efault"):
485 return TOK_DEFAULT
486 elif self.tok == 'b' and self.check_keyword("ool"):
487 return TOK_BOOL
488 elif self.tok == 'i' and self.check_keyword("f"):
489 return TOK_IF
490 elif self.tok == 'y' and self.check_keyword(""):
491 return TOK_Y
492 elif self.tok == 'n' and self.check_keyword(""):
493 return TOK_N
494 elif (self.tok == 's' and self.check_keyword("ource")) or \
495 self.tok == 'i' and self.check_keyword("nclude"):
496 # source FILENAME
497 # include FILENAME
498 while self.src[self.cursor].isspace():
499 self.cursor += 1
500 start = self.cursor
501 self.cursor = self.src.find('\n', self.cursor)
502 self.val = self.src[start:self.cursor]
503 return TOK_SOURCE
504 elif self.tok.isalpha():
505 # identifier
506 while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_':
507 self.cursor += 1
508 self.val = self.src[self.pos:self.cursor]
509 return TOK_ID
510 elif self.tok == '\n':
511 if self.cursor == len(self.src):
512 return TOK_EOF
513 self.line += 1
514 self.line_pos = self.cursor
515 elif not self.tok.isspace():
516 raise KconfigParserError(self, 'invalid input')
517
518 return None
519
520if __name__ == '__main__':
521 fname = len(sys.argv) > 1 and sys.argv[1] or 'Kconfig.test'
Paolo Bonzini53167f52019-01-23 14:55:57 +0800522 data = KconfigParser.parse(open(fname, 'r'))
523 for i in data.clauses:
524 print i