маленький язык в питоне - PullRequest
6 голосов
/ 14 июня 2011

Я пишу то, что в Python даже нельзя назвать языком. В настоящее время у меня есть несколько операторов: +, -, *, ^, fac, @, !!. fac вычисляет факториал, @ возвращает значение переменной, !! устанавливает переменную. Код ниже. Как мне написать способ определения функций на этом простом языке?

РЕДАКТИРОВАТЬ: я обновил код!

import sys, shlex, readline, os, string
List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\
kill, clr, STO, RET, fib, curs = {}, "set", "get", "+", "-", "/", "^", "*",\
"%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars"
def fact(num):
    if num == 1: return 1
    else: return num*fact(num-1)
def Simp(op, num2, num1):
    global List
    try: num1, num2 = float(num1), float(num2)
    except:
        try: num1 = float(num1)
        except:
            try: num2 = float(num2)
            except: pass
    if op == mul: return num1*num2
    elif op == div: return num1/num2
    elif op == sub: return num1-num2
    elif op == add: return num1+num2
    elif op == Pow: return num1**num2
    elif op == assign: List[num1] = num2; return "ok"
    elif op == call: return List[num1]
    elif op == fac: return fact(num1)
    elif op == duf: return "%s %s %s"%(duf, num1, num2)
    elif op == mod: return num1%num2
    elif op == kill: del List[num1]; return "ok"
    elif op == clr: os.system("clear")
    elif op == STO: List[num2] = num1; return "ok"
    elif op == RET: return List[num1]
    elif op == curs: return List
    elif op == read: List[num1] = Eval(raw_input("%s "%num1)); return "ok"
def Eval(expr):
    ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs)
    stack, expr, ops = [], shlex.split(string.lower(expr)), ops.split()
    for i in expr:
        if i[0] != ';':
            if i not in ops: stack.append(i)
            elif i in ops: stack.append(Simp(i, stack.pop(), stack.pop()))
        else: stack.append("ok")
    return stack[0]
def shell():
    try:
        x = ""
        while x != "quit":
            x = raw_input("star>   ")
            try: l = Eval(x)
            except KeyError: l = "does not exist"
            except: l = "parse error!"
            if l != None: print "   =>",l,"\n"
    except (EOFError, KeyboardInterrupt): print
if len(sys.argv) > 1:
    x = open(sys.argv[1], 'r'); l = x.readlines(); x.close()
    for i in l:
        if i[0] != ";":
            i = ' '.join(i.split())
            x = Eval(i)
            if x != None: print i,"\n   =>",x,"\n"
        else: pass
    shell()
else: shell()

Ответы [ 5 ]

7 голосов
/ 14 июня 2011

Ваша программа очень запутана, и ее нужно исправить, прежде чем ее можно будет модифицировать для поддержки определения функций.Я сделаю это в несколько этапов, и по мере их завершения я добавлю их в ответ.Этот ответ будет довольно длинным.

Кроме того, вы, очевидно, еще не решили, каким должно быть ваше определение языка.Вы решили сделать свое определение языка в некотором роде похожим на вашу технику реализации, и это отчасти нарушено и приводит к большим трудностям.

Во-первых, определение для вашей Simp функции действительно нарушено.,Это требует, чтобы все брало ровно два значения из стека и снова поместило ровно одно значение.Сломано.Функция факториала не работает таким же образом, как и функция Фибоначчи, поэтому вы вынуждены иметь «фиктивный» аргумент, который никогда не используется.Кроме того, такие вещи, как присваивание элементу вашего глобального списка или словаря, не имеют смысла помещать значения в стек, так что вы нажимаете «ОК».Это не работает и требует исправления.

Вот версия с этой проблемой исправлена.Обратите внимание, что я переименовал Simp в builtin_op, чтобы более точно отразить его назначение:

import sys, shlex, readline, os, string
List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\
kill, clr, STO, RET, fib, curs = {}, "set", "get", "+", "-", "/", "^", "*",\
"%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars"
def fact(num):
    if num == 1: return 1
    else: return num*fact(num-1)
