-1

So I am making a parser, but the program doesn't parse commas. For example:

>>> evaluate("round(pi)")
3

>>> evaluate("round(pi, 2)")
SyntaxError: Expected {{[- | +] {{{{{{{W:(ABCD..., ABCD...) Suppress:("(") : ...} Suppress:(")")} | 'PI'} | 'E'} | 'PHI'} | 'TAU'} | {Combine:({{W:(+-01..., 0123...) [{"." [W:(0123...)]}]} [{{'E' [W:(+-)]} W:(0123...)}]}) | Combine:({{{[W:(+-)] "."} W:(0123...)} [{{'E' [W:(+-)]} W:(0123...)}]})}}} | {[- | +] Group:({{Suppress:("(") : ...} Suppress:(")")})}}, found ','  (at
char 8), (line:1, col:9)

How can the program parse commas that are used in functions? My objective is that functions like round(pi, 2) returns 3.14, or log(10, 10) returns 1.0.

TheOneMusic
  • 1,776
  • 3
  • 15
  • 39
  • 1
    The problem is that your definition of a function call only works for functions that take a single argument. Evaluating your functions will be extra difficult if they take a variable number of arguments, you won't know how many values to pop off the stack. As it is, once you support multiple arguments in a function, you'll have to keep track of which functions take 1 arg, which take 2, etc. Since it is your parser, I suggest you not support variable arguments. – PaulMcG Jul 31 '19 at 22:29
  • 1
    Find the place in your parser where functions are parsed, and change the single argument `expr` to a delimited list of `expr`s. – PaulMcG Jul 31 '19 at 22:29
  • 1
    My previous comment already answered that, but you will have to read it, understand it, and apply it using pyparsing's helpful methods. I would like you to start making more of an effort on your own with this. You already have a dict of the supported functions (which I am already very familiar with), think about how, if you want some functions to support multiple arguments, how that would be added to this dict. Then you would know how many args to pop off the stack for a particular function. If this is too advanced of Python then you should work through some tutorials. – PaulMcG Aug 01 '19 at 00:12

2 Answers2

1

If you have a parser that parses a single integer in parentheses like this:

LPAR, RPAR = map(Suppress, "()")
integer = Word(nums)
int_values = LPAR + integer + RPAR

and you want to change it to accept a list of integers instead, you would write:

int_values = LPAR + delimitedList(integer) + RPAR

You would also probably use Group to keep these parsed values together logically:

int_values = LPAR + Group(delimitedList(integer)) + RPAR
PaulMcG
  • 62,419
  • 16
  • 94
  • 130
1

So, I used:

expr = Forward()
exprlist = delimitedList(expr)
atom = ((Optional(oneOf("- +")) +
        (Word(alphas, alphas + nums + "_$") + lpar + exprlist + rpar | CaselessLiteral("PI") | e | CaselessLiteral("PHI") | CaselessLiteral("TAU") | fnumber).setParseAction(self.__push_first__))
        | Optional(oneOf("- +")) + Group(lpar + exprlist + rpar)
        ).setParseAction(self.__push_minus__)

instead of:

expr = Forward()
atom = ((Optional(oneOf("- +")) +
                 (ident + lpar + expr + rpar | pi | e | tau | fnumber).setParseAction(self.__push_first__))
                | Optional(oneOf("- +")) + Group(lpar + expr + rpar)
                ).setParseAction(self.__push_minus__)

Then, in the __evaluate_stack__(self, s) method, I tried:

if op in self.fn:
    if op == "round":
        try:
            op2 = self.__evaluate_stack__(s)
        except IndexError:
            op2 = 0   # Default value of second argument of round() function
        op1 = self.__evaluate_stack__(s)
        return self.fn[op](op1, int(op2))
    return self.fn[op](self.__evaluate_stack__(s))

The problem is that round(pi) (without second argument) raises SyntaxError, and round(pi, 4, 5) returns 4 instead of throwing SyntaxError. What is wrong with it?

TheOneMusic
  • 1,776
  • 3
  • 15
  • 39
  • 1
    Try adding a parse action on `exprlist` to push the length of the list. Then the evaluator could pop that number first, and then pop that many arguments. You are really getting very close! Note that now *all* your functions will need to pop this arg count, even those that you know only take 1 arg. – PaulMcG Aug 01 '19 at 20:31
  • @PaulMcG Thank you, but how do I do that? Do I use `exprlist = delimitedList(expr).setParseAction(self.__push_first__)`? I tried using: `def __push_list__(self, strg, loc, toks): self.exprStack.append(tuple(toks))` and then use `exprlist = delimitedList(expr).setParseAction(self.__push_list__)`, but it doesn't work. Indeed, when I try `round(pi, 4)` I get: `NameError: function ('PI', '4') is not defined.` – TheOneMusic Aug 02 '19 at 21:55
  • 1
    Probably a better name is `__push_list_length__`, because you don't want to push the whole list - the list elements already got pushed as part of their own parse actions. You just need to append to exprStack the length of the list. And then when evaluating, pull the number of arguments off the stack before evaluating them. – PaulMcG Aug 02 '19 at 23:36