Skip to main content

simp_sexp

A simple S-expression parser for Python.

Features

  • Simple and lightweight S-expression parser
  • Parse and manipulate S-expressions with an intuitive object-oriented interface
  • Convert between string representations and Python data structures
  • Nested expressions are handled automatically
  • Pretty-printing support for readable output
  • Advanced search capabilities for finding elements within complex S-expressions
  • Support for quoted strings with proper escape handling
  • Automatic type conversion for numbers
  • Convenient value property for extracting single values from labeled expressions

Installation

pip install simp_sexp

Usage

Basic Parsing and Formatting

from simp_sexp import Sexp, prettify_sexp

# Parse a string into an S-expression
expr = Sexp("(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))")
print(expr)
# Output: ['define', ['factorial', 'n'], ['if', ['=', 'n', 0], 1, ['*', 'n', ['factorial', ['-', 'n', 1]]]]]

# Convert an S-expression back to a string
s_expr = expr.to_str()
print(s_expr)
# Output: (define (factorial "n") (if (= "n" 0) 1 (* "n" (factorial (- "n" 1)))))

# Format with pretty printing (default behavior)
pretty = expr.to_str(indent=4)
print(pretty)
"""
Output:
(define
    (factorial "n")
    (if
        (= "n" 0)
        1
        (* "n" (factorial (- "n" 1)))))
"""

# Format without line breaks
compact = expr.to_str(break_inc=0)
print(compact)
# Output: (define (factorial "n") (if (= "n" 0) 1 (* "n" (factorial (- "n" 1)))))

Working with Simple Expressions

from simp_sexp import Sexp

# Simple list
expr1 = Sexp("(a b c)")
print(expr1)  # ['a', 'b', 'c']

# Numbers are automatically converted
expr2 = Sexp("(1 2.5 -3)")
print(expr2)  # [1, 2.5, -3]

# Mixed types
expr3 = Sexp("(add 10 20)")
print(expr3)  # ['add', 10, 20]

# Create S-expressions from Python lists
list_expr = Sexp(['define', ['square', 'x'], ['*', 'x', 'x']])
print(list_expr.to_str(break_inc=0))
# Output: (define (square "x") (* "x" "x"))

# Control quoting behavior
print(list_expr.to_str(quote_strs=False, break_inc=0))
# Output: (define (square x) (* x x))

Extracting Values with the value Property

The value property provides a convenient way to extract single values from S-expressions that contain a single labeled element (a two-element list with a label and value).

from simp_sexp import Sexp

# Extract simple values
version = Sexp("((version 20171130))")
print(version.value)  # 20171130

description = Sexp('((description "A test component"))')
print(description.value)  # A test component

# Combining with search results
config = Sexp("""
(kicad_pcb
  (version 20171130)
  (general
    (thickness 1.6)
    (drawings 5))
  (layers
    (0 F.Cu signal)))
""")

# Find and extract version
print(f"PCB version: {config.search('/kicad_pcb/version').value}")  # PCB version: 20171130

# Find and extract thickness
print(f"Board thickness: {config.search('/kicad_pcb/general/thickness').value}mm")  # Board thickness: 1.6mm

# Extract values from multiple search results
print(f"Number of drawings: {config.search('/kicad_pcb/general/drawings').value}")  # Number of drawings: 5

# Error handling - value property requires specific structure
try:
    invalid = Sexp("(multiple elements here)")
    print(invalid.value)  # This will raise ValueError
except ValueError as e:
    print(f"Error: {e}")  # Error: Sexp isn't in a form that permits extracting a single value.

try:
    empty = Sexp("()")
    print(empty.value)  # This will also raise ValueError
except ValueError as e:
    print(f"Error: {e}")  # Error: Sexp isn't in a form that permits extracting a single value.

Handling Nested Expressions

from simp_sexp import Sexp

# Nested lists
nested = Sexp("(a (b c) (d (e f)))")
print(nested)  # ['a', ['b', 'c'], ['d', ['e', 'f']]]

# Access elements
print(nested[0])  # 'a'
print(nested[1])  # ['b', 'c']
print(nested[2][1][0])  # 'e'

# Modify elements
nested[1][1] = 'modified'
print(nested)  # ['a', ['b', 'modified'], ['d', ['e', 'f']]]

# Add elements
nested[2][1].append('g')
print(nested)  # ['a', ['b', 'modified'], ['d', ['e', 'f', 'g']]]

# Lisp-like function calls
lambda_expr = Sexp("(lambda (x) (+ x 1))")
print(lambda_expr)  # ['lambda', ['x'], ['+', 'x', 1]]

