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
valueproperty 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)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
simp_sexp-0.3.1-py3-none-any.whl
(12.4 kB
view details)
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe85fad291079ab5bca181c228282e25bc0df70741c0dee593a4216b87980b5a
|
|
| MD5 |
d2f7c6a094b785dbce23859017ff8802
|
|
| BLAKE2b-256 |
df67e34c5dabe185f0e7f30f349a0a0dd611a916f7f2c0316ee92699165827f8
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ba5a43e8c322e6631748b798c953016bb4f7884bcaf42bcf76e9c6c5a0a1db1
|
|
| MD5 |
ab463e1ba336f200ea558e95ff575792
|
|
| BLAKE2b-256 |
052b93fcb851dfaeeb478c1b4a23a961f248161ea2ceb96c585f4f4f74e1728c
|