1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
#
# one_to_ninety_nine.py
#
# Copyright 2021, Paul McGuire
#
# Parser/evaluator for expressions of numbers as written out in words:
# - one
# - seven
# - twelve
# - twenty six
# - forty-two
#
# BNF:
# units ::= one | two | three | ... | nine
# teens ::= ten | eleven | twelve | ... | nineteen
# tens ::= twenty | thirty | ... | ninety
# one_to_99 ::= units | teens | (tens [["-"] units])
#
import pyparsing as pp
def define_numeric_word_range(
names: str, from_: int, to_: int, step: int = 1
) -> pp.MatchFirst:
"""
Compose a MatchFirst of CaselessKeywords, given their names and values,
which when parsed, are converted to their value
"""
def define_numeric_word(nm: str, val: int):
return pp.CaselessKeyword(nm).add_parse_action(lambda: val)
names = names.split()
values = range(from_, to_ + 1, step)
return pp.MatchFirst(
define_numeric_word(name, value) for name, value in zip(names, values)
)
units = define_numeric_word_range(
"one two three four five six seven eight nine", 1, 9
).set_name("units")
teens = define_numeric_word_range(
"ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen",
10,
19,
).set_name("teens")
tens = define_numeric_word_range(
"twenty thirty forty fifty sixty seventy eighty ninety", 20, 90, step=10
).set_name("tens")
opt_dash = pp.Opt(pp.Suppress("-"))
twenty_to_99 = tens + pp.Opt(opt_dash + units)
one_to_99 = (units | teens | twenty_to_99).set_name("1-99")
# for expressions that parse multiple values, add them up
one_to_99.add_parse_action(sum)
numeric_expression = one_to_99
if __name__ == "__main__":
numeric_expression.run_tests(
"""
one
seven
twelve
twenty six
forty-two
"""
)
# create railroad diagram
numeric_expression.create_diagram("one_to_99_diagram.html", vertical=5)
|