def builtin_op(op, stack):
    global List
    if op == mul: stack.append(float(stack.pop())*float(stack.pop()))
    elif op == div: stack.append(float(stack.pop())/float(stack.pop()))
    elif op == sub: stack.append(float(stack.pop())-float(stack.pop()))
    elif op == add: stack.append(float(stack.pop())+float(stack.pop()))
    elif op == Pow: stack.append(float(stack.pop())**float(stack.pop()))
    elif op == assign: val = List[stack.pop()] = stack.pop(); stack.append(val)
    elif op == call: stack.append(List[stack.pop()])
    elif op == fac: stack.append(fact(stack.pop()))
    elif op == duf: stack.append("%s %s %s" % (duf, stack.pop(), stack.pop()))
    elif op == mod: stack.append(float(stack.pop())%float(stack.pop()))
    elif op == kill: del List[stack.pop()]
    elif op == clr: os.system("clear")
    elif op == STO: val = List[stack.pop()] = stack.pop(); stack.append(val)
    elif op == RET: stack.append(List[stack.pop()])
    elif op == curs: stack.append(List)
    elif op == read: prompt = stack.pop(); List[prompt] = Eval(raw_input("%s "%prompt)); stack.append(List[prompt])
def Eval(expr):
    ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs)
    stack, expr, ops = [], shlex.split(string.lower(expr)), ops.split()
    for i in expr:
        if i[0] != ';':
            if i not in ops: stack.append(i)
            elif i in ops: builtin_op(i, stack)
        else: stack.append("ok")
    return stack[0]
def shell():
    try:
        x = ""
        while x != "quit":
            x = raw_input("star>   ")
            try: l = Eval(x)
            except KeyError: l = "does not exist"
            except: l = "parse error!"
            if l != None: print "   =>",l,"\n"
    except (EOFError, KeyboardInterrupt): print
if len(sys.argv) > 1:
    x = open(sys.argv[1], 'r'); l = x.readlines(); x.close()
    for i in l:
        if i[0] != ";":
            i = ' '.join(i.split())
            x = Eval(i)
            if x != None: print i,"\n   =>",x,"\n"
        else: pass
    shell()
else: shell()

Здесь все еще есть ряд проблем, которые не устранены, и я не буду исправлять влюбая будущая версия.Например, возможно, что значение в стеке не может быть интерпретировано как число с плавающей запятой.Это вызовет исключение, и это исключение может быть сгенерировано до того, как другое значение будет считано из стека.Это означает, что если неправильные «типы» находятся в стеке, стек может находиться в неоднозначном состоянии после «ошибки разбора».Как правило, вы хотите избежать подобных ситуаций в языке.

Определение функций - интересная проблема.На вашем языке оценка незамедлительна.У вас нет механизма для отсрочки оценки на потом.Но вы используете модуль shlex для разбора.И есть способ сказать, что целая группа символов (включая пробелы и тому подобное) является частью одной сущности.Это дает нам быстрый и простой способ реализации функций.Вы можете сделать что-то вроде этого:

star>   "3 +" add3 func

, чтобы создать свою функцию, и:

star>   2 add3 get

, чтобы вызвать ее.Я использовал get, потому что это то, что вы присвоили call в своей программе.

Единственная проблема заключается в том, что для работы функции потребуется доступ к текущему состоянию стека.Вы можете легко передать строку для функции обратно в Eval, но Eval всегда создает новый стек каждый раз, когда он вызывается.Для реализации функций это необходимо исправить.Поэтому я добавил аргумент по умолчанию stack в функцию Eval.Если для этого аргумента оставить значение по умолчанию, Eval все равно создаст новый стек, как и раньше.Но если будет передан существующий стек, Eval будет использовать его вместо этого.

Вот модифицированный код:

import sys, shlex, readline, os, string
List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\
kill, clr, STO, RET, fib, curs = {}, "set", "get", "+", "-", "/", "^", "*",\
"%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars"
funcdict = {}
def fact(num):
    if num == 1: return 1
    else: return num*fact(num-1)
