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

Release files for simp-sexp 0.3.1

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.1
File Size Uploaded
simp_sexp-0.3.1.tar.gz 17.8 kB Details

Built distribution (wheel)

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

Total release size: 30.2 kB

Release files / simp_sexp-0.3.1.tar.gz

Download URL simp_sexp-0.3.1.tar.gz
Size 17.8 kB
Tags Source
SHA-256 checksum
How to use checksums
fe85fad291079ab5bca181c228282e25bc0df70741c0dee593a4216b87980b5a
BLAKE2b-256 checksum
How to use checksums
df67e34c5dabe185f0e7f30f349a0a0dd611a916f7f2c0316ee92699165827f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.11

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

Download URL simp_sexp-0.3.1-py3-none-any.whl
Size 12.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3ba5a43e8c322e6631748b798c953016bb4f7884bcaf42bcf76e9c6c5a0a1db1
BLAKE2b-256 checksum
How to use checksums
052b93fcb851dfaeeb478c1b4a23a961f248161ea2ceb96c585f4f4f74e1728c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.11

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

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