2
line = "add(multiply(add(2,3),add(4,5)),1)"

def readLine(line):
    countLeftBracket=0
    string = ""
    for char in line:
        if char !=")":
            string += char
        else:
            string +=char
            break

    for i in string:
        if i=="(":
            countLeftBracket+=1

    if countLeftBracket>1:
        cutString(string)
    else:
        return execute(string)

def cutString(string):
    countLeftBracket=0

    for char in string:
        if char!="(":
            string.replace(char,'')
        elif char=="(":
            string.replace(char,'')
            break

    for char in string:
        if char=="(":
            countLeftBracket+=1

    if countLeftBracket>1:
        cutString(string)
    elif countLeftBracket==1:
        return execute(string)

def add(num1,num2):
    return print(num1+num2)

def multiply(num1,num2):
    return print(num1*num2)

readLines(line)

I need to execute the whole line string. I tried to cut each function inside of brackets one by one and replace them with the result, but I am kind of lost. Not sure how to continue, my code gets the error:

  File "main.py", line 26, in cutString                                                                                 
    if char!="(":                                                                                                       
RuntimeError: maximum recursion depth exceeded in comparison 

Give me an idea of where to move, which method to use?

10
  • What is the expected output? and does execute do? Commented Jan 30, 2020 at 22:23
  • In general I would suggest starting by writing a lexer that returns a stream of tokens. Parsing by string-substitution is just not the way to approach this. Commented Jan 30, 2020 at 22:25
  • @zamir answer to whole nested function. In this case, the answer is 46, but I need to handle more random input in that format. Commented Jan 30, 2020 at 22:25
  • @thebjorn can you give me some example, please? I am not really familiar with that. Commented Jan 30, 2020 at 22:26
  • 1
    I would suggest starting here: pyparsing-docs.readthedocs.io/en/latest/HowToUsePyparsing.html Commented Jan 30, 2020 at 22:28

3 Answers 3

5

Here is a solution that uses pyparsing, and as such will be much easier to expand:

from pyparsing import *

first a convenience function (use the second tag function and print the parse tree to see why)

def tag(name):
    """This version converts ["expr", 4] => 4
       comment in the version below to see the original parse tree
    """
    def tagfn(tokens):
        tklist = tokens.asList()
        if name == 'expr' and len(tklist) == 1:
            # LL1 artifact removal
            return tklist
        return tuple([name] + tklist)
    return tagfn

# def tag(name):
#     return lambda tokens: tuple([name] + tokens.asList())

Our lexer needs ot recognize left and right parenthesis, integers, and names. This is how you define them with pyparsing:

LPAR = Suppress("(")
RPAR = Suppress(")")
integer = Word(nums).setParseAction(lambda s,l,t: [int(t[0])])
name = Word(alphas)

our parser has function calls, which take zero or more expressions as parameters. A function call is also an expression, so to deal with the circularity we have to forward declare expr and fncall:

expr = Forward()
fncall = Forward()

expr << (integer | fncall).setParseAction(tag('expr'))
fnparams = delimitedList(expr)

fncall << (name + Group(LPAR + Optional(fnparams, default=[]) + RPAR)).setParseAction(tag('fncall'))

Now we can parse our string (we can add spaces and more or less than two parameters to functions as well):

line = "add(multiply(add(2,3),add(4,5)),1)"
res = fncall.parseString(line)

to see what is returned you can print it, this is called the parse-tree (or, since our tag function has simplified it, an abstract syntax tree):

import pprint
pprint.pprint(list(res))

which outputs:

[('fncall',
  'add',
  [('fncall',
    'multiply',
    [('fncall', 'add', [2, 3]), ('fncall', 'add', [4, 5])]),
   1])]

with the commented out tag function it would be (which is just more work to deal with for no added benefit):

[('fncall',
  'add',
  [('expr',
    ('fncall',
     'multiply',
     [('expr', ('fncall', 'add', [('expr', 2), ('expr', 3)])),
      ('expr', ('fncall', 'add', [('expr', 4), ('expr', 5)]))])),
   ('expr', 1)])]

Now define the functions that are available to our program:

FUNCTIONS = {
    'add': lambda *args: sum(args, 0),
    'multiply': lambda *args: reduce(lambda a, b: a*b, args, 1),
}

# print FUNCTIONS['multiply'](1,2,3,4)   # test that it works ;-)

Our parser is now very simple to write:

def parse(ast):
    if not ast:  # will not happen in our program, but it's good practice to exit early on no input
        return

    if isinstance(ast, tuple) and ast[0] == 'fncall':
        # ast is here ('fncall', <name-of-function>, [list-of-arguments])
        fn_name = ast[1]          # get the function name
        fn_args = parse(ast[2])   # parse each parameter (see elif below)
        return FUNCTIONS[fn_name](*fn_args)  # find and apply the function to its arguments
    elif isinstance(ast, list):
        # this is called when we hit a parameter list
        return [parse(item) for item in ast]
    elif isinstance(ast, int):
        return ast

Now call the parser on the result of the lexing phase:

>>> print parse(res[0])  # the outermost item is an expression
46
Sign up to request clarification or add additional context in comments.

Comments

3

Sounds like this could be solved with regex.

So this is an example of a single reduction

import re, operator
def apply(match):
   func_name = match.group(1) # what's outside the patentesis
   func_args = [int(x) for x in match.group(2).split(',')]
   func = {"add": operator.add, "multiply": operator.mul}
   return str(func[func_name](*func_args))
def single_step(line):
   return re.sub(r"([a-z]+)\(([^()]+)\)",apply,line)

For example:

line = "add(multiply(add(2,3),add(4,5)),1)"
print(single_step(line))

Would output:

add(multiply(5,9),1)

All that is left to do, is to loop until the expression is a number

while not line.isdigit():
   line = single_step(line)
print (line)

Will show

46

9 Comments

Copy the entire code from my answer, from top to bottom - it works
File "main.py", line 3, in apply func_name = g[1] TypeError: '_sre.SRE_Match' object is not subscriptable
File "main.py", line 8, in single_step return re.sub(r"([a-z]+)(([^()]+))",apply,line) File "/usr/lib/python3.4/re.py", line 179, in sub return _compile(pattern, flags).sub(repl, string, count)
you're using python2 !
though you should really mention if you're not using the latest version
|
3

You can use a generator function to build a very simple parser:

import re, operator
line, f = "add(multiply(add(2,3),add(4,5)),1)", {'add':operator.add, 'multiply':operator.mul}
def parse(d):
   n = next(d, None)
   if n is not None and n != ')':
     if n == '(':
       yield iter(parse(d))
     else:
       yield n
     yield from parse(d)

parsed = parse(iter(re.findall('\(|\)|\w+', line)))
def _eval(d):
   _r = []
   n = next(d, None)
   while n is not None:
      if n.isdigit():
        _r.append(int(n))
      else:
        _r.append(f[n](*_eval(next(d))))
      n = next(d, None)
   return _r

print(_eval(parsed)[0])

Output:

46

5 Comments

Thanks a lot really appreciate it
File "main.py", line 4 if (n:=next(d, None)) is not None and n != ')': ^ SyntaxError: invalid syntax
Same issue here, @Ajax1234 is assuming you have python 3.8, and you probably have a older version
@BayramJumageldiyev Yes, my original solution utilized assignment expressions, which are only available in Python 3.8. Please see my recent edit.
@UriGoren Actually the syntax I was originally using were assignment expressions which are only available in Python 3.8. I edited my solution to be compatible for Python versions <= 3.8

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.