def builtin_op(op, stack):
    global List
    global funcdict
    if op == mul: stack.append(float(stack.pop())*float(stack.pop()))
    elif op == div: stack.append(float(stack.pop())/float(stack.pop()))
    elif op == sub: stack.append(float(stack.pop())-float(stack.pop()))
    elif op == add: stack.append(float(stack.pop())+float(stack.pop()))
    elif op == Pow: stack.append(float(stack.pop())**float(stack.pop()))
    elif op == assign: val = List[stack.pop()] = stack.pop(); stack.append(val)
    elif op == call: Eval(funcdict[stack.pop()], stack)
    elif op == fac: stack.append(fact(stack.pop()))
    elif op == duf: name = stack.pop(); funcdict[name] = stack.pop(); stack.append(name)
    elif op == mod: stack.append(float(stack.pop())%float(stack.pop()))
    elif op == kill: del List[stack.pop()]
    elif op == clr: os.system("clear")
    elif op == STO: val = List[stack.pop()] = stack.pop(); stack.append(val)
    elif op == RET: stack.append(List[stack.pop()])
    elif op == curs: stack.append(List)
    elif op == read: prompt = stack.pop(); List[prompt] = Eval(raw_input("%s "%prompt)); stack.append(List[prompt])
def Eval(expr, stack=None):
    ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs)
    if stack is None:
        stack = []
    expr, ops = shlex.split(string.lower(expr)), ops.split()
    for i in expr:
        if i[0] != ';':
            if i not in ops: stack.append(i)
            elif i in ops: builtin_op(i, stack)
        else: stack.append("ok")
    return stack[0]
def shell():
    try:
        x = ""
        while x != "quit":
            x = raw_input("star>   ")
            try: l = Eval(x)
            except KeyError: l = "does not exist"
            except: l = "parse error!"
            if l != None: print "   =>",l,"\n"
    except (EOFError, KeyboardInterrupt): print
if len(sys.argv) > 1:
    x = open(sys.argv[1], 'r'); l = x.readlines(); x.close()
    for i in l:
        if i[0] != ";":
            i = ' '.join(i.split())
            x = Eval(i)
            if x != None: print i,"\n   =>",x,"\n"
        else: pass
    shell()
else: shell()

В языках на основе стека два очень полезных встроенных оператора:dup и swap.dup берет верхний элемент стека и дублирует его.swap меняет местами два верхних элемента стека.

Если у вас есть dup, вы можете реализовать функцию square следующим образом:

star>   "dup *" square func

Вот ваша программа с dupи swap реализовано:

import sys, shlex, readline, os, string
List, assign, call, add, sub, div, Pow, mul, mod, fac, duf, read,\
kill, clr, STO, RET, fib, curs, dup, swap = {}, "set", "get", "+", "-", "/", "^", "*",\
"%", "fact", "func", "read", "kill", "clear", ">", "@", "fib", "vars", "dup", "swap"
funcdict = {}
def fact(num):
    if num == 1: return 1
    else: return num*fact(num-1)
def builtin_op(op, stack):
    global List
    global funcdict
    if op == mul: stack.append(float(stack.pop())*float(stack.pop()))
    elif op == div: stack.append(float(stack.pop())/float(stack.pop()))
    elif op == sub: stack.append(float(stack.pop())-float(stack.pop()))
    elif op == add: stack.append(float(stack.pop())+float(stack.pop()))
    elif op == Pow: stack.append(float(stack.pop())**float(stack.pop()))
    elif op == assign: val = List[stack.pop()] = stack.pop(); stack.append(val)
    elif op == call: Eval(funcdict[stack.pop()], stack)
    elif op == fac: stack.append(fact(stack.pop()))
    elif op == duf: name = stack.pop(); funcdict[name] = stack.pop(); stack.append(name)
    elif op == mod: stack.append(float(stack.pop())%float(stack.pop()))
    elif op == kill: del List[stack.pop()]
    elif op == clr: os.system("clear")
    elif op == STO: val = List[stack.pop()] = stack.pop(); stack.append(val)
    elif op == RET: stack.append(List[stack.pop()])
    elif op == curs: stack.append(List)
    elif op == dup: val = stack.pop(); stack.append(val); stack.append(val)
    elif op == swap: val1 = stack.pop(); val2 = stack.pop(); stack.append(val1); stack.append(val2)
    elif op == read: prompt = stack.pop(); List[prompt] = Eval(raw_input("%s "%prompt)); stack.append(List[prompt])
