From f2c8d21bbea607746755023ee4dfd246fa00012c Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 29 May 2026 15:47:14 +0100 Subject: [PATCH 1/3] Add PyScript examples for pyparsing Generated by apply_llm_response.py from prompts/pyparsing/response.toml. Examples included: - hello_greeting: Hello, pyparsing - arithmetic_expression: An arithmetic expression evaluator - structured_log_lines: Parsing structured log lines Generated-By: apply_llm_response.py --- examples/pyparsing/README.md | 18 ++++ .../pyparsing/arithmetic_expression/code.py | 68 +++++++++++++++ .../arithmetic_expression/config.toml | 1 + .../pyparsing/arithmetic_expression/setup.py | 23 ++++++ examples/pyparsing/hello_greeting/code.py | 48 +++++++++++ examples/pyparsing/hello_greeting/config.toml | 1 + examples/pyparsing/hello_greeting/setup.py | 46 +++++++++++ examples/pyparsing/order.json | 5 ++ .../pyparsing/structured_log_lines/code.py | 82 +++++++++++++++++++ .../structured_log_lines/config.toml | 1 + .../pyparsing/structured_log_lines/setup.py | 24 ++++++ 11 files changed, 317 insertions(+) create mode 100644 examples/pyparsing/README.md create mode 100644 examples/pyparsing/arithmetic_expression/code.py create mode 100644 examples/pyparsing/arithmetic_expression/config.toml create mode 100644 examples/pyparsing/arithmetic_expression/setup.py create mode 100644 examples/pyparsing/hello_greeting/code.py create mode 100644 examples/pyparsing/hello_greeting/config.toml create mode 100644 examples/pyparsing/hello_greeting/setup.py create mode 100644 examples/pyparsing/order.json create mode 100644 examples/pyparsing/structured_log_lines/code.py create mode 100644 examples/pyparsing/structured_log_lines/config.toml create mode 100644 examples/pyparsing/structured_log_lines/setup.py 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..540397e --- /dev/null +++ b/examples/pyparsing/arithmetic_expression/code.py @@ -0,0 +1,68 @@ +# --------------------------------------------------------------------- +# 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. + +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 = [""] +for text in cases: + value = expression.parse_string(text, parse_all=True)[0] + rows.append(f"") +rows.append("
ExpressionResult
{text}{value}
") +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." +) diff --git a/examples/pyparsing/arithmetic_expression/config.toml b/examples/pyparsing/arithmetic_expression/config.toml new file mode 100644 index 0000000..ced2a53 --- /dev/null +++ b/examples/pyparsing/arithmetic_expression/config.toml @@ -0,0 +1 @@ +packages = ["pyparsing"] diff --git a/examples/pyparsing/arithmetic_expression/setup.py b/examples/pyparsing/arithmetic_expression/setup.py new file mode 100644 index 0000000..49170f9 --- /dev/null +++ b/examples/pyparsing/arithmetic_expression/setup.py @@ -0,0 +1,23 @@ +"""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"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +import pyparsing as pp +from pyparsing import ( + Word, alphas, nums, one_of, infix_notation, OpAssoc, pyparsing_common, +) diff --git a/examples/pyparsing/hello_greeting/code.py b/examples/pyparsing/hello_greeting/code.py new file mode 100644 index 0000000..75b4f19 --- /dev/null +++ b/examples/pyparsing/hello_greeting/code.py @@ -0,0 +1,48 @@ +""" +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 + +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..73beb63 --- /dev/null +++ b/examples/pyparsing/hello_greeting/setup.py @@ -0,0 +1,46 @@ +""" +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.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) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +# Package imports for this example. +import pyparsing as pp +from pyparsing import Word, alphas 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..269a9d5 --- /dev/null +++ b/examples/pyparsing/structured_log_lines/code.py @@ -0,0 +1,82 @@ +# --------------------------------------------------------------------- +# A real-world flavor: extracting structured fields from log lines. +# --------------------------------------------------------------------- +# +# 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 = [ + "" +] +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"" + f"" + f"" + ) +rows.append("
TimestampLevelFields
{parsed.timestamp}{parsed.level}{fields}
") +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 {len(found)} well-formed log entries:") +for ts, lvl, fs in found: + display(HTML(f"{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..dd58ff8 --- /dev/null +++ b/examples/pyparsing/structured_log_lines/setup.py @@ -0,0 +1,24 @@ +"""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) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +import pyparsing as pp +from pyparsing import ( + Word, Literal, Suppress, Combine, QuotedString, one_of, + nums, alphas, alphanums, Group, OneOrMore, pyparsing_common, +) From 1e0f3764d66e67a8b3fc0b073364cccc0fc93312 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Thu, 11 Jun 2026 13:24:46 +0100 Subject: [PATCH 2/3] Fix imports. --- examples/pyparsing/arithmetic_expression/code.py | 6 ++++++ examples/pyparsing/arithmetic_expression/setup.py | 6 ------ examples/pyparsing/hello_greeting/code.py | 4 ++++ examples/pyparsing/hello_greeting/setup.py | 4 ---- examples/pyparsing/structured_log_lines/code.py | 7 +++++++ examples/pyparsing/structured_log_lines/setup.py | 7 ------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/pyparsing/arithmetic_expression/code.py b/examples/pyparsing/arithmetic_expression/code.py index 540397e..15635fb 100644 --- a/examples/pyparsing/arithmetic_expression/code.py +++ b/examples/pyparsing/arithmetic_expression/code.py @@ -6,6 +6,12 @@ # 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 " diff --git a/examples/pyparsing/arithmetic_expression/setup.py b/examples/pyparsing/arithmetic_expression/setup.py index 49170f9..050a929 100644 --- a/examples/pyparsing/arithmetic_expression/setup.py +++ b/examples/pyparsing/arithmetic_expression/setup.py @@ -15,9 +15,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - - -import pyparsing as pp -from pyparsing import ( - Word, alphas, nums, one_of, infix_notation, OpAssoc, pyparsing_common, -) diff --git a/examples/pyparsing/hello_greeting/code.py b/examples/pyparsing/hello_greeting/code.py index 75b4f19..a524105 100644 --- a/examples/pyparsing/hello_greeting/code.py +++ b/examples/pyparsing/hello_greeting/code.py @@ -7,6 +7,10 @@ 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( diff --git a/examples/pyparsing/hello_greeting/setup.py b/examples/pyparsing/hello_greeting/setup.py index 73beb63..07879f9 100644 --- a/examples/pyparsing/hello_greeting/setup.py +++ b/examples/pyparsing/hello_greeting/setup.py @@ -40,7 +40,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -# Package imports for this example. -import pyparsing as pp -from pyparsing import Word, alphas diff --git a/examples/pyparsing/structured_log_lines/code.py b/examples/pyparsing/structured_log_lines/code.py index 269a9d5..e878f2e 100644 --- a/examples/pyparsing/structured_log_lines/code.py +++ b/examples/pyparsing/structured_log_lines/code.py @@ -1,6 +1,13 @@ # --------------------------------------------------------------------- # 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: # diff --git a/examples/pyparsing/structured_log_lines/setup.py b/examples/pyparsing/structured_log_lines/setup.py index dd58ff8..78a0606 100644 --- a/examples/pyparsing/structured_log_lines/setup.py +++ b/examples/pyparsing/structured_log_lines/setup.py @@ -15,10 +15,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - - -import pyparsing as pp -from pyparsing import ( - Word, Literal, Suppress, Combine, QuotedString, one_of, - nums, alphas, alphanums, Group, OneOrMore, pyparsing_common, -) From 29f6da5d89ab561a7176c726a73a3d51d5b46999 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 26 Jun 2026 10:49:33 +0100 Subject: [PATCH 3/3] Add version_info to IPython shim for PyScript update. --- examples/pyparsing/hello_greeting/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/pyparsing/hello_greeting/setup.py b/examples/pyparsing/hello_greeting/setup.py index 07879f9..a536bd1 100644 --- a/examples/pyparsing/hello_greeting/setup.py +++ b/examples/pyparsing/hello_greeting/setup.py @@ -25,6 +25,7 @@ def display(*args, **kwargs): 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