Searching S-expressions

from simp_sexp import Sexp
import re

# Create a complex S-expression
config = Sexp("""
(config
  (version 1.0)
  (settings
    (theme dark)
    (font "Courier New")
    (size 12))
  (keybindings
    (save "Ctrl+S")
    (open "Ctrl+O")
    (preferences
      (toggle "Ctrl+P")
      (help "F1"))))
""")

# Search by key path (relative)
font_results = config.search("font")
print(font_results[0][1])  # ['font', 'Courier New']

# Search by absolute path
version_results = config.search("/config/version")
print(version_results[0][1])  # ['version', 1.0]

# Search using a function
results = config.search(lambda x: len(x) > 2 and x[0] == 'settings')
print(results[0][1])  # ['settings', ['theme', 'dark'], ['font', 'Courier New'], ['size', 12]]

# Search using regex
ctrl_bindings = config.search(re.compile(r'^Ctrl\+'))
print([match[1] for _, match in ctrl_bindings])  # Will show all Ctrl+ keybindings

# Search with contains=True to match any element
theme_results = config.search("dark", contains=True)
print(theme_results[0][1])  # ['theme', 'dark']

# Case-insensitive search
prefs = config.search("PREFERENCES", ignore_case=True)
print(prefs[0][1])  # ['preferences', ['toggle', 'Ctrl+P'], ['help', 'F1']]

Manipulating S-expressions

from simp_sexp import Sexp

# Start with a simple expression
expr = Sexp("(define x 10)")

# Convert to list and modify
expr[2] = 20
print(expr.to_str())  # (define "x" 20)

# Add elements
expr.append(['comment', 'updated value'])
print(expr.to_str(break_inc=0))  # (define "x" 20 (comment "updated value"))

# Create a new expression from scratch
new_expr = Sexp()
new_expr.append('if')
new_expr.append(['>', 'x', 0])
new_expr.append('positive')
new_expr.append('negative')
print(new_expr.to_str(quote_strs=False, break_inc=0))
# Output: (if (> x 0) positive negative)

# Replace parts of an expression
def replace_value(sublist):
    if sublist and sublist[0] == 'x':
        return ['y']
    return sublist

# Find and replace operations in complex expressions
math_expr = Sexp("(+ (* x 3) (/ x 2))")
for path, match in math_expr.search('x'):
    # Create the full path to the parent element
    parent_path = path[:-1]
    index = path[-1]
    
    # Navigate to the parent element
    parent = math_expr
    for i in parent_path:
        parent = parent[i]
        
    # Replace 'x' with 'y'
    parent[index] = 'y'

print(math_expr.to_str(quote_strs=False, break_inc=0))
# Output: (+ (* y 3) (/ y 2))

Working with Files

from simp_sexp import Sexp

# Example of loading an S-expression from a file
def load_config(filename):
    with open(filename, 'r') as f:
        config_str = f.read()
    return Sexp(config_str)

# Example of saving an S-expression to a file
def save_config(config_sexp, filename):
    with open(filename, 'w') as f:
        f.write(config_sexp.to_str(indent=2))

# Usage example (pseudo-code)
# config = load_config("config.sexp")
# config[1][2] = "new_value"  # Modify the config
# save_config(config, "config.sexp")

License

MIT

Metadata

Release files for simp-sexp 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for simp-sexp 0.3.2
File Size Uploaded Provenance Actions
simp_sexp-0.3.2.tar.gz 18.2 kB Without provenance Details for simp_sexp-0.3.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for simp-sexp 0.3.2
File Interpreter ABI Platform Actions
simp_sexp-0.3.2-py3-none-any.whl Python 3 none any Details for simp_sexp-0.3.2-py3-none-any.whl

Total release size: 30.6 kB

Release files / simp_sexp-0.3.2.tar.gz

Download URL simp_sexp-0.3.2.tar.gz
Size 18.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5e8ee29e5b2ae2c77279cf0e76014ffae21a54b57b1315a07009e7d4f8e4120b
BLAKE2b-256 checksum
How to use checksums
41bfa883ff79bc2f656c1d27cda30b5fd059113c76a6ebec117439267b163be9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Release files / simp_sexp-0.3.2-py3-none-any.whl

Download URL simp_sexp-0.3.2-py3-none-any.whl
Size 12.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9529607a9bb876a825a84be5550f8af12adc43abe20eb52469d3ce6b50d54b82
BLAKE2b-256 checksum
How to use checksums
3e2092db24faec55104a03cbfdf7d3aea09ca722cd6ddf773127b87f5f5e3069
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Security for simp-sexp 0.3.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page