def Eval(expr, stack=None):
    ops = "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s"%(mul, add, sub, div, Pow, assign, call, fac, duf, mod, read, kill, clr, STO, RET, curs, dup, swap)
    if stack is None:
        stack = []
    expr, ops = shlex.split(string.lower(expr)), ops.split()
    for i in expr:
        if i[0] != ';':
            if i not in ops: stack.append(i)
            elif i in ops: builtin_op(i, stack)
        else: stack.append("ok")
    return stack[0]
def shell():
    try:
        x = ""
        while x != "quit":
            x = raw_input("star>   ")
            try: l = Eval(x)
            except KeyError: l = "does not exist"
            except: l = "parse error!"
            if l != None: print "   =>",l,"\n"
    except (EOFError, KeyboardInterrupt): print
if len(sys.argv) > 1:
    x = open(sys.argv[1], 'r'); l = x.readlines(); x.close()
    for i in l:
        if i[0] != ";":
            i = ' '.join(i.split())
            x = Eval(i)
            if x != None: print i,"\n   =>",x,"\n"
        else: pass
    shell()
else: shell()

Наконец, вот моя версия на Python, которая намного яснее (на мой взгляд, в любом случае), чем написанный вами Python:

import shlex, functools, sys, StringIO

def bin_numeric_op(func):
    @functools.wraps(func)
    def execute(self):
        n2, n1 = self._stack.pop(), self._stack.pop()
        n1 = float(n1)
        n2 = float(n2)
        self._stack.append(func(n1, n2))
    return execute

def relational_op(func):
    @functools.wraps(func)
    def execute(self):
        n2, n1 = self._stack.pop(), self._stack.pop()
        self._stack.append(bool(func(n1, n2)))
    return execute

def bin_bool_op(func):
    @functools.wraps(func)
    def execute(self):
        n2, n1 = self._stack.pop(), self._stack.pop()
        n1 = bool(n1)
        n2 = bool(n2)
        self._stack.append(bool(func(n1, n2)))
    return execute

