summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorptmcg <ptmcg@austin.rr.com>2020-10-25 14:42:43 -0500
committerptmcg <ptmcg@austin.rr.com>2020-10-25 14:42:43 -0500
commit96e0fab07788fca87e1473b0ae755335d6988895 (patch)
tree3a59a97f7221042f793c7a43adb805cce5787034
parent27dc324608a8c83afa47b296c52b7d6c9aa8795e (diff)
downloadpyparsing-git-96e0fab07788fca87e1473b0ae755335d6988895.tar.gz
Add number_words.py example; update diagramming code
-rw-r--r--examples/number_words.py89
-rw-r--r--pyparsing/diagram/__init__.py19
2 files changed, 105 insertions, 3 deletions
diff --git a/examples/number_words.py b/examples/number_words.py
new file mode 100644
index 0000000..724059f
--- /dev/null
+++ b/examples/number_words.py
@@ -0,0 +1,89 @@
+# number_words.py
+#
+# Copyright 2020, Paul McGuire
+#
+# Parser/evaluator for expressions of numbers as written out in words:
+# - one
+# - seven
+# - twelve
+# - twenty six
+# - forty-two
+# - one hundred and seven
+#
+#
+# BNF:
+"""
+ optional_and ::= ["and" | "-"]
+ optional_dash ::= ["-"]
+ units ::= one | two | three | ... | nine
+ teens_only ::= eleven | twelve | ... | nineteen
+ teens ::= ten | teens_only
+ tens ::= twenty | thirty | ... | ninety
+ hundreds ::= (units | teens_only | tens optional_dash units) "hundred"
+ one_to_99 ::= units | teens | (tens [optional_dash units])
+ thousands = one_to_99 "thousand"
+
+ number = [thousands] [hundreds] optional_and units | [thousands] optional_and hundreds | thousands
+"""
+import pyparsing as pp
+from operator import mul
+import pyparsing.diagram
+
+def define_numeric_word(s, value):
+ return pp.CaselessKeyword(s).addParseAction(lambda: value)
+
+def define_numeric_word_range(s, vals):
+ if isinstance(s, str):
+ s = s.split()
+ return pp.MatchFirst(define_numeric_word(nm, nm_value) for nm, nm_value in zip(s, vals))
+
+opt_dash = pp.Optional(pp.Suppress("-")).setName("optional '-'")
+opt_and = pp.Optional((pp.CaselessKeyword("and") | "-").suppress()).setName("optional 'and'")
+
+zero = define_numeric_word_range("zero oh", [0, 0])
+one_to_9 = define_numeric_word_range("one two three four five six seven eight nine", range(1, 9 + 1)).setName("1-9")
+eleven_to_19 = define_numeric_word_range("eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen",
+ range(11, 19 + 1)).setName("eleven_to_19")
+ten_to_19 = (define_numeric_word("ten", 10) | eleven_to_19).setName("ten_to_19")
+one_to_19 = (one_to_9 | ten_to_19).setName("1-19")
+tens = define_numeric_word_range("twenty thirty forty fifty sixty seventy eighty ninety", range(20, 90+1, 10))
+hundreds = (one_to_9 | eleven_to_19 | (tens + opt_dash + one_to_9)) + define_numeric_word("hundred", 100)
+one_to_99 = (one_to_19 | (tens + pp.Optional(opt_dash + one_to_9)).addParseAction(sum)).setName("1-99")
+one_to_999 = ((pp.Optional(hundreds + opt_and) + one_to_99 | hundreds).addParseAction(sum)).setName("1-999")
+thousands = one_to_999 + define_numeric_word("thousand", 1000)
+hundreds.setName("100s")
+thousands.setName("1000s")
+
+def multiply(t):
+ return mul(*t)
+hundreds.addParseAction(multiply)
+thousands.addParseAction(multiply)
+
+numeric_expression = (pp.Optional(thousands + opt_and)
+ + pp.Optional(hundreds + opt_and)
+ + one_to_99
+ | pp.Optional(thousands + opt_and)
+ + hundreds
+ | thousands
+ ).setName("numeric_words")
+numeric_expression.addParseAction(sum)
+
+
+if __name__ == '__main__':
+ numeric_expression.runTests("""
+ one
+ seven
+ twelve
+ twenty six
+ forty-two
+ two hundred
+ twelve hundred
+ one hundred and eleven
+ ninety nine thousand nine hundred and ninety nine
+ nine hundred thousand nine hundred and ninety nine
+ nine hundred and ninety nine thousand nine hundred and ninety nine
+ nineteen hundred thousand nineteen hundred and ninety nine
+ """)
+
+ # create railroad diagram
+ numeric_expression.create_diagram("numeric_words_diagram.html")
diff --git a/pyparsing/diagram/__init__.py b/pyparsing/diagram/__init__.py
index e9ad70d..9678368 100644
--- a/pyparsing/diagram/__init__.py
+++ b/pyparsing/diagram/__init__.py
@@ -275,7 +275,7 @@ def _to_diagram_element(
element: pyparsing.ParserElement,
parent: Optional[EditablePartial],
lookup: ConverterState = None,
- vertical: Union[int, bool] = 5,
+ vertical: Union[int, bool] = 3,
index: int = 0,
name_hint: str = None,
) -> Optional[EditablePartial]:
@@ -341,9 +341,9 @@ def _to_diagram_element(
ret = EditablePartial.from_call(railroad.Sequence, items=[])
elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)):
if _should_vertical(vertical, len(exprs)):
- ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[])
- else:
ret = EditablePartial.from_call(railroad.Choice, 0, items=[])
+ else:
+ ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[])
elif isinstance(element, pyparsing.Optional):
ret = EditablePartial.from_call(railroad.Optional, item="")
elif isinstance(element, pyparsing.OneOrMore):
@@ -424,3 +424,16 @@ def _to_diagram_element(
)
else:
return ret
+
+# monkeypatch .create_diagram method onto ParserElement
+def _create_diagram(expr: pyparsing.ParserElement, output_html):
+ railroad = to_railroad(expr)
+ if isinstance(output_html, str):
+ with open(output_html, "w", encoding="utf-8") as diag_file:
+ diag_file.write(railroad_to_html(railroad))
+ else:
+ # we were passed a file-like object, just write to it
+ output_html.write(railroad_to_html(railroad))
+
+
+pyparsing.ParserElement.create_diagram = _create_diagram