Skip to main content

A simple S-expression parser

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simp_sexp-0.3.1.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simp_sexp-0.3.1-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file simp_sexp-0.3.1.tar.gz.

File metadata

  • Download URL: simp_sexp-0.3.1.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.11

File hashes

Hashes for simp_sexp-0.3.1.tar.gz
Algorithm Hash digest
SHA256 fe85fad291079ab5bca181c228282e25bc0df70741c0dee593a4216b87980b5a
MD5 d2f7c6a094b785dbce23859017ff8802
BLAKE2b-256 df67e34c5dabe185f0e7f30f349a0a0dd611a916f7f2c0316ee92699165827f8

See more details on using hashes here.

File details

Details for the file simp_sexp-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: simp_sexp-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 12.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.11

File hashes

Hashes for simp_sexp-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3ba5a43e8c322e6631748b798c953016bb4f7884bcaf42bcf76e9c6c5a0a1db1
MD5 ab463e1ba336f200ea558e95ff575792
BLAKE2b-256 052b93fcb851dfaeeb478c1b4a23a961f248161ea2ceb96c585f4f4f74e1728c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page