class Interpreter(object):
    def __init__(self):
        self._stack = []
        self._vars = {}
        self._squarestack = []

    def processToken(self, token):
        if token == '[':
            self._squarestack.append(len(self._stack))
        # Currently inside square brackets, don't execute
        elif len(self._squarestack) > 0:
            if token == ']':
                startlist = self._squarestack.pop()
                lst = self._stack[startlist:]
                self._stack[startlist:] = [tuple(lst)]
            else:
                self._stack.append(token)
        # Not current inside list and close square token, something's wrong.
        elif token == ']':
            raise ValueError("Unmatched ']'")
        elif token in self.builtin_ops:
            self.builtin_ops[token](self)
        else:
            self._stack.append(token)
    def get_stack(self):
        return self._stack
    def get_vars(self):
        return self._vars
    @bin_numeric_op
    def add(n1, n2):
        return n1 + n2
    @bin_numeric_op
    def mul(n1, n2):
        return n1 * n2
    @bin_numeric_op
    def div(n1, n2):
        return n1 / n2
    @bin_numeric_op
    def sub(n1, n2):
        return n1 - n2
    @bin_numeric_op
    def mod(n1, n2):
        return n1 % n2
    @bin_numeric_op
    def Pow(n1, n2):
        return n1**n2
    @relational_op
    def less(v1, v2):
        return v1 < v2
    @relational_op
    def lesseq(v1, v2):
        return v1 <= v2
    @relational_op
    def greater(v1, v2):
        return v1 > v2
    @relational_op
    def greatereq(v1, v2):
        return v1 > v2
    @relational_op
    def isequal(v1, v2):
        return v1 == v2
    @relational_op
    def isnotequal(v1, v2):
        return v1 != v2
    @bin_bool_op
    def bool_and(v1, v2):
        return v1 and v2
    @bin_bool_op
    def bool_or(v1, v2):
        return v1 or v2
    def bool_not(self):
        stack = self._stack
        v1 = stack.pop()
        stack.append(not v1)
    def if_func(self):
        stack = self._stack
        pred = stack.pop()
        code = stack.pop()
        if pred:
            self.run(code)
    def ifelse_func(self):
        stack = self._stack
        pred = stack.pop()
        nocode = stack.pop()
        yescode = stack.pop()
        code = yescode if pred else nocode
        self.run(code)
    def store(self):
        stack = self._stack
        value = stack.pop()
        varname = stack.pop()
        self._vars[varname] = value
    def fetch(self):
        stack = self._stack
        varname = stack.pop()
        stack.append(self._vars[varname])
    def remove(self):
        varname = self._stack.pop()
        del self._vars[varname]
    # The default argument is because this is used internally as well.
    def run(self, code=None):
        if code is None:
            code = self._stack.pop()
        for tok in code:
            self.processToken(tok)
    def dup(self):
        self._stack.append(self._stack[-1])
    def swap(self):
        self._stack[-2:] = self._stack[-1:-3:-1]
    def pop(self):
        self._stack.pop()
    def showstack(self):
        print"%r" % (self._stack,)
    def showvars(self):
        print "%r" % (self._vars,)
    builtin_ops = {
        '+': add,
        '*': mul,
        '/': div,
        '-': sub,
        '%': mod,
        '^': Pow,
        '<': less,
        '<=': lesseq,
        '>': greater,
        '>=': greatereq,
        '==': isequal,
        '!=': isnotequal,
        '&&': bool_and,
        '||': bool_or,
        'not': bool_not,
        'if': if_func,
        'ifelse': ifelse_func,
        '!': store,
        '@': fetch,
        'del': remove,
        'call': run,
        'dup': dup,
        'swap': swap,
        'pop': pop,
        'stack': showstack,
        'vars': showvars
        }

def shell(interp):
    try:
        while True:
            x = raw_input("star>   ")
            msg = None
            try:
                interp.run(shlex.split(x))
            except KeyError:
                msg = "does not exist"
            except:
                sys.excepthook(*sys.exc_info())
                msg = "parse error!"
            if msg != None:
                print "   =>",msg,"\n"
            else:
                print "   => %r\n" % (interp.get_stack(),)
    except (EOFError, KeyboardInterrupt):
        print

interp = Interpreter()
if len(sys.argv) > 1:
    lex = shlex.shlex(open(sys.argv[1], 'r'), sys.argv[1])
    tok = shlex.get_token()
    while tok is not None:
        interp.processToken(tok)
        tok = lex.get_token()
shell(interp)

ЭтоНовая версия поддерживает операторы if и ifelse.С помощью этого и вызова функций можно реализовать функции fib и fact на языке.Я добавлю, как вы будете определять их позже.

Вот как вы должны определить функцию fib:

star>   fib [ dup [ pop 1 0 + ] swap [ dup 1 - fib @ call swap 2 - fib @ call + ] swap 0 + 2 0 + < ifelse ] !
   => []

star>   15 fib @ call
   => [987.0]

Последовательность 0 + 2 0 + до того, как < будетзаставить сравнение быть числовым.

Также обратите внимание, как одиночные символы [ и ] заключают в кавычки операторы.Они приводят к тому, что все между ними не выполняется и вместо этого сохраняется в стеке как единый список элементов.Это ключ к определению функций.Функции - это последовательность токенов, которые вы можете выполнить с помощью оператора call.Они также могут использоваться для «анонимных блоков», которые являются чем-то вроде скрещивания между lambda выражениями и стандартным блоком Python.Они используются в функции fib для двух возможных путей оператора ifelse.

