简体   繁体   English

如何使用 LEX 和 YACC 在 Python 中为递归循环命令定义函数?

[英]How to define a function for a recursive loop command in Python by using LEX and YACC?

I'm trying to define a specific grammar for commands shown down below:我正在尝试为下面显示的命令定义特定的语法:

  • F n (go forward n units) F n (前进 n 个单位)
  • R n (turn right n degrees) R n (右转 n 度)
  • L n [ … ] (repeat commands inside parenthesis n times) L n [ … ] (重复括号内的命令 n 次)
  • COLOR f (f = line color and colors are: K: red, Y: green, M: blue, S: black) (defines color of the line) COLOR f (f = 线条颜色,颜色为:K:红色,Y:绿色,M:蓝色,S:黑色)(定义线条的颜色)
  • PEN n (n = line thickness) (1: thin, 2: medium, 3: thick) (defines thickness of the line) PEN n (n = 线条粗细)(1:细,2:中,3:粗)(定义线条的粗细)

Here is an example command:这是一个示例命令:

L 36 [L 4 [F 100 R 90] R 10] 

Output:输出:

示例命令的输出

Here is the grammar I defined:这是我定义的语法:

<start> ::= <function> | <option> 
<function> ::= <forward> | <right> | <loop> | <color> | <pen>
<option> ::= <function> <start> | ε
<forward> ::= F <numbers> 
<right> ::= R <numbers>
<loop> ::= L <numbers> [ <start> ]
<color> ::= COLOR <colors>
<pen> ::= PEN <numbers>
<colors> ::= M | K | S | Y

I tried to call start non-terminal in _loop function I defined:我试图在我定义的 _loop 函数中调用start非终端:

def p_loop(p):
    'loop : LOOP NUMBER LSQB grammar RSQB'
    
    p[0]=loop_(p[2])

def loop_(x):
    i=1
    while i<=x:
        p_grammar(p)
        i=i+1

And it did'nt work.它没有工作。

Then, I tried this:然后,我尝试了这个:

def p_loop(p):
    'loop : LOOP NUMBER LSQB start RSQB '
    i=1
    while i<=p[2]:
        p[0]=p[4]

def p_start(p):
    '''start : function 
             | option'''
    p[0]=p[1]

def p_function(p):
    '''
    function : forward
             | right
             | loop
             | color
             | pen
    '''
    p[0]=p[1] 

It didn't work properly too.它也不能正常工作。 I cannot find another solution.我找不到其他解决方案。 So, how can I define a function for the L command?那么,如何为 L 命令定义一个函数呢?

Here are project codes if you want to check.如果您想检查,这里是项目代码。

You need to distinguish between two different concepts: parsing (or compilation) and evaluation (or execution).您需要区分两个不同的概念:解析(或编译)和评估(或执行)。

Parsing takes as input a textual representation of a program and produces a data object more suitable for repeated evaluation.解析将程序的文本表示作为输入,并生成更适合重复评估的数据对象。 Evaluation takes the object produced by the parser and uses it to run the program -- in this case, to draw a picture.评估获取解析器生成的对象并使用它来运行程序——在本例中,是绘制图片。

Except in very simple cases (such as calculators which have no loops or conditionals), these two procedures are separated, both in time and in logic.除了在非常简单的情况下(例如没有循环或条件的计算器),这两个过程在时间和逻辑上都是分开的。 First, you use one program (or function) to parse the input and create the executable;首先,您使用一个程序(或函数)来解析输入并创建可执行文件; then you set the parser aside and use a completely different program (or function) to evaluate the result.然后您将解析器放在一边,并使用完全不同的程序(或函数)来评估结果。

It should be clear from thinking about how loops work.思考循环是如何工作的应该很清楚。 By definition, a loop is executed several times.根据定义,一个循环会执行多次。 But it is only parsed once.但它只解析一次。 So how could it be executed during the parse?那么在解析过程中如何执行呢?

One way, of course, would be to parse the body of the loop over and over again.当然,一种方法是一遍又一遍地解析循环体。 Some scripting languages do that.一些脚本语言可以做到这一点。 But it requires a very different parsing framework, because the Ply (or yacc) framework assumes that you will just do a single pass over the input, so it doesn't keep the already parsed text anywhere.但它需要一个非常不同的解析框架,因为 Ply(或 yacc)框架假定您只需对输入进行一次传递,因此它不会将已解析的文本保留在任何地方。 Anyway, it's really a waste of cycles to parse the same text more than once.无论如何,多次解析相同的文本确实是在浪费周期。

So the parse needs to produce a data structure which can be executed, and therefore each production's semantic value must be a data object which fits somewhere in this data structure.所以解析需要产生一个可以执行的数据结构,因此每个产生式的语义值必须是一个适合该数据结构某处的数据对象。 Thus, your first task is to design the data structure you want use.因此,您的首要任务是设计您想要使用的数据结构。 In fact, I would have recommended doing that before you wrote the parser, because it tells you what the parser's result has to be.事实上,我会建议您在编写解析器之前这样做,因为它会告诉您解析器的结果必须是什么。

There are a number of possible data structures which can be used;有许多可能的数据结构可以使用; it's possible that your assignment has a clear suggestion.您的作业可能有明确的建议。 Probably this was discussed in the lectures or reading material as well (and if it wasn't and you paid for the course, I'd say you have a reasonable complaint).可能在讲座或阅读材料中也讨论了这一点(如果不是这样并且您为课程付费,我会说您有合理的投诉)。 Two common possibilities are:两种常见的可能性是:

  • A recursive collection of objects, often referred to as an "abstract syntax tree" because it abstracts from the syntax.对象的递归集合,通常称为“抽象语法树”,因为它从语法中抽象出来。 Calling this a "tree" is a bit misleading.称其为“树”有点误导。 Although you could implement it from a general purpose tree data object using node attributes to hold relevant information, that's usually more complicated than defining a collection of classes, each of which implements a simple interface.尽管您可以使用节点属性从通用树数据对象中实现它以保存相关信息,但这通常比定义一个类集合更复杂,每个类都实现一个简单的接口。 An evaluate method might be all that you need, although for debugging you might also want to implement a method which prints the object in some convenient format.一个evaluate方法可能就是你所需要的,尽管为了调试,你可能还想实现一个以某种方便的格式打印对象的方法。

  • A list of low-level instructions which could be evaluated by a virtual machine.可由虚拟机评估的低级指令列表。 Commands like LEFT and RIGHT would just be a simple instructions, which could possibly be tuples of two elements (an opcode and an integer operand).LEFTRIGHT这样的命令只是一个简单的指令,它可能是两个元素的元组(一个操作码和一个整数操作数)。 To implement loops, the virtual machine would require a stack;为了实现循环,虚拟机需要一个堆栈; a simple design might be something like this:一个简单的设计可能是这样的:

     [0] PUSH 36 # Pushes 36 onto the stack [1] PUSH 4 # Body of the outer loop; pushes 4 [2] FWD 100 [3] RIGHT 90 [4] LOOP 2 # '2' is the index of the loop body [5] FWD 10 [6] LOOP 1

    The LOOP instruction decrements the integer at the stop of the stack. LOOP指令在堆栈停止处递减整数。 If it is non-zero, it continues executing with the instruction whose index is the argument;如果它不为零,则继续执行索引为参数的指令; otherwise, the top element of the stack is 0 and the LOOP instruction pops that off the stack and continues with the next instruction in sequence.否则,堆栈的顶部元素为 0,并且LOOP指令将其从堆栈中弹出并继续按顺序执行下一条指令。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM