Since you cite some tests, it sounds like you've at least taken a stab at the problem. I assume you've already defined a single number, which can be integer or real - doesn't matter, you are converting everything to float anyway - and a fraction of two numbers, probably something like this:
from pyparsing import Regex, Optional
number = Regex(r"\d+(\.\d*)?").setParseAction(lambda t: float(t[0]))
fraction = number("numerator") + "/" + number("denominator")
fraction.setParseAction(lambda t: t.numerator / t.denominator)
(Note the use of parse actions, which do the floating point conversion and fractional division right at parse time. I prefer to do this while parsing, when I know something is a number or a fraction or whatever, instead of coming back later and sifting through a bunch of fragmented strings, trying to recreate the recognition logic that the parser has already done.)
Here are the test cases I composed for your problem, made up of a whole number, a fraction, and a whole number and fraction, using both integers and reals:
tests = """\
1
1.0
1/2
1.0/2.0
1 1/2
1.0 1/2
1.0 1.0/2.0""".splitlines()
for t in tests:
print t, fractExpr.parseString(t)
The last step is how to define a fractional expression that can be a single number, a fraction, or a single number and a fraction.
Since pyparsing is left-to-right, it does not do the same kind of backtracking like regexen do. So this expression wont work so well:
fractExpr = Optional(number) + Optional(fraction)
To sum together the numeric values that might come from the number and fraction parts, add this parse action:
fractExpr.setParseAction(lambda t: sum(t))
Our tests print out:
1 [1.0]
1.0 [1.0]
1/2 [1.0]
1.0/2.0 [1.0]
1 1/2 [1.5]
1.0 1/2 [1.5]
1.0 1.0/2.0 [1.5]
For the test case 1/2
, containing just a fraction by itself, the leading numerator matches the Optional(number)
term, but that leaves us just with "/2", which doesn't match the Optional(fraction)
- fortunately, since the second term is optional, this "passes", but it's not really doing what we want.
We need to make fractExpr a little smarter, and have it look first for a lone fraction, since there is this potential confusion between a lone number and the leading numerator of a fraction. The easiest way to do this is to make fractExpr read:
fractExpr = fraction | number + Optional(fraction)
Now with this change, our tests come out better:
1 [1.0]
1.0 [1.0]
1/2 [0.5]
1.0/2.0 [0.5]
1 1/2 [1.5]
1.0 1/2 [1.5]
1.0 1.0/2.0 [1.5]
There are a couple of classic pitfalls with pyparsing, and this is one of them. Just remember that pyparsing only does the lookahead that you tell it to, otherwise it is just straight left-to-right parsing.