Синтаксический анализатор для этого смехотворно прост.И shlex достаточно мощный как лексер для этого простого языка.Другие проекты будут извлекать отдельные элементы из списка.Создание нового списка, который состоит только из части предыдущего списка.«Листификация» одного токена в стеке.Реализация while примитива.Числовые операторы, которые работают с целыми числами (в действительности Forth, числовые операции по умолчанию работают с целыми числами, и вам нужно указать что-то вроде +., чтобы получить версию с плавающей запятой).И некоторые операции с символьными токенами, которые позволяют манипулировать строками.Возможно, будет достаточно операций «split» и «join», которые превращают токен в список отдельных токенов для символов или объединяют список в один токен.

2 голосов
/ 15 июня 2011

Правильный ответ зависит от того, о чем вы беспокоитесь. Если вас беспокоит наличие масштабируемого решения, в котором будет расти сложность языка, вам, вероятно, следует начать изучение / использование одного из модулей синтаксического анализатора. Это потенциально ответ, если вы беспокоитесь о производительности, поскольку некоторые модули, вероятно, будут лучше оптимизированы, чем те, которые вы могли бы легко сгенерировать вручную.

Если, с другой стороны, вас интересует изучение, проверьте алгоритм маневрового двора . Возможно, вы могли бы создать словарь функций (который будет быстрее, чем вы, если оператор) с операциями в соответствии с:

funcs = {}
funcs["+"] = lambda x, y: x + y
funcs["*"] = lambda x, y: y * y

Тогда в функции Simp вы можете вызвать

func = funcs.get[Op]
if func is not None:
    func[Op](num1,num2)
else:
    #whatever you want to do here
1 голос
/ 15 июня 2011

Вы можете иметь словарь, где хранить переменные и связывать их с именем функции.

Например, предположим, что вы читаете код за строкой:

a = 1
b = 2
c = a + b
function x() 
   d = 4
   e = 5
   f = d + e 
end

Когда вы определяете переменные (a, b, c), вы сохраняете их в виде списка и этого списка в области видимости, это может быть глобальная область видимости, что-то вроде:

variables = scopes["global"]
variables.append( "a" ) 

У вас может быть похожая структура данных для функций, поэтому, когда вы обнаруживаете функцию, вы добавляете ее в эту структуру:

fs = functions["global"]
fs.append("x")

И вы также добавляете новую «область видимости» в словарь областей действия

scopes["x"] = [] // the function x doesn't have any var 

Когда вы найдете новый var и , если вы находитесь внутри определения функции, вы сохраняете этот новый var в этой "области видимости"

variables = scopes["x"]
variables.append("d")

Имеет ли это смысл?

Все это должно быть выполнимо в вашей текущей реализации. Если вы хотите сделать что-то более серьезное, я настоятельно рекомендую вам купить эту книгу

http://pragprog.com/titles/tpdsl/language-implementation-patterns

Несмотря на то, что он написан с использованием Java в качестве примера, он даст вам прочную основу языковых приложений и его очень легко читать.

Тогда у вас должны быть инструменты для:

  1. Создать лексер (разбить входные данные на токены)
  2. Анализ (проверка, если токены соответствуют вашим языковым правилам)
  3. AST (чтобы пройти ваш ввод)
  4. Идентифицировать символы программы (например, переменные и функции)
  5. Построить переводчика.

Надеюсь, это поможет

1 голос
/ 14 июня 2011

Похоже, вы пытаетесь написать что-то вроде этого Forth в Python.

1 голос
/ 14 июня 2011

Вам нужно преобразовать последовательность символов (числа, операции с числами, скобки) в древовидную структуру, которая представляет собой вычисление, выраженное вашей последовательностью символов.Такая вещь - в точности работа '' парсера ''. Возможно, вы захотите взглянуть на простые парсеры, подобные этому http://en.wikipedia.org/wiki/LL_parser Они просты в коде, и вы можете вычислять таблицы синтаксического анализа карандашом и бумагой,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...