Table of Contents
1. Arithmetic expression evaluation
Home Backend Development Python Tutorial How to implement recursive descent Parser in Python

How to implement recursive descent Parser in Python

May 17, 2023 am 08:44 AM
python parser

    1. Arithmetic expression evaluation

    To parse this type of text, another specific grammar rule is required. We introduce here the grammatical rules Backus Normal Form (BNF) and Extended Backus Normal Form (EBNF) that can represent context free grammar. From as small as an arithmetic expression to as large as almost all programming languages, they are defined using context-free grammars.

    For simple arithmetic operation expressions, it is assumed that we have used word segmentation technology to convert it into an input tokens stream, such as NUM NUM*NUM (see the previous blog post for word segmentation method).

    On this basis, we define the BNF rule as follows:

    expr ::= expr + term
         | expr - term 
         | term
    term ::= term * factor
         | term / factor
         | factor
    factor ::= (expr)
         | NUM

    Of course, this method is not concise and clear enough. What we actually use is the EBNF form:

    expr ::= term { (+|-) term }*
    term ::= factor { (*|/) factor }*
    factor ::= (expr) 
           | NUM
    ## Each rule of #BNF and EBNF (an expression in the form of ::=) can be regarded as a substitution, that is, the symbol on the left can be replaced by the symbol on the right. We try to use BNF/EBNF to match the input text with grammar rules during the parsing process to complete various substitutions and expansions. In EBNF, rules placed within {...}* are optional, and * indicates that they can be repeated zero or more times (analogous to regular expressions).

    The following figure vividly shows the relationship between the "recursion" and "descent" parts of the recursive descent parser (parser) and ENBF:

    How to implement recursive descent Parser in Python

    In practice During the parsing process, we scan the tokens stream from left to right, and process the tokens during the scanning process. If it gets stuck, a syntax error will be generated. Each grammar rule is converted into a function or method. For example, the above ENBF rule is converted into the following method:

    class ExpressionEvaluator():
        ...
        def expr(self):
            ...
        def term(self):
            ...
        def factor(self):
            ...

    In the process of calling the method corresponding to a rule, if we find the next symbol If we need to use another rule to match, we will "descend" to another rule method (such as calling term in expr and factor in term), which is the "descending" part of the recursive descent.

    Sometimes methods that are already executing are called (for example, term is called in expr, factor is called in term, and expr is called in factor, which is equivalent to an ouroboros), which is recursive descent. The "recursive" part.

    For the repeated parts that appear in the grammar (such as

    expr ::= term { ( |-) term }*), we implement it through a while loop.

    Let’s look at the specific code implementation. The first is the word segmentation part. Let’s refer to the previous article to introduce the code of the word segmentation blog.

    import re
    import collections
    
    # 定义匹配token的模式
    NUM = r&#39;(?P<NUM>\d+)&#39;  # \d表示匹配数字,+表示任意长度
    PLUS = r&#39;(?P<PLUS>\+)&#39;  # 注意转义
    MINUS = r&#39;(?P<MINUS>-)&#39;
    TIMES = r&#39;(?P<TIMES>\*)&#39;  # 注意转义
    DIVIDE = r&#39;(?P<DIVIDE>/)&#39;
    LPAREN = r&#39;(?P<LPAREN>\()&#39;  # 注意转义
    RPAREN = r&#39;(?P<RPAREN>\))&#39;  # 注意转义
    WS = r&#39;(?P<WS>\s+)&#39;  # 别忘记空格,\s表示空格,+表示任意长度
    
    master_pat = re.compile(
        &#39;|&#39;.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS]))
    
    # Tokenizer
    Token = collections.namedtuple(&#39;Token&#39;, [&#39;type&#39;, &#39;value&#39;])
    
    
    def generate_tokens(text):
        scanner = master_pat.scanner(text)
        for m in iter(scanner.match, None):
            tok = Token(m.lastgroup, m.group())
            if tok.type != &#39;WS&#39;:  # 过滤掉空格符
                yield tok

    The following is the specific implementation of the expression evaluator:

    class ExpressionEvaluator():
        """ 递归下降的Parser实现,每个语法规则都对应一个方法,
        使用 ._accept()方法来测试并接受当前处理的token,不匹配不报错,
        使用 ._except()方法来测试当前处理的token,并在不匹配的时候抛出语法错误
        """
    
        def parse(self, text):
            """ 对外调用的接口 """
            self.tokens = generate_tokens(text)
            self.tok, self.next_tok = None, None  # 已匹配的最后一个token,下一个即将匹配的token
            self._next()  # 转到下一个token
            return self.expr()  # 开始递归
    
        def _next(self):
            """ 转到下一个token """
            self.tok, self.next_tok = self.next_tok, next(self.tokens, None)
    
        def _accept(self, tok_type):
            """ 如果下一个token与tok_type匹配,则转到下一个token """
            if self.next_tok and self.next_tok.type == tok_type:
                self._next()
                return True
            else:
                return False
    
        def _except(self, tok_type):
            """ 检查是否匹配,如果不匹配则抛出异常 """
            if not self._accept(tok_type):
                raise SyntaxError("Excepted"+tok_type)
    
        # 接下来是语法规则,每个语法规则对应一个方法
        
        def expr(self):
            """ 对应规则: expression ::= term { (&#39;+&#39;|&#39;-&#39;) term }* """
            exprval = self.term() # 取第一项
            while self._accept("PLUS") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                op = self.tok.type 
                # 再取下一项,即运算符右值
                right = self.term() 
                if op == "PLUS":
                    exprval += right
                elif op == "MINUS":
                    exprval -= right
            return exprval
                
        def term(self):
            """ 对应规则: term ::= factor { (&#39;*&#39;|&#39;/&#39;) factor }* """
            
            termval = self.factor() # 取第一项
            while self._accept("TIMES") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                op = self.tok.type 
                # 再取下一项,即运算符右值
                right = self.factor() 
                if op == "TIMES":
                    termval *= right
                elif op == "DIVIDE":
                    termval /= right
            return termval          
                
            
        def factor(self):
            """ 对应规则: factor ::= NUM | ( expr ) """
            if self._accept("NUM"): # 递归出口
                return int(self.tok.value)
            elif self._accept("LPAREN"):
                exprval = self.expr() # 继续递归下去求表达式值
                self._except("RPAREN") # 别忘记检查是否有右括号,没有则抛出异常
                return exprval
            else:
                raise SyntaxError("Expected NUMBER or LPAREN")

    We enter the following expression for testing:

    e = ExpressionEvaluator()
    print(e.parse("2"))
    print(e.parse("2+3"))
    print(e.parse("2+3*4"))
    print(e.parse("2+(3+4)*5"))

    The evaluation results are as follows:

    2

    5
    14
    37

    If the text we enter does not comply with the grammatical rules:

    print(e.parse("2 + (3 + * 4)"))

    , a SyntaxError exception will be thrown:

    Expected NUMBER or LPAREN. In summary, it can be seen that our expression evaluation algorithm runs correctly.

    2. Generate expression tree

    We got the result of the expression above, but what if we want to analyze the structure of the expression and generate a simple expression parsing tree? Then we need to make certain modifications to the methods of the above classes:

    class ExpressionTreeBuilder(ExpressionEvaluator):
        def expr(self):
                """ 对应规则: expression ::= term { (&#39;+&#39;|&#39;-&#39;) term }* """
                exprval = self.term() # 取第一项
                while self._accept("PLUS") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                    op = self.tok.type 
                    # 再取下一项,即运算符右值
                    right = self.term() 
                    if op == "PLUS":
                        exprval = (&#39;+&#39;, exprval, right)
                    elif op == "MINUS":
                        exprval -= (&#39;-&#39;, exprval, right)
                return exprval
        
        def term(self):
            """ 对应规则: term ::= factor { (&#39;*&#39;|&#39;/&#39;) factor }* """
            
            termval = self.factor() # 取第一项
            while self._accept("TIMES") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                op = self.tok.type 
                # 再取下一项,即运算符右值
                right = self.factor() 
                if op == "TIMES":
                    termval = (&#39;*&#39;, termval, right)
                elif op == "DIVIDE":
                    termval = (&#39;/&#39;, termval, right)
            return termval          
        
        def factor(self):
            """ 对应规则: factor ::= NUM | ( expr ) """
            if self._accept("NUM"): # 递归出口
                return int(self.tok.value) # 字符串转整形
            elif self._accept("LPAREN"):
                exprval = self.expr() # 继续递归下去求表达式值
                self._except("RPAREN") # 别忘记检查是否有右括号,没有则抛出异常
                return exprval
            else:
                raise SyntaxError("Expected NUMBER or LPAREN")

    Enter the following expression to test:

    print(e.parse("2+3"))
    print(e.parse("2+3*4"))
    print(e.parse("2+(3+4)*5"))
    print(e.parse(&#39;2+3+4&#39;))

    The following is the generated result:

    (' ' , 2, 3)

    (' ', 2, ('*', 3, 4))
    (' ', 2, ('*', (' ', 3, 4), 5))
    (' ', (' ', 2, 3), 4)

    You can see that the expression tree is generated correctly.

    Our example above is very simple, but the recursive descent parser can also be used to implement quite complex parsers. For example, Python code is parsed through a recursive descent parser. If you are interested in this, you can check the

    Grammar file in the Python source code to find out. However, as we will see below, writing a parser yourself comes with various pitfalls and challenges.

    Left recursion and operator precedence trap

    Any grammar rule involving the

    left recursion form cannot be solved by the recursive descent parser. The so-called left recursion means that the leftmost symbol on the right side of the rule expression is the rule header. For example, for the following rules:

    items ::= items &#39;,&#39; item 
          | item

    To complete the analysis, you may define the following method:

    def items(self):
        itemsval = self.items() # 取第一项,然而此处会无穷递归!
        if itemsval and self._accept(&#39;,&#39;):
            itemsval.append(self.item())
        else:
            itemsval = [self.item()]

    This will In the first line,

    self.items() is called infinitely, resulting in an infinite recursion error.

    There is also an error in the grammar rules themselves, such as operator precedence. If we ignore the operator precedence and directly simplify the expression as follows:

    expr ::= factor { (&#39;+&#39;|&#39;-&#39;|&#39;*&#39;|&#39;/&#39;) factor }*
    factor ::= &#39;(&#39; expr &#39;)&#39;
           | NUM
    PYTHON Copy full screen

    This syntax can be technically implemented, but the calculation order convention is not followed, resulting in "3 4* 5" evaluates to 35 instead of 23 as expected. Therefore, separate expr and term rules are required to ensure the correctness of the calculation results.

    The above is the detailed content of How to implement recursive descent Parser in Python. For more information, please follow other related articles on the PHP Chinese website!

    Statement of this Website
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

    Hot AI Tools

    Undress AI Tool

    Undress AI Tool

    Undress images for free

    Undresser.AI Undress

    Undresser.AI Undress

    AI-powered app for creating realistic nude photos

    AI Clothes Remover

    AI Clothes Remover

    Online AI tool for removing clothes from photos.

    Clothoff.io

    Clothoff.io

    AI clothes remover

    Video Face Swap

    Video Face Swap

    Swap faces in any video effortlessly with our completely free AI face swap tool!

    Hot Tools

    Notepad++7.3.1

    Notepad++7.3.1

    Easy-to-use and free code editor

    SublimeText3 Chinese version

    SublimeText3 Chinese version

    Chinese version, very easy to use

    Zend Studio 13.0.1

    Zend Studio 13.0.1

    Powerful PHP integrated development environment

    Dreamweaver CS6

    Dreamweaver CS6

    Visual web development tools

    SublimeText3 Mac version

    SublimeText3 Mac version

    God-level code editing software (SublimeText3)

    Accessing data from a web API in Python Accessing data from a web API in Python Jul 16, 2025 am 04:52 AM

    The key to using Python to call WebAPI to obtain data is to master the basic processes and common tools. 1. Using requests to initiate HTTP requests is the most direct way. Use the get method to obtain the response and use json() to parse the data; 2. For APIs that need authentication, you can add tokens or keys through headers; 3. You need to check the response status code, it is recommended to use response.raise_for_status() to automatically handle exceptions; 4. Facing the paging interface, you can request different pages in turn and add delays to avoid frequency limitations; 5. When processing the returned JSON data, you need to extract information according to the structure, and complex data can be converted to Data

    Completed python blockbuster online viewing entrance python free finished website collection Completed python blockbuster online viewing entrance python free finished website collection Jul 23, 2025 pm 12:36 PM

    This article has selected several top Python "finished" project websites and high-level "blockbuster" learning resource portals for you. Whether you are looking for development inspiration, observing and learning master-level source code, or systematically improving your practical capabilities, these platforms are not to be missed and can help you grow into a Python master quickly.

    How to use PHP combined with AI to achieve text error correction PHP syntax detection and optimization How to use PHP combined with AI to achieve text error correction PHP syntax detection and optimization Jul 25, 2025 pm 08:57 PM

    To realize text error correction and syntax optimization with AI, you need to follow the following steps: 1. Select a suitable AI model or API, such as Baidu, Tencent API or open source NLP library; 2. Call the API through PHP's curl or Guzzle and process the return results; 3. Display error correction information in the application and allow users to choose whether to adopt it; 4. Use php-l and PHP_CodeSniffer for syntax detection and code optimization; 5. Continuously collect feedback and update the model or rules to improve the effect. When choosing AIAPI, focus on evaluating accuracy, response speed, price and support for PHP. Code optimization should follow PSR specifications, use cache reasonably, avoid circular queries, review code regularly, and use X

    Python for Quantum Machine Learning Python for Quantum Machine Learning Jul 21, 2025 am 02:48 AM

    To get started with quantum machine learning (QML), the preferred tool is Python, and libraries such as PennyLane, Qiskit, TensorFlowQuantum or PyTorchQuantum need to be installed; then familiarize yourself with the process by running examples, such as using PennyLane to build a quantum neural network; then implement the model according to the steps of data set preparation, data encoding, building parametric quantum circuits, classic optimizer training, etc.; in actual combat, you should avoid pursuing complex models from the beginning, paying attention to hardware limitations, adopting hybrid model structures, and continuously referring to the latest documents and official documents to follow up on development.

    How to use PHP to develop product recommendation module PHP recommendation algorithm and user behavior analysis How to use PHP to develop product recommendation module PHP recommendation algorithm and user behavior analysis Jul 23, 2025 pm 07:00 PM

    To collect user behavior data, you need to record browsing, search, purchase and other information into the database through PHP, and clean and analyze it to explore interest preferences; 2. The selection of recommendation algorithms should be determined based on data characteristics: based on content, collaborative filtering, rules or mixed recommendations; 3. Collaborative filtering can be implemented in PHP to calculate user cosine similarity, select K nearest neighbors, weighted prediction scores and recommend high-scoring products; 4. Performance evaluation uses accuracy, recall, F1 value and CTR, conversion rate and verify the effect through A/B tests; 5. Cold start problems can be alleviated through product attributes, user registration information, popular recommendations and expert evaluations; 6. Performance optimization methods include cached recommendation results, asynchronous processing, distributed computing and SQL query optimization, thereby improving recommendation efficiency and user experience.

    How to join a list of strings in Python How to join a list of strings in Python Jul 18, 2025 am 02:15 AM

    In Python, the following points should be noted when merging strings using the join() method: 1. Use the str.join() method, the previous string is used as a linker when calling, and the iterable object in the brackets contains the string to be connected; 2. Make sure that the elements in the list are all strings, and if they contain non-string types, they need to be converted first; 3. When processing nested lists, you must flatten the structure before connecting.

    Python web scraping tutorial Python web scraping tutorial Jul 21, 2025 am 02:39 AM

    To master Python web crawlers, you need to grasp three core steps: 1. Use requests to initiate a request, obtain web page content through get method, pay attention to setting headers, handling exceptions, and complying with robots.txt; 2. Use BeautifulSoup or XPath to extract data. The former is suitable for simple parsing, while the latter is more flexible and suitable for complex structures; 3. Use Selenium to simulate browser operations for dynamic loading content. Although the speed is slow, it can cope with complex pages. You can also try to find a website API interface to improve efficiency.

    PHP calls AI intelligent voice assistant PHP voice interaction system construction PHP calls AI intelligent voice assistant PHP voice interaction system construction Jul 25, 2025 pm 08:45 PM

    User voice input is captured and sent to the PHP backend through the MediaRecorder API of the front-end JavaScript; 2. PHP saves the audio as a temporary file and calls STTAPI (such as Google or Baidu voice recognition) to convert it into text; 3. PHP sends the text to an AI service (such as OpenAIGPT) to obtain intelligent reply; 4. PHP then calls TTSAPI (such as Baidu or Google voice synthesis) to convert the reply to a voice file; 5. PHP streams the voice file back to the front-end to play, completing interaction. The entire process is dominated by PHP to ensure seamless connection between all links.

    See all articles