基本脚本实现函数

17 投票
4 回答
728 浏览
提问于 2025-04-16 19:46

我最近在玩Python,想通过写一个自定义的脚本处理器来更好地理解编程语言。到目前为止,我已经成功实现了一个基本的内存处理器,并将一个内存地址连接到屏幕打印上。我的问题可以这样提出:

在这里怎么实现函数呢?用goto语句太简单了,我想尝试一些更复杂的东西。(编辑)最终我希望能够做到:

f0(x, y, z):=ax^by^cz

...在一个运行脚本的shell中运行这个模块(听起来有点傻,是吧?)

# notes: separate addresses from data lest the loop of doom cometh

class Interpreter:

  def __init__(self):
    self.memory = { }
    self.dictionary = {"mov" : self.mov,
                       "put" : self.put,
                       "add" : self.add,
                       "sub" : self.sub,
                       "clr" : self.clr,
                       "cpy" : self.cpy,
                       "ref" : self.ref }
    self.hooks = {self.val("0") : self.out }

  def interpret(self, line):
    x = line.split(" ")
    vals = tuple(self.val(y) for y in x[1:])
    dereferenced = []
    keys_only = tuple(key for key in self.memory)
    for val in vals:
      while val in self.memory: val = self.memory[val]
      dereferenced.append(val)
    vals = tuple(y for y in dereferenced)
    self.dictionary[x[0]](vals)

  def val(self, x):
    return tuple(int(y) for y in str(x).split("."))

  def mov(self, value):
    self.ptr = value[0]

  def put(self, value):
    self.memory[self.ptr] = value[0]

  def clr(self, value):
    if self.ptr in self.hooks and self.ptr in self.memory:
      x = self.hooks[self.ptr]
      y = self.memory[self.ptr]
      for z in y: x(z)
    del self.memory[self.ptr]

  def add(self, values):
    self.put(self.mat(values, lambda x, y: x + y))

  def sub(self, values):
    self.put(self.mat(values, lambda x, y: x - y))

  def mat(self, values, op):
    a, b = self.memory[values[0]], self.memory[values[1]]
    if len(a) > len(b): a, b = b, a
    c = [op(a[x], b[x]) for x in xrange(len(b))] + [x for x in a[len(a):]]
    return [tuple(x for x in c)]

  def cpy(self, value):
    self.put(value)

  def out(self, x):
    print chr(x),

  def ref(self, x):
    self.put(x)

interp = Interpreter()
for x in file(__file__.split('/')[-1].split(".")[-2] + ".why"):
  interp.interpret(x.strip())

一个示例脚本:

mov 1
put 104.101.108.108.111.10
mov 0
ref 1
clr 0

(编辑)我决定把这个尝试作为灵感,从头开始这个项目。(希望在上课前能找到一些时间坐下来编码。) 我打算在几天后给出最佳答案。我希望这个信息不会让潜在的贡献者不愿意提交他们认为对这个编码问题有帮助的任何内容。

4 个回答

2

我不太确定我是否理解你的意思,但如果你的目标是能够通过像 f0(x):=mov x 这样的方式来定义函数,并且使用其他复杂的语法,那么听起来你缺少的主要部分是某种词法分析和语法解析器。一旦你不再把“行首的符号定义了这一行的功能”这个概念作为基础,那么你用 line.split(" ") 的方法就不够用了。这些工具相当复杂,任何比汇编语言更复杂的语言都需要这些工具(不过根据语言和编译器/解释器的不同,这些工具可能是手动构建的)。

大多数语言的输入解析分为两个主要步骤:

1) 词法分析 -- 这个步骤会把“x+1/5”这样的表达式转换成有意义的符号,比如“变量 操作符 数字 操作符 数字”。这个步骤的输出会作为语法解析器的输入。

2) 语法解析 -- 这个步骤更复杂,关于最佳的语法解析方法有很多理论。这一步会把上面的输入解析成一个可以被计算的树形结构。例如:

Operator+ 
|     |
|     ----Variable x 
Operator/
|    |
1    5

我在Python中没有使用过这两种工具的经验。在C++中,我用过的工具叫做flex和bison。我相信这里还有其他人用过类似的工具在Python中,并且可以给你一些链接。看起来这个问题有一些相关的链接:高效的上下文无关语法解析器,最好是Python友好的

我试着为你搜索了一些关于这些概念的教程,但没有找到。今天晚上我的搜索技能似乎不太灵光。

2

如果你去查阅编译器的手册,它会建议在调用方法时使用栈。这可以让你创建递归函数,也就是一个函数可以调用其他函数,同时还能保持变量在正确的范围内。

所以你会用栈来存储每次函数调用的变量,没错,可以用 goto 跳转到函数的地址。然后用你的栈来获取函数的返回地址,以及在调用函数时变量的状态。就这么简单。

祝你好运!

3

我有点搞不清楚你在问什么。你的函数定义应该放在哪里?是在脚本处理器里,还是在脚本中?

如果是在脚本处理器里,最简单的解决办法就是用 lambda 表达式。根据你在问题中给出的例子 f0(x, y, z):=x^2,可以这样写:

>>> f0 = lambda x, y, z : x**2
>>> f0(2,3,4)
4

如果函数定义要放在脚本本身里,你可以结合使用 lambdaeval 表达式。这里有个简单的例子,我刚刚拼凑出来,来说明这个想法。

class ScriptParser(object):

    # See 'to_python' to check out what this does
    mapping = {'^':'**', '!':' not ', '&':' and '}

    def to_python(self, calc):
        '''
        Parse the calculation syntax from the script grammar to the python one.
        This could be grown to a more complex parser, if needed. For now it will
        simply assume any operator as defined in the grammar used for the script
        has an equivalent in python.
        '''
        for k, v in self.mapping.items():
            calc = calc.replace(k, v)
        return calc

    def feed(self, lfs):
        '''
        Parse a line of the script containing a function defintion
        '''
        signature, calc = lfs.split(':=')
        funcname, variables = [s.strip() for s in signature.split('(')]
        # as we stripped the strings, it's now safe to do...'
        variables = variables[:-1]
        setattr(self, funcname,
                eval('lambda ' + variables + ' : ' + self.to_python(calc)))

def main():
    lines = ['f0(x, y, z) := x^2',
             'f1(x) := x**2 + x**3 + x*1000']
    sp = ScriptParser()
    for line in lines:
        sp.feed(line)
        print('Script definition  : %s' % line)
    for i in range(5):
        res0 = sp.f0(i, None, None)
        res1 = sp.f1(i)
        print('f0(%d) = %d' % (i, res0))
        print('f1(%d) = %d' % (i, res1))
        print('--------')

if __name__ == '__main__':
    main()

运行这个程序会输出:

Script definition  : f0(x, y, z) := x^2
Script definition  : f1(x) := x**2 + x**3 + x*1000
f0(0) = 0
f1(0) = 0
--------
f0(1) = 1
f1(1) = 1002
--------
f0(2) = 4
f1(2) = 2012
--------
f0(3) = 9
f1(3) = 3036
--------
f0(4) = 16
f1(4) = 4080
--------

不过要记住:

  1. 使用 eval 可能会有安全隐患,你需要注意。
  2. 自己写一个语法解析器是个非常酷的学习经历!! :)

希望这对你有帮助,

Mac.

撰写回答