diff --git a/examples/pyparsing/README.md b/examples/pyparsing/README.md
new file mode 100644
index 0000000..02e6401
--- /dev/null
+++ b/examples/pyparsing/README.md
@@ -0,0 +1,18 @@
+# pyparsing Examples
+
+Each sub-directory contains a self-contained example. The order in
+which the examples are to appear is specified in `order.json` (an
+array of directory names in the expected order).
+
+In each example directory you'll find:
+
+* `config.toml` - must conform to the specification outlined here:
+ https://docs.pyscript.net/latest/user-guide/configuration/ This is
+ parsed and ultimately turned into a JSON representation as part of
+ the package's API object.
+* `setup.py` - Python code for contextual and environmental setup,
+ NOT SEEN BY THE END USER, but is run before the `code.py` code is
+ evaluated. Allows us to create useful (IPython) shims, avoid
+ repeating boilerplate and whatnot.
+* `code.py` - the actual code added to the editor which forms the
+ practical example of using the package.
diff --git a/examples/pyparsing/arithmetic_expression/code.py b/examples/pyparsing/arithmetic_expression/code.py
new file mode 100644
index 0000000..15635fb
--- /dev/null
+++ b/examples/pyparsing/arithmetic_expression/code.py
@@ -0,0 +1,74 @@
+# ---------------------------------------------------------------------
+# Building an arithmetic expression parser with infix_notation.
+# ---------------------------------------------------------------------
+#
+# infix_notation is the high-level helper for expressions with operator
+# precedence. You give it an operand and a list of (operator, arity,
+# associativity, action) tuples, ordered from highest to lowest precedence.
+
+import pyparsing as pp
+from pyparsing import (
+ one_of, infix_notation, OpAssoc, pyparsing_common,
+)
+
+
+heading("Arithmetic with operator precedence")
+note(
+ "We'll parse expressions like 2 + 3 * (4 - 1) and "
+ "evaluate them on the fly using parse actions."
+)
+
+# Each parse action receives the matched tokens and returns the computed
+# value. By returning a number, we replace the matched tokens in-place so
+# higher-precedence rules see already-evaluated sub-expressions.
+
+def eval_signed(tokens):
+ sign, value = tokens[0]
+ return -value if sign == "-" else value
+
+
+def eval_binop(tokens):
+ # Tokens are flat: [a, op, b, op, c, ...] for left-associative chains.
+ flat = tokens[0]
+ result = flat[0]
+ for op, rhs in zip(flat[1::2], flat[2::2]):
+ if op == "+": result += rhs
+ elif op == "-": result -= rhs
+ elif op == "*": result *= rhs
+ elif op == "/": result /= rhs
+ return result
+
+
+number = pyparsing_common.number # parses ints and floats, returns numerics
+
+expression = pp.Forward()
+expression <<= infix_notation(
+ number,
+ [
+ (one_of("+ -"), 1, OpAssoc.RIGHT, eval_signed),
+ (one_of("* /"), 2, OpAssoc.LEFT, eval_binop),
+ (one_of("+ -"), 2, OpAssoc.LEFT, eval_binop),
+ ],
+)
+
+cases = [
+ "2 + 3",
+ "2 + 3 * 4",
+ "(2 + 3) * 4",
+ "10 - 2 - 3", # left-associative subtraction
+ "-5 + 2 * -3", # unary minus
+ "1 + 2 * 3 - 4 / 2",
+]
+
+rows = ["
| Expression | Result |
|---|---|
{text} | {value} |
{text}
"), append=True) diff --git a/examples/pyparsing/hello_greeting/code.py b/examples/pyparsing/hello_greeting/code.py new file mode 100644 index 0000000..a524105 --- /dev/null +++ b/examples/pyparsing/hello_greeting/code.py @@ -0,0 +1,52 @@ +""" +A first look at pyparsing: build a tiny grammar in pure Python. + +pyparsing lets you compose grammars from class instances, using +'+' for sequence and '|' for alternatives. The result of parsing +is a ParseResults object, which behaves like a list and (when you +add results names) like a dict. +""" +from IPython.core.display import display, HTML +# Package imports for this example. +import pyparsing as pp +from pyparsing import Word, alphas + + +heading("1. Parsing a friendly greeting") +note( + "Our grammar says: a word, then a comma, then another word, " + "then an exclamation mark. pyparsing handles whitespace for us." +) + +# Word(alphas) matches a run of letters. The literal strings "," and "!" +# match themselves. The '+' operator concatenates expressions. +greeting = Word(alphas) + "," + Word(alphas) + "!" + +samples = [ + "Hello, World!", + "Hi,Friend!", # no spaces + " Hey , Ada ! ", # extra whitespace everywhere +] + +for text in samples: + parsed = greeting.parse_string(text) + note(f"{text!r} → {list(parsed)}")
+
+heading("2. Naming parts of the match")
+note(
+ "Attach names with set_results_name (or the shorthand '()' call). "
+ "Named pieces become attributes and dict keys on the result."
+)
+
+named_greeting = (
+ Word(alphas)("salutation")
+ + ","
+ + Word(alphas)("addressee")
+ + "!"
+)
+
+result = named_greeting.parse_string("Howdy, Partner!")
+note(f"As a list: {list(result)}")
+note(f"result.salutation = {result.salutation!r}")
+note(f"result.addressee = {result.addressee!r}")
+note(f"As a dict: {result.as_dict()}")
diff --git a/examples/pyparsing/hello_greeting/config.toml b/examples/pyparsing/hello_greeting/config.toml
new file mode 100644
index 0000000..ced2a53
--- /dev/null
+++ b/examples/pyparsing/hello_greeting/config.toml
@@ -0,0 +1 @@
+packages = ["pyparsing"]
diff --git a/examples/pyparsing/hello_greeting/setup.py b/examples/pyparsing/hello_greeting/setup.py
new file mode 100644
index 0000000..a536bd1
--- /dev/null
+++ b/examples/pyparsing/hello_greeting/setup.py
@@ -0,0 +1,43 @@
+"""
+Shim IPython's display API onto PyScript so example code written in a
+Jupyter/IPython idiom runs unmodified in the browser.
+"""
+
+import sys
+import types
+import js
+from pyscript import window, HTML, display as _display
+
+js.alert = window.alert
+
+
+def display(*args, **kwargs):
+ """Wrap pyscript.display so output lands in the example target."""
+ return _display(
+ *args, **kwargs, target=__pyscript_display_target__,
+ )
+
+
+ipython = types.ModuleType("IPython")
+core = types.ModuleType("IPython.core")
+core_display = types.ModuleType("IPython.core.display")
+core_display.display = display
+core_display.HTML = HTML
+ipython.core = core
+core.display = core_display
+ipython.version_info = (9, 0, 2, '')
+ipython.get_ipython = lambda: None
+ipython.display = core_display
+sys.modules["IPython"] = ipython
+sys.modules["IPython.core"] = core
+sys.modules["IPython.core.display"] = core_display
+sys.modules["IPython.display"] = core_display
+
+
+def heading(text, level=2):
+ display(HTML(f"{text}
"), append=True) + diff --git a/examples/pyparsing/order.json b/examples/pyparsing/order.json new file mode 100644 index 0000000..537a6d4 --- /dev/null +++ b/examples/pyparsing/order.json @@ -0,0 +1,5 @@ +[ + "hello_greeting", + "arithmetic_expression", + "structured_log_lines" +] diff --git a/examples/pyparsing/structured_log_lines/code.py b/examples/pyparsing/structured_log_lines/code.py new file mode 100644 index 0000000..e878f2e --- /dev/null +++ b/examples/pyparsing/structured_log_lines/code.py @@ -0,0 +1,89 @@ +# --------------------------------------------------------------------- +# A real-world flavor: extracting structured fields from log lines. +# --------------------------------------------------------------------- + +import pyparsing as pp +from pyparsing import ( + Word, Suppress, Combine, QuotedString, one_of, + nums, alphas, alphanums, Group, OneOrMore, pyparsing_common, +) + +# +# We'll define a grammar for lines like: +# +# 2026-04-15 09:31:02 [INFO] user=ada action="login" ms=42 +# +# and pull out timestamp, level, and a dictionary of key=value pairs. + +heading("Defining the grammar piece by piece") + +# Date and time, built from digit runs joined by literal separators. +# Combine merges adjacent matches into one string token. +integer = Word(nums) +date = Combine(integer + "-" + integer + "-" + integer) +time = Combine(integer + ":" + integer + ":" + integer) +timestamp = Combine(date + " " + time)("timestamp") + +# Log level in square brackets. Suppress(...) matches but discards. +level = ( + Suppress("[") + + one_of("DEBUG INFO WARN ERROR")("level") + + Suppress("]") +) + +# A key=value pair. Values can be bare words, quoted strings, or numbers. +key = Word(alphas, alphanums + "_") +bare_value = Word(alphanums + "_.-") +quoted_value = QuotedString('"') +value = quoted_value | pyparsing_common.number | bare_value +pair = Group(key("key") + Suppress("=") + value("value")) + +pairs = Group(OneOrMore(pair))("fields") + +log_line = timestamp + level + pairs + +heading("Parsing some log lines") + +lines = [ + '2026-04-15 09:31:02 [INFO] user=ada action="login" ms=42', + '2026-04-15 09:31:07 [WARN] user=ada action="slow_query" ms=1873 table=orders', + '2026-04-15 09:31:12 [ERROR] user=bob action="checkout" code=500 reason="db timeout"', +] + +rows = [ + "| Timestamp | Level | Fields |
|---|---|---|
{parsed.timestamp} | "
+ f"{parsed.level} | " + f"{fields} |
{ts} [{lvl}] {fs}"), append=True)
diff --git a/examples/pyparsing/structured_log_lines/config.toml b/examples/pyparsing/structured_log_lines/config.toml
new file mode 100644
index 0000000..ced2a53
--- /dev/null
+++ b/examples/pyparsing/structured_log_lines/config.toml
@@ -0,0 +1 @@
+packages = ["pyparsing"]
diff --git a/examples/pyparsing/structured_log_lines/setup.py b/examples/pyparsing/structured_log_lines/setup.py
new file mode 100644
index 0000000..78a0606
--- /dev/null
+++ b/examples/pyparsing/structured_log_lines/setup.py
@@ -0,0 +1,17 @@
+"""Lighter setup for example 3."""
+import js
+from pyscript import window, HTML, display as _display
+
+js.alert = window.alert
+
+
+def display(*args, **kwargs):
+ return _display(*args, **kwargs, target=__pyscript_display_target__)
+
+
+def heading(text, level=2):
+ display(HTML(f"{text}
"), append=True)