master
py 736 lines 25.3 KB
Raw
1 #!/usr/bin/env python3
2 #
3 # Mini-Kconfig parser
4 #
5 # Copyright (c) 2015 Red Hat Inc.
6 #
7 # Authors:
8 # Paolo Bonzini <pbonzini@redhat.com>
9 #
10 # This work is licensed under the terms of the GNU GPL, version 2
11 # or, at your option, any later version. See the COPYING file in
12 # the top-level directory.
13
14 from __future__ import annotations
15
16 import os
17 import random
18 import re
19 import sys
20 import typing as T
21 from dataclasses import dataclass
22
23 __all__ = [ 'KconfigDataError', 'KconfigParserError',
24 'KconfigData', 'KconfigParser' ,
25 'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
26
27 Mangler = T.Callable[[bool], bool]
28
29 @dataclass
30 class IncludeInfo:
31 file: str
32 line: int
33 parent: IncludeInfo | None
34
35 def __iter__(self) -> T.Iterator[str]:
36 inf: IncludeInfo | None = self
37 while inf is not None:
38 yield "%s:%d" % (inf.file, inf.line)
39 inf = inf.parent
40
41 def error_path(self) -> str:
42 res = ""
43 for loc in self:
44 res = "In file included from %s:\n" % loc + res
45 return res
46
47 def debug_print(*args: object) -> None:
48 #print('# ' + (' '.join(str(x) for x in args)))
49 pass
50
51 # -------------------------------------------
52 # KconfigData implements the Kconfig semantics. For now it can only
53 # detect undefined symbols, i.e. symbols that were referenced in
54 # assignments or dependencies but were not declared with "config FOO".
55 #
56 # Semantic actions are represented by methods called do_*. The do_var
57 # method return the semantic value of a variable (which right now is
58 # just its name).
59 # -------------------------------------------
60
61 class KconfigDataError(Exception):
62 def __init__(self, msg: str) -> None:
63 self.msg = msg
64
65 def __str__(self) -> str:
66 return self.msg
67
68 allyesconfig: Mangler = lambda x: True
69 allnoconfig: Mangler = lambda x: False
70 defconfig: Mangler = lambda x: x
71 randconfig: Mangler = lambda x: random.randint(0, 1) == 1
72
73 class KconfigData:
74 class Expr:
75 def __and__(self, rhs: KconfigData.Expr) -> KconfigData.Expr:
76 return KconfigData.AND(self, rhs)
77 def __or__(self, rhs: KconfigData.Expr) -> KconfigData.Expr:
78 return KconfigData.OR(self, rhs)
79 def __invert__(self) -> KconfigData.Expr:
80 return KconfigData.NOT(self)
81
82 # Abstract methods
83 def add_edges_to(self, var: KconfigData.Var) -> None:
84 pass
85 def evaluate(self) -> bool:
86 assert False
87
88 class AND(Expr):
89 def __init__(self, lhs: KconfigData.Expr, rhs: KconfigData.Expr) -> None:
90 self.lhs = lhs
91 self.rhs = rhs
92 def __str__(self) -> str:
93 return "(%s && %s)" % (self.lhs, self.rhs)
94
95 def add_edges_to(self, var: KconfigData.Var) -> None:
96 self.lhs.add_edges_to(var)
97 self.rhs.add_edges_to(var)
98 def evaluate(self) -> bool:
99 return self.lhs.evaluate() and self.rhs.evaluate()
100
101 class OR(Expr):
102 def __init__(self, lhs: KconfigData.Expr, rhs: KconfigData.Expr) -> None:
103 self.lhs = lhs
104 self.rhs = rhs
105 def __str__(self) -> str:
106 return "(%s || %s)" % (self.lhs, self.rhs)
107
108 def add_edges_to(self, var: KconfigData.Var) -> None:
109 self.lhs.add_edges_to(var)
110 self.rhs.add_edges_to(var)
111 def evaluate(self) -> bool:
112 return self.lhs.evaluate() or self.rhs.evaluate()
113
114 class NOT(Expr):
115 def __init__(self, lhs: KconfigData.Expr) -> None:
116 self.lhs = lhs
117 def __str__(self) -> str:
118 return "!%s" % (self.lhs)
119
120 def add_edges_to(self, var: KconfigData.Var) -> None:
121 self.lhs.add_edges_to(var)
122 def evaluate(self) -> bool:
123 return not self.lhs.evaluate()
124
125 class Var(Expr):
126 def __init__(self, name: str) -> None:
127 self.name = name
128 self.value: bool | None = None
129 self.outgoing: set[KconfigData.Var] = set()
130 self.clauses_for_var: list[KconfigData.Clause] = []
131 def __str__(self) -> str:
132 return self.name
133
134 def has_value(self) -> bool:
135 return self.value is not None
136 def set_value(self, val: bool, clause: KconfigData.Clause) -> None:
137 self.clauses_for_var.append(clause)
138 if self.has_value() and self.value != val:
139 print("The following clauses were found for " + self.name, file=sys.stderr)
140 for i in self.clauses_for_var:
141 print(" " + str(i), file=sys.stderr)
142 raise KconfigDataError('contradiction between clauses when setting %s' % self)
143 debug_print("=> %s is now %s" % (self.name, val))
144 self.value = val
145
146 # depth first search of the dependency graph
147 def dfs(self, visited: set[KconfigData.Var],
148 f: T.Callable[[KconfigData.Var], None]) -> None:
149 if self in visited:
150 return
151 visited.add(self)
152 for v in self.outgoing:
153 v.dfs(visited, f)
154 f(self)
155
156 def add_edges_to(self, var: KconfigData.Var) -> None:
157 self.outgoing.add(var)
158 def evaluate(self) -> bool:
159 if not self.has_value():
160 raise KconfigDataError('cycle found including %s' % self)
161 assert self.value is not None
162 return self.value
163
164 class Clause:
165 def __init__(self, dest: KconfigData.Var) -> None:
166 self.dest = dest
167 def priority(self) -> int:
168 return 0
169 def process(self) -> None:
170 pass
171
172 class AssignmentClause(Clause):
173 def __init__(self, dest: KconfigData.Var, value: bool) -> None:
174 KconfigData.Clause.__init__(self, dest)
175 self.value = value
176 def __str__(self) -> str:
177 return "CONFIG_%s=%s" % (self.dest, 'y' if self.value else 'n')
178
179 def process(self) -> None:
180 self.dest.set_value(self.value, self)
181
182 class DefaultClause(Clause):
183 def __init__(self, dest: KconfigData.Var, value: bool,
184 cond: KconfigData.Expr | None = None) -> None:
185 KconfigData.Clause.__init__(self, dest)
186 self.value = value
187 self.cond = cond
188 if self.cond is not None:
189 self.cond.add_edges_to(self.dest)
190 def __str__(self) -> str:
191 value = 'y' if self.value else 'n'
192 if self.cond is None:
193 return "config %s default %s" % (self.dest, value)
194 else:
195 return "config %s default %s if %s" % (self.dest, value, self.cond)
196
197 def priority(self) -> int:
198 # Defaults are processed just before leaving the variable
199 return -1
200 def process(self) -> None:
201 if not self.dest.has_value() and \
202 (self.cond is None or self.cond.evaluate()):
203 self.dest.set_value(self.value, self)
204
205 class DependsOnClause(Clause):
206 def __init__(self, dest: KconfigData.Var, expr: KconfigData.Expr) -> None:
207 KconfigData.Clause.__init__(self, dest)
208 self.expr = expr
209 self.expr.add_edges_to(self.dest)
210 def __str__(self) -> str:
211 return "config %s depends on %s" % (self.dest, self.expr)
212
213 def process(self) -> None:
214 if not self.expr.evaluate():
215 self.dest.set_value(False, self)
216
217 class SelectClause(Clause):
218 def __init__(self, dest: KconfigData.Var, cond: KconfigData.Expr) -> None:
219 KconfigData.Clause.__init__(self, dest)
220 self.cond = cond
221 self.cond.add_edges_to(self.dest)
222 def __str__(self) -> str:
223 return "select %s if %s" % (self.dest, self.cond)
224
225 def process(self) -> None:
226 if self.cond.evaluate():
227 self.dest.set_value(True, self)
228
229 def __init__(self, value_mangler: Mangler = defconfig) -> None:
230 self.value_mangler = value_mangler
231 self.previously_included: list[str] = []
232 self.defined_vars: set[str] = set()
233 self.referenced_vars: dict[str, KconfigData.Var] = {}
234 self.clauses: list[KconfigData.Clause] = []
235
236 # semantic analysis -------------
237
238 def check_undefined(self) -> bool:
239 undef = False
240 for i in self.referenced_vars:
241 if i not in self.defined_vars:
242 print("undefined symbol %s" % (i), file=sys.stderr)
243 undef = True
244 return undef
245
246 def compute_config(self) -> dict[str, bool]:
247 if self.check_undefined():
248 raise KconfigDataError("there were undefined symbols")
249
250 debug_print("Input:")
251 for clause in self.clauses:
252 debug_print(clause)
253
254 debug_print("\nDependency graph:")
255 for source, edges in self.referenced_vars.items():
256 debug_print(source, "->", [str(x) for x in edges.outgoing])
257
258 # The reverse of the depth-first order is the topological sort
259 dfo: dict[KconfigData.Var, int] = {}
260 visited: set[KconfigData.Var] = set()
261 debug_print("\n")
262 def visit_fn(var: KconfigData.Var) -> None:
263 debug_print(var, "has DFS number", len(dfo))
264 dfo[var] = len(dfo)
265
266 for name, v in self.referenced_vars.items():
267 self.do_default(v, False)
268 v.dfs(visited, visit_fn)
269
270 # Put higher DFS numbers and higher priorities first. This
271 # places the clauses in topological order and places defaults
272 # after assignments and dependencies.
273 self.clauses.sort(key=lambda x: (-dfo[x.dest], -x.priority()))
274
275 debug_print("\nSorted clauses:")
276 for clause in self.clauses:
277 debug_print(clause)
278 clause.process()
279
280 debug_print("")
281 values: dict[str, bool] = {}
282 for name, v in self.referenced_vars.items():
283 debug_print("Evaluating", name)
284 values[name] = v.evaluate()
285
286 return values
287
288 # semantic actions -------------
289
290 def do_declaration(self, var: KconfigData.Var) -> None:
291 if var.name in self.defined_vars:
292 raise KconfigDataError('variable "%s" defined twice' % var.name)
293 self.defined_vars.add(var.name)
294
295 # var is a string with the variable's name.
296 def do_var(self, var: str) -> KconfigData.Var:
297 if var in self.referenced_vars:
298 return self.referenced_vars[var]
299
300 var_obj = self.referenced_vars[var] = KconfigData.Var(var)
301 return var_obj
302
303 def do_assignment(self, var: KconfigData.Var, val: bool) -> None:
304 self.clauses.append(KconfigData.AssignmentClause(var, val))
305
306 def do_cmdline_assignment(self, var: str, val: bool) -> None:
307 assert var.startswith("CONFIG_")
308 self.do_assignment(self.do_var(var[7:]), val)
309
310 def do_default(self, var: KconfigData.Var, val: bool,
311 cond: KconfigData.Expr | None = None) -> None:
312 val = self.value_mangler(val)
313 self.clauses.append(KconfigData.DefaultClause(var, val, cond))
314
315 def do_depends_on(self, var: KconfigData.Var,
316 expr: KconfigData.Expr) -> None:
317 self.clauses.append(KconfigData.DependsOnClause(var, expr))
318
319 def do_select(self, var: KconfigData.Var, symbol: KconfigData.Var,
320 cond: KconfigData.Expr | None = None) -> None:
321 cond = (cond & var) if cond is not None else var
322 self.clauses.append(KconfigData.SelectClause(symbol, cond))
323
324 def do_imply(self, var: KconfigData.Var, symbol: KconfigData.Var,
325 cond: KconfigData.Expr | None = None) -> None:
326 # "config X imply Y [if COND]" is the same as
327 # "config Y default y if X [&& COND]"
328 cond = (cond & var) if cond is not None else var
329 self.do_default(symbol, True, cond)
330
331 # -------------------------------------------
332 # KconfigParser implements a recursive descent parser for (simplified)
333 # Kconfig syntax.
334 # -------------------------------------------
335
336 # tokens table
337 TOKENS: dict[int, str] = {}
338 TOK_NONE = -1
339 TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("'
340 TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"'
341 TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="'
342 TOK_AND = 3; TOKENS[TOK_AND] = '"&&"'
343 TOK_OR = 4; TOKENS[TOK_OR] = '"||"'
344 TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"'
345 TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"'
346 TOK_ON = 7; TOKENS[TOK_ON] = '"on"'
347 TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"'
348 TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"'
349 TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"'
350 TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"'
351 TOK_Y = 12; TOKENS[TOK_Y] = '"y"'
352 TOK_N = 13; TOKENS[TOK_N] = '"n"'
353 TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"'
354 TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"'
355 TOK_IF = 16; TOKENS[TOK_IF] = '"if"'
356 TOK_ID = 17; TOKENS[TOK_ID] = 'identifier'
357 TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file'
358
359 class KconfigParserError(Exception):
360 def __init__(self, parser: KconfigParser, msg: str,
361 tok: int | str | None = None) -> None:
362 self.loc = parser.location()
363 tok = tok if tok is not None else parser.tok
364 if tok != TOK_NONE:
365 location = TOKENS[tok] if isinstance(tok, int) else '"%s"' % tok
366 msg = '%s before %s' % (msg, location)
367 self.msg = msg
368
369 def __str__(self) -> str:
370 return "%s: %s" % (self.loc, self.msg)
371
372 class KconfigParser:
373
374 @classmethod
375 def parse(cls, fp: T.TextIO, data: KconfigData, incl_info: IncludeInfo | None = None) -> None:
376 cls(fp, data, incl_info).parse_config()
377
378 def __init__(self, fp: T.TextIO, data: KconfigData, incl_info: IncludeInfo | None = None):
379 self.data = data
380 self.incl_info = incl_info
381 self.abs_fname = os.path.abspath(fp.name)
382 self.fname = fp.name
383 self.data.previously_included.append(self.abs_fname)
384
385 src = fp.read()
386 if src == '' or src[-1] != '\n':
387 src += '\n'
388 self.src = src
389 self.cursor: int = 0
390 self.line: int = 1
391 self.line_pos: int = 0
392 self.pos: int = 0
393 self.tok: int = TOK_NONE
394 self.val: str | None = None
395 self.get_token()
396
397 # file management -----
398
399 def location(self) -> str:
400 col = 1
401 for ch in self.src[self.line_pos:self.pos]:
402 if ch == '\t':
403 col += 8 - ((col - 1) % 8)
404 else:
405 col += 1
406 inf = self.incl_info
407 incl_chain = inf.error_path() if inf is not None else ""
408 return '%s%s:%d:%d' % (incl_chain, self.fname, self.line, col)
409
410 def do_include(self, include: str) -> None:
411 incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
412 include)
413 # catch inclusion cycle
414 inf = self.incl_info
415 while inf:
416 if incl_abs_fname == os.path.abspath(inf.file):
417 raise KconfigParserError(self, "Inclusion loop for %s"
418 % include)
419 inf = inf.parent
420
421 # skip multiple include of the same file
422 if incl_abs_fname in self.data.previously_included:
423 return
424 try:
425 try:
426 fp = open(incl_abs_fname, 'rt', encoding='utf-8')
427 except IOError as e:
428 raise KconfigParserError(self, '%s: %s' % (e.strerror, include))
429
430 inner = IncludeInfo(file=self.fname, line=self.line, parent=self.incl_info)
431 type(self).parse(fp, self.data, inner)
432 finally:
433 fp.close()
434
435 # recursive descent parser -----
436
437 # y_or_n: Y | N
438 def parse_y_or_n(self) -> bool:
439 if self.tok == TOK_Y:
440 self.get_token()
441 return True
442 if self.tok == TOK_N:
443 self.get_token()
444 return False
445 raise KconfigParserError(self, 'Expected "y" or "n"')
446
447 # var: ID
448 def parse_var(self) -> KconfigData.Var:
449 if self.tok != TOK_ID:
450 raise KconfigParserError(self, 'Expected identifier')
451 val = self.val
452 assert val is not None
453 self.get_token()
454 return self.data.do_var(val)
455
456 # assignment_var: ID (starting with "CONFIG_")
457 def parse_assignment_var(self) -> KconfigData.Var:
458 if self.tok != TOK_ID:
459 raise KconfigParserError(self, 'Expected identifier')
460 val = self.val
461 assert val is not None
462 if not val.startswith("CONFIG_"):
463 raise KconfigParserError(self,
464 'Expected identifier starting with "CONFIG_"', TOK_NONE)
465 self.get_token()
466 return self.data.do_var(val[7:])
467
468 # assignment: var EQUAL y_or_n
469 def parse_assignment(self) -> None:
470 var = self.parse_assignment_var()
471 if self.tok != TOK_EQUAL:
472 raise KconfigParserError(self, 'Expected "="')
473 self.get_token()
474 self.data.do_assignment(var, self.parse_y_or_n())
475
476 # primary: NOT primary
477 # | LPAREN expr RPAREN
478 # | var
479 def parse_primary(self) -> KconfigData.Expr:
480 if self.tok == TOK_NOT:
481 self.get_token()
482 val = ~self.parse_primary()
483 elif self.tok == TOK_LPAREN:
484 self.get_token()
485 val = self.parse_expr()
486 if self.tok != TOK_RPAREN:
487 raise KconfigParserError(self, 'Expected ")"')
488 self.get_token()
489 elif self.tok == TOK_ID:
490 val = self.parse_var()
491 else:
492 raise KconfigParserError(self, 'Expected "!" or "(" or identifier')
493 return val
494
495 # disj: primary (OR primary)*
496 def parse_disj(self) -> KconfigData.Expr:
497 lhs = self.parse_primary()
498 while self.tok == TOK_OR:
499 self.get_token()
500 lhs = lhs | self.parse_primary()
501 return lhs
502
503 # expr: disj (AND disj)*
504 def parse_expr(self) -> KconfigData.Expr:
505 lhs = self.parse_disj()
506 while self.tok == TOK_AND:
507 self.get_token()
508 lhs = lhs & self.parse_disj()
509 return lhs
510
511 # condition: IF expr
512 # | empty
513 def parse_condition(self) -> KconfigData.Expr | None:
514 if self.tok != TOK_IF:
515 return None
516 self.get_token()
517 return self.parse_expr()
518
519 # property: DEFAULT y_or_n condition
520 # | DEPENDS ON expr
521 # | SELECT var condition
522 # | BOOL
523 def parse_property(self, var: KconfigData.Var) -> None:
524 if self.tok == TOK_DEFAULT:
525 self.get_token()
526 val = self.parse_y_or_n()
527 cond = self.parse_condition()
528 self.data.do_default(var, val, cond)
529 elif self.tok == TOK_DEPENDS:
530 self.get_token()
531 if self.tok != TOK_ON:
532 raise KconfigParserError(self, 'Expected "on"')
533 self.get_token()
534 self.data.do_depends_on(var, self.parse_expr())
535 elif self.tok == TOK_SELECT:
536 self.get_token()
537 symbol = self.parse_var()
538 cond = self.parse_condition()
539 self.data.do_select(var, symbol, cond)
540 elif self.tok == TOK_IMPLY:
541 self.get_token()
542 symbol = self.parse_var()
543 cond = self.parse_condition()
544 self.data.do_imply(var, symbol, cond)
545 elif self.tok == TOK_BOOL:
546 self.get_token()
547 else:
548 raise KconfigParserError(self, 'Error in recursive descent?')
549
550 # properties: properties property
551 # | /* empty */
552 def parse_properties(self, var: KconfigData.Var) -> None:
553 while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
554 self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
555 self.tok == TOK_IMPLY:
556 self.parse_property(var)
557
558 # for nicer error message
559 if self.tok != TOK_SOURCE and self.tok != TOK_CONFIG and \
560 self.tok != TOK_ID and self.tok != TOK_EOF:
561 raise KconfigParserError(self, 'expected "source", "config", identifier, '
562 + '"default", "depends on", "imply" or "select"')
563
564 # declaration: config var properties
565 def parse_declaration(self) -> None:
566 if self.tok == TOK_CONFIG:
567 self.get_token()
568 var = self.parse_var()
569 self.data.do_declaration(var)
570 self.parse_properties(var)
571 else:
572 raise KconfigParserError(self, 'Error in recursive descent?')
573
574 # clause: SOURCE
575 # | declaration
576 # | assignment
577 def parse_clause(self) -> None:
578 if self.tok == TOK_SOURCE:
579 val = self.val
580 assert val is not None
581 self.get_token()
582 self.do_include(val)
583 elif self.tok == TOK_CONFIG:
584 self.parse_declaration()
585 elif self.tok == TOK_ID:
586 self.parse_assignment()
587 else:
588 raise KconfigParserError(self, 'expected "source", "config" or identifier')
589
590 # config: clause+ EOF
591 def parse_config(self) -> KconfigData:
592 while self.tok != TOK_EOF:
593 self.parse_clause()
594 return self.data
595
596 # scanner -----
597
598 def get_token(self) -> None:
599 assert self.src is not None
600 while True:
601 ch = self.src[self.cursor]
602 self.pos = self.cursor
603 self.cursor += 1
604
605 self.val = None
606 tok = self.scan_token(ch)
607 if tok is not None:
608 self.tok = tok
609 return
610
611 def check_keyword(self, rest: str) -> bool:
612 assert self.src is not None
613 if not self.src.startswith(rest, self.cursor):
614 return False
615 length = len(rest)
616 if self.src[self.cursor + length].isalnum() or self.src[self.cursor + length] == '_':
617 return False
618 self.cursor += length
619 return True
620
621 def scan_token(self, ch: str) -> int | None:
622 assert self.src is not None
623 if ch == '#':
624 self.cursor = self.src.find('\n', self.cursor)
625 return None
626 if ch == '=':
627 return TOK_EQUAL
628 if ch == '(':
629 return TOK_LPAREN
630 if ch == ')':
631 return TOK_RPAREN
632 if ch == '&' and self.src[self.pos+1] == '&':
633 self.cursor += 1
634 return TOK_AND
635 if ch == '|' and self.src[self.pos+1] == '|':
636 self.cursor += 1
637 return TOK_OR
638 if ch == '!':
639 return TOK_NOT
640 if ch == 'd' and self.check_keyword("epends"):
641 return TOK_DEPENDS
642 if ch == 'o' and self.check_keyword("n"):
643 return TOK_ON
644 if ch == 's' and self.check_keyword("elect"):
645 return TOK_SELECT
646 if ch == 'i' and self.check_keyword("mply"):
647 return TOK_IMPLY
648 if ch == 'c' and self.check_keyword("onfig"):
649 return TOK_CONFIG
650 if ch == 'd' and self.check_keyword("efault"):
651 return TOK_DEFAULT
652 if ch == 'b' and self.check_keyword("ool"):
653 return TOK_BOOL
654 if ch == 'i' and self.check_keyword("f"):
655 return TOK_IF
656 if ch == 'y' and self.check_keyword(""):
657 return TOK_Y
658 if ch == 'n' and self.check_keyword(""):
659 return TOK_N
660 if (ch == 's' and self.check_keyword("ource")) or \
661 ch == 'i' and self.check_keyword("nclude"):
662 # source FILENAME
663 # include FILENAME
664 while self.src[self.cursor].isspace():
665 self.cursor += 1
666 start = self.cursor
667 self.cursor = self.src.find('\n', self.cursor)
668 self.val = self.src[start:self.cursor]
669 return TOK_SOURCE
670 if ch.isalnum():
671 # identifier
672 while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_':
673 self.cursor += 1
674 self.val = self.src[self.pos:self.cursor]
675 return TOK_ID
676 if ch == '\n':
677 if self.cursor == len(self.src):
678 return TOK_EOF
679 self.line += 1
680 self.line_pos = self.cursor
681 return None
682 if ch.isspace():
683 return None
684
685 raise KconfigParserError(self, 'invalid input', ch)
686
687
688 def main() -> None:
689 argv = sys.argv
690 mode: Mangler = defconfig
691 if len(sys.argv) > 1:
692 if argv[1] == '--defconfig':
693 del argv[1]
694 elif argv[1] == '--randconfig':
695 random.seed()
696 mode = randconfig
697 del argv[1]
698 elif argv[1] == '--allyesconfig':
699 mode = allyesconfig
700 del argv[1]
701 elif argv[1] == '--allnoconfig':
702 mode = allnoconfig
703 del argv[1]
704
705 if len(argv) == 1:
706 print ("%s: at least one argument is required" % argv[0], file=sys.stderr)
707 sys.exit(1)
708
709 if argv[1].startswith('-'):
710 print ("%s: invalid option %s" % (argv[0], argv[1]), file=sys.stderr)
711 sys.exit(1)
712
713 data = KconfigData(mode)
714 external_vars: set[str] = set()
715 for arg in argv[3:]:
716 m = re.match(r'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg)
717 if m is not None:
718 name, value = m.groups()
719 data.do_cmdline_assignment(name, value == 'y')
720 external_vars.add(name[7:])
721 else:
722 with open(arg, 'rt', encoding='utf-8') as fp:
723 KconfigParser.parse(fp, data)
724
725 config = data.compute_config()
726 for key in sorted(config.keys()):
727 if key not in external_vars and config[key]:
728 print ('CONFIG_%s=y' % key)
729
730 deps = open(argv[2], 'wt', encoding='utf-8')
731 for fname in data.previously_included:
732 print ('%s: %s' % (argv[1], fname), file=deps)
733 deps.close()
734
735 if __name__ == '__main__':
736 main()