Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/pyparsing/README.md
Original file line number Diff line number Diff line change
@@ -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.
74 changes: 74 additions & 0 deletions examples/pyparsing/arithmetic_expression/code.py
Original file line number Diff line number Diff line change
@@ -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 <code>2 + 3 * (4 - 1)</code> 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 = ["<table><tr><th>Expression</th><th>Result</th></tr>"]
for text in cases:
value = expression.parse_string(text, parse_all=True)[0]
rows.append(f"<tr><td><code>{text}</code></td><td>{value}</td></tr>")
rows.append("</table>")
display(HTML("".join(rows)), append=True)

note(
"Note how parse actions turn the parser into an evaluator: "
"each layer reduces matched tokens to a Python number, so by the "
"time we reach the top of the stack, parsing and evaluation are done."
)
1 change: 1 addition & 0 deletions examples/pyparsing/arithmetic_expression/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pyparsing"]
17 changes: 17 additions & 0 deletions examples/pyparsing/arithmetic_expression/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Lighter setup for example 2: same names as the first cell, no IPython shim."""
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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
52 changes: 52 additions & 0 deletions examples/pyparsing/hello_greeting/code.py
Original file line number Diff line number Diff line change
@@ -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"<code>{text!r}</code> &rarr; <code>{list(parsed)}</code>")

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: <code>{list(result)}</code>")
note(f"result.salutation = <code>{result.salutation!r}</code>")
note(f"result.addressee = <code>{result.addressee!r}</code>")
note(f"As a dict: <code>{result.as_dict()}</code>")
1 change: 1 addition & 0 deletions examples/pyparsing/hello_greeting/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pyparsing"]
43 changes: 43 additions & 0 deletions examples/pyparsing/hello_greeting/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

5 changes: 5 additions & 0 deletions examples/pyparsing/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"hello_greeting",
"arithmetic_expression",
"structured_log_lines"
]
89 changes: 89 additions & 0 deletions examples/pyparsing/structured_log_lines/code.py
Original file line number Diff line number Diff line change
@@ -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 = [
"<table><tr><th>Timestamp</th><th>Level</th><th>Fields</th></tr>"
]
for line in lines:
parsed = log_line.parse_string(line, parse_all=True)
fields = {p.key: p.value for p in parsed.fields}
rows.append(
f"<tr><td><code>{parsed.timestamp}</code></td>"
f"<td><strong>{parsed.level}</strong></td>"
f"<td><code>{fields}</code></td></tr>"
)
rows.append("</table>")
display(HTML("".join(rows)), append=True)

heading("Scanning a larger blob of text")
note(
"scan_string walks through arbitrary text and yields every match "
"of the grammar, along with its location. Great for extracting "
"structured data from messy input."
)

blob = """
Some preamble that isn't a log line at all.
2026-04-15 09:31:02 [INFO] user=ada action="login" ms=42
... noise in between ...
2026-04-15 09:31:99 [INFO] this line is malformed and will be skipped
2026-04-15 09:32:00 [INFO] user=ada action="logout" ms=8
"""

found = []
for tokens, start, end in log_line.scan_string(blob):
fields = {p.key: p.value for p in tokens.fields}
found.append((tokens.timestamp, tokens.level, fields))

note(f"Found <strong>{len(found)}</strong> well-formed log entries:")
for ts, lvl, fs in found:
display(HTML(f"<code>{ts} [{lvl}] {fs}</code>"), append=True)
1 change: 1 addition & 0 deletions examples/pyparsing/structured_log_lines/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pyparsing"]
17 changes: 17 additions & 0 deletions examples/pyparsing/structured_log_lines/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)