From nobody Sat Sep 28 15:02:43 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=intel.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1548227795964707.1149385802622; Tue, 22 Jan 2019 23:16:35 -0800 (PST) Received: from localhost ([127.0.0.1]:56989 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1gmCm2-0000Tj-PV for importer@patchew.org; Wed, 23 Jan 2019 02:16:34 -0500 Received: from eggs.gnu.org ([209.51.188.92]:59913) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1gmCVk-0004UD-Ot for qemu-devel@nongnu.org; Wed, 23 Jan 2019 01:59:47 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1gmCVg-0003Rj-94 for qemu-devel@nongnu.org; Wed, 23 Jan 2019 01:59:44 -0500 Received: from mga18.intel.com ([134.134.136.126]:36919) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1gmCVg-0002Mh-0s for qemu-devel@nongnu.org; Wed, 23 Jan 2019 01:59:40 -0500 Received: from orsmga001.jf.intel.com ([10.7.209.18]) by orsmga106.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 22 Jan 2019 22:59:10 -0800 Received: from he.bj.intel.com ([10.238.157.85]) by orsmga001.jf.intel.com with ESMTP; 22 Jan 2019 22:59:07 -0800 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.56,510,1539673200"; d="scan'208";a="129980912" From: Yang Zhong To: qemu-devel@nongnu.org Date: Wed, 23 Jan 2019 14:55:56 +0800 Message-Id: <20190123065618.3520-23-yang.zhong@intel.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190123065618.3520-1-yang.zhong@intel.com> References: <20190123065618.3520-1-yang.zhong@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 134.134.136.126 Subject: [Qemu-devel] [RFC PATCH v4 22/44] minikconfig: add parser skeleton X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: yang.zhong@intel.com, peter.maydell@linaro.org, thuth@redhat.com, ehabkost@redhat.com, pbonzini@redhat.com, sameo@linux.intel.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" From: Paolo Bonzini This implements a scanner and recursive descent parser for Kconfig-like configuration files. The only "action" of the parser is for now to detect undefined variables and process include files. The main differences between Kconfig and this are: * only the "bool" type is supported * variables can only be defined once * choices are not supported (but they could be added as syntactic sugar for multiple Boolean values) * menus and other graphical concepts (prompts, help text) are not supported * assignments ("CONFIG_FOO=3Dy", "CONFIG_FOO=3Dn") are parsed as part of the Kconfig language, not as a separate file. The idea was originally by =C3=81kos Kov=C3=A1cs, but I could not find his implementation so I had to redo it. Signed-off-by: Paolo Bonzini --- scripts/minikconf.py | 423 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 scripts/minikconf.py diff --git a/scripts/minikconf.py b/scripts/minikconf.py new file mode 100644 index 0000000000..fb39e35d6a --- /dev/null +++ b/scripts/minikconf.py @@ -0,0 +1,423 @@ +# +# Mini-Kconfig parser +# +# Copyright (c) 2015 Red Hat Inc. +# +# Authors: +# Paolo Bonzini +# +# This work is licensed under the terms of the GNU GPL, version 2 +# or, at your option, any later version. See the COPYING file in +# the top-level directory. + +import os +import sys + +__all__ =3D [ 'KconfigParserError', 'KconfigData', 'KconfigParser' ] + +# ------------------------------------------- +# KconfigData implements the Kconfig semantics. For now it can only +# detect undefined symbols, i.e. symbols that were referenced in +# assignments or dependencies but were not declared with "config FOO". +# +# Semantic actions are represented by methods called do_*. The do_var +# method return the semantic value of a variable (which right now is +# just its name). +# ------------------------------------------- + +class KconfigData: + def __init__(self): + self.previously_included =3D [] + self.incl_info =3D None + self.defined_vars =3D set() + self.referenced_vars =3D set() + + # semantic analysis ------------- + + def check_undefined(self): + undef =3D False + for i in self.referenced_vars: + if not (i in self.defined_vars): + print "undefined symbol %s" % (i) + undef =3D True + return undef + + # semantic actions ------------- + + def do_declaration(self, var): + if (var in self.defined_vars): + raise Exception('variable "' + var + '" defined twice') + + self.defined_vars.add(var) + + # var is a string with the variable's name. + # + # For now this just returns the variable's name itself. + def do_var(self, var): + self.referenced_vars.add(var) + return var + + def do_assignment(self, var, val): + pass + + def do_default(self, var, val, cond=3DNone): + pass + + def do_depends_on(self, var, expr): + pass + + def do_select(self, var, symbol, cond=3DNone): + pass + +# ------------------------------------------- +# KconfigParser implements a recursive descent parser for (simplified) +# Kconfig syntax. +# ------------------------------------------- + +# tokens table +TOKENS =3D {} +TOK_NONE =3D -1 +TOK_LPAREN =3D 0; TOKENS[TOK_LPAREN] =3D '"("'; +TOK_RPAREN =3D 1; TOKENS[TOK_RPAREN] =3D '")"'; +TOK_EQUAL =3D 2; TOKENS[TOK_EQUAL] =3D '"=3D"'; +TOK_AND =3D 3; TOKENS[TOK_AND] =3D '"&&"'; +TOK_OR =3D 4; TOKENS[TOK_OR] =3D '"||"'; +TOK_NOT =3D 5; TOKENS[TOK_NOT] =3D '"!"'; +TOK_DEPENDS =3D 6; TOKENS[TOK_DEPENDS] =3D '"depends"'; +TOK_ON =3D 7; TOKENS[TOK_ON] =3D '"on"'; +TOK_SELECT =3D 8; TOKENS[TOK_SELECT] =3D '"select"'; +TOK_CONFIG =3D 9; TOKENS[TOK_CONFIG] =3D '"config"'; +TOK_DEFAULT =3D 10; TOKENS[TOK_DEFAULT] =3D '"default"'; +TOK_Y =3D 11; TOKENS[TOK_Y] =3D '"y"'; +TOK_N =3D 12; TOKENS[TOK_N] =3D '"n"'; +TOK_SOURCE =3D 13; TOKENS[TOK_SOURCE] =3D '"source"'; +TOK_BOOL =3D 14; TOKENS[TOK_BOOL] =3D '"bool"'; +TOK_IF =3D 15; TOKENS[TOK_IF] =3D '"if"'; +TOK_ID =3D 16; TOKENS[TOK_ID] =3D 'identifier'; +TOK_EOF =3D 17; TOKENS[TOK_EOF] =3D 'end of file'; + +class KconfigParserError(Exception): + def __init__(self, parser, msg, tok=3DNone): + self.loc =3D parser.location() + tok =3D tok or parser.tok + if tok !=3D TOK_NONE: + msg =3D '%s before %s' %(msg, TOKENS[tok]) + self.msg =3D msg + + def __str__(self): + return "%s: %s" % (self.loc, self.msg) + +class KconfigParser: + @classmethod + def parse(self, fp): + data =3D KconfigData() + parser =3D KconfigParser(data) + parser.parse_file(fp) + if data.check_undefined(): + raise KconfigParserError(parser, "there were undefined symbols= ") + + return data + + def __init__(self, data): + self.data =3D data + + def parse_file(self, fp): + self.abs_fname =3D os.path.abspath(fp.name) + self.fname =3D fp.name + self.data.previously_included.append(self.abs_fname) + self.src =3D fp.read() + if self.src =3D=3D '' or self.src[-1] !=3D '\n': + self.src +=3D '\n' + self.cursor =3D 0 + self.line =3D 1 + self.line_pos =3D 0 + self.get_token() + self.parse_config() + + # file management ----- + + def error_path(self): + inf =3D self.data.incl_info + res =3D "" + while inf: + res =3D ("In file included from %s:%d:\n" % (inf['file'], + inf['line'])) + res + inf =3D inf['parent'] + return res + + def location(self): + col =3D 1 + for ch in self.src[self.line_pos:self.pos]: + if ch =3D=3D '\t': + col +=3D 8 - ((col - 1) % 8) + else: + col +=3D 1 + return '%s%s:%d:%d' %(self.error_path(), self.fname, self.line, co= l) + + def do_include(self, include): + incl_abs_fname =3D os.path.join(os.path.dirname(self.abs_fname), + include) + # catch inclusion cycle + inf =3D self.data.incl_info + while inf: + if incl_abs_fname =3D=3D os.path.abspath(inf['file']): + raise KconfigParserError(self, "Inclusion loop for %s" + % include) + inf =3D inf['parent'] + + # skip multiple include of the same file + if incl_abs_fname in self.data.previously_included: + return + try: + fp =3D open(incl_abs_fname, 'r') + except IOError, e: + raise KconfigParserError(self, + '%s: %s' % (e.strerror, include)) + + inf =3D self.data.incl_info + self.data.incl_info =3D { 'file': self.fname, 'line': self.line, + 'parent': inf } + KconfigParser(self.data).parse_file(fp) + self.data.incl_info =3D inf + + # recursive descent parser ----- + + # y_or_n: Y | N + def parse_y_or_n(self): + if self.tok =3D=3D TOK_Y: + self.get_token() + return True + if self.tok =3D=3D TOK_N: + self.get_token() + return False + raise KconfigParserError(self, 'Expected "y" or "n"') + + # var: ID + def parse_var(self): + if self.tok =3D=3D TOK_ID: + val =3D self.val + self.get_token() + return self.data.do_var(val) + else: + raise KconfigParserError(self, 'Expected identifier') + + # assignment_var: ID (starting with "CONFIG_") + def parse_assignment_var(self): + if self.tok =3D=3D TOK_ID: + val =3D self.val + if not val.startswith("CONFIG_"): + raise KconfigParserError(self, + 'Expected identifier starting with "CONFIG_"', = TOK_NONE) + self.get_token() + return self.data.do_var(val[7:]) + else: + raise KconfigParserError(self, 'Expected identifier') + + # assignment: var EQUAL y_or_n + def parse_assignment(self): + var =3D self.parse_assignment_var() + if self.tok !=3D TOK_EQUAL: + raise KconfigParserError(self, 'Expected "=3D"') + self.get_token() + self.data.do_assignment(var, self.parse_y_or_n()) + + # primary: NOT primary + # | LPAREN expr RPAREN + # | var + def parse_primary(self): + if self.tok =3D=3D TOK_NOT: + self.get_token() + self.parse_primary() + elif self.tok =3D=3D TOK_LPAREN: + self.get_token() + self.parse_expr() + if self.tok !=3D TOK_RPAREN: + raise KconfigParserError(self, 'Expected ")"') + self.get_token() + elif self.tok =3D=3D TOK_ID: + self.parse_var() + else: + raise KconfigParserError(self, 'Expected "!" or "(" or identif= ier') + + # disj: primary (OR primary)* + def parse_disj(self): + self.parse_primary() + while self.tok =3D=3D TOK_OR: + self.get_token() + self.parse_primary() + + # expr: disj (AND disj)* + def parse_expr(self): + self.parse_disj() + while self.tok =3D=3D TOK_AND: + self.get_token() + self.parse_disj() + + # condition: IF expr + # | empty + def parse_condition(self): + if self.tok =3D=3D TOK_IF: + self.get_token() + return self.parse_expr() + else: + return None + + # property: DEFAULT y_or_n condition + # | DEPENDS ON expr + # | SELECT var condition + # | BOOL + def parse_property(self, var): + if self.tok =3D=3D TOK_DEFAULT: + self.get_token() + val =3D self.parse_y_or_n() + cond =3D self.parse_condition() + self.data.do_default(var, val, cond) + elif self.tok =3D=3D TOK_DEPENDS: + self.get_token() + if self.tok !=3D TOK_ON: + raise KconfigParserError(self, 'Expected "on"') + self.get_token() + self.data.do_depends_on(var, self.parse_expr()) + elif self.tok =3D=3D TOK_SELECT: + self.get_token() + symbol =3D self.parse_var() + cond =3D self.parse_condition() + self.data.do_select(var, symbol, cond) + elif self.tok =3D=3D TOK_BOOL: + self.get_token() + else: + raise KconfigParserError(self, 'Error in recursive descent?') + + # properties: properties property + # | /* empty */ + def parse_properties(self, var): + had_default =3D False + while self.tok =3D=3D TOK_DEFAULT or self.tok =3D=3D TOK_DEPENDS o= r \ + self.tok =3D=3D TOK_SELECT or self.tok =3D=3D TOK_BOOL: + self.parse_property(var) + self.data.do_default(var, False) + + # for nicer error message + if self.tok !=3D TOK_SOURCE and self.tok !=3D TOK_CONFIG and \ + self.tok !=3D TOK_ID and self.tok !=3D TOK_EOF: + raise KconfigParserError(self, 'expected "source", "config", i= dentifier, ' + + '"default", "depends on" or "select"') + + # declaration: config var properties + def parse_declaration(self): + if self.tok =3D=3D TOK_CONFIG: + self.get_token() + var =3D self.parse_var() + self.data.do_declaration(var) + self.parse_properties(var) + else: + raise KconfigParserError(self, 'Error in recursive descent?') + + # clause: SOURCE + # | declaration + # | assignment + def parse_clause(self): + if self.tok =3D=3D TOK_SOURCE: + val =3D self.val + self.get_token() + self.do_include(val) + elif self.tok =3D=3D TOK_CONFIG: + self.parse_declaration() + elif self.tok =3D=3D TOK_ID: + self.parse_assignment() + else: + raise KconfigParserError(self, 'expected "source", "config" or= identifier') + + # config: clause+ EOF + def parse_config(self): + while self.tok !=3D TOK_EOF: + self.parse_clause() + return self.data + + # scanner ----- + + def get_token(self): + while True: + self.tok =3D self.src[self.cursor] + self.pos =3D self.cursor + self.cursor +=3D 1 + + self.val =3D None + self.tok =3D self.scan_token() + if self.tok is not None: + return + + def check_keyword(self, rest): + if not self.src.startswith(rest, self.cursor): + return False + length =3D len(rest) + if self.src[self.cursor + length].isalnum() or self.src[self.curso= r + length] =3D=3D '|': + return False + self.cursor +=3D length + return True + + def scan_token(self): + if self.tok =3D=3D '#': + self.cursor =3D self.src.find('\n', self.cursor) + return None + elif self.tok =3D=3D '=3D': + return TOK_EQUAL + elif self.tok =3D=3D '(': + return TOK_LPAREN + elif self.tok =3D=3D ')': + return TOK_RPAREN + elif self.tok =3D=3D '&' and self.src[self.pos+1] =3D=3D '&': + self.cursor +=3D 1 + return TOK_AND + elif self.tok =3D=3D '|' and self.src[self.pos+1] =3D=3D '|': + self.cursor +=3D 1 + return TOK_OR + elif self.tok =3D=3D '!': + return TOK_NOT + elif self.tok =3D=3D 'd' and self.check_keyword("epends"): + return TOK_DEPENDS + elif self.tok =3D=3D 'o' and self.check_keyword("n"): + return TOK_ON + elif self.tok =3D=3D 's' and self.check_keyword("elect"): + return TOK_SELECT + elif self.tok =3D=3D 'c' and self.check_keyword("onfig"): + return TOK_CONFIG + elif self.tok =3D=3D 'd' and self.check_keyword("efault"): + return TOK_DEFAULT + elif self.tok =3D=3D 'b' and self.check_keyword("ool"): + return TOK_BOOL + elif self.tok =3D=3D 'i' and self.check_keyword("f"): + return TOK_IF + elif self.tok =3D=3D 'y' and self.check_keyword(""): + return TOK_Y + elif self.tok =3D=3D 'n' and self.check_keyword(""): + return TOK_N + elif (self.tok =3D=3D 's' and self.check_keyword("ource")) or \ + self.tok =3D=3D 'i' and self.check_keyword("nclude"): + # source FILENAME + # include FILENAME + while self.src[self.cursor].isspace(): + self.cursor +=3D 1 + start =3D self.cursor + self.cursor =3D self.src.find('\n', self.cursor) + self.val =3D self.src[start:self.cursor] + return TOK_SOURCE + elif self.tok.isalpha(): + # identifier + while self.src[self.cursor].isalnum() or self.src[self.cursor]= =3D=3D '_': + self.cursor +=3D 1 + self.val =3D self.src[self.pos:self.cursor] + return TOK_ID + elif self.tok =3D=3D '\n': + if self.cursor =3D=3D len(self.src): + return TOK_EOF + self.line +=3D 1 + self.line_pos =3D self.cursor + elif not self.tok.isspace(): + raise KconfigParserError(self, 'Stray "%s"' % self.tok) + + return None + +if __name__ =3D=3D '__main__': + fname =3D len(sys.argv) > 1 and sys.argv[1] or 'Kconfig.test' + KconfigParser.parse(open(fname, 'r')) --=20 2.17.1