Coverage for src/pycse/lisp.py: 94.83%
116 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-23 16:23 -0400
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-23 16:23 -0400
1"""Library to convert Python data structures to lisp data structures.
3This module provides a lightweight wrapper and helper classes to convert
4Python objects to Lisp representations.
6Transformations:
7 string -> "string"
8 int, float -> number
9 [1, 2, 3] -> (1 2 3)
10 (1, 2, 3) -> (1 2 3)
11 {"a": 6} -> (:a 6) p-list
13Helper classes:
14 Symbol("lambda") -> lambda
15 Cons(a, b) -> (a . b)
16 Alist(["A" 2 "B" 5]) -> (("A" . 2) ("B" 5))
17 Quote("symbol") -> 'symbol
18 Quote([1, 2, 3]) -> '(1 2 3)
19 SharpQuote("symbol") -> #'symbol
20 Vector([1, 2, 3]) -> [1 2 3]
22Usage:
23 from pycse.lisp import L, Symbol, Quote
25 # Using the L wrapper
26 str(L([1, 2, 3])) # "(1 2 3)"
27 str(L({"a": 6})) # "(:a 6)"
28 print(L([1, 2, 3])) # "(1 2 3)"
30 # Using helper classes
31 str(Symbol("lambda")) # "lambda"
32 str(Quote("symbol")) # "'symbol"
34 # Concatenating with + operator
35 Symbol("defun") + Symbol("square") # "defun square"
36 L([1, 2]) + Symbol("x") # "(1 2) x"
38http://kitchingroup.cheme.cmu.edu/blog/2015/05/16/Python-data-structures-to-lisp/
39"""
41import numpy as np
44def to_lisp(obj):
45 """Convert a Python object to a Lisp representation string.
47 Args:
48 obj: Python object to convert (str, int, float, list, tuple, dict, etc.)
50 Returns:
51 str: Lisp representation of the object
53 Raises:
54 TypeError: If object cannot be converted to Lisp
55 """
56 # Handle basic types
57 if isinstance(obj, str):
58 return f'"{obj}"'
59 elif isinstance(obj, bool):
60 # Handle bool before int (bool is subclass of int)
61 return "t" if obj else "nil"
62 elif isinstance(obj, (int, float, np.integer, np.floating)):
63 return str(obj)
65 # Handle collections
66 elif isinstance(obj, (list, tuple, np.ndarray)):
67 if len(obj) == 0:
68 return "()"
69 elements = [to_lisp(item) for item in obj]
70 return "(" + " ".join(elements) + ")"
72 # Handle dictionaries as property lists
73 elif isinstance(obj, dict):
74 if len(obj) == 0:
75 return "()"
76 items = [f":{key} {to_lisp(value)}" for key, value in obj.items()]
77 return "(" + " ".join(items) + ")"
79 # Handle None
80 elif obj is None:
81 return "nil"
83 # Handle custom Lisp classes (L, Symbol, Quote, etc.) - use their __str__ method
84 elif hasattr(obj, "__module__") and obj.__module__ == "pycse.lisp":
85 return str(obj)
87 else:
88 raise TypeError(f"Cannot convert {type(obj).__name__} to Lisp: {obj}")
91class L:
92 """Lightweight wrapper for converting Python objects to Lisp representation.
94 Usage:
95 str(L([1, 2, 3])) # "(1 2 3)"
96 str(L({"a": 6})) # "(:a 6)"
97 print(L([1, 2, 3])) # "(1 2 3)"
98 L([1, 2]) + Symbol("x") # "(1 2) x"
99 """
101 __slots__ = ("_obj",)
103 def __init__(self, obj):
104 """Initialize wrapper with a Python object."""
105 self._obj = obj
107 def __str__(self):
108 """Return Lisp representation as string."""
109 return to_lisp(self._obj)
111 def __repr__(self):
112 """Return readable representation."""
113 return f"L({self._obj!r})"
115 def __add__(self, other):
116 """Concatenate Lisp representations with space separator."""
117 return f"{str(self)} {str(other)}"
120# Helper classes for generating Lisp code
123class Symbol:
124 """A Lisp symbol.
126 Symbols are used to print strings without double quotes.
128 Usage:
129 str(Symbol("lambda")) # "lambda"
130 Symbol("defun") + Symbol("x") # "defun x"
131 """
133 def __init__(self, sym):
134 """Initialize a Symbol."""
135 if not isinstance(sym, str):
136 raise TypeError(f"Symbol must be a string, not {type(sym).__name__}")
137 self.sym = sym
139 def __str__(self):
140 """Return Lisp representation of symbol."""
141 return self.sym
143 def __repr__(self):
144 """Return readable representation."""
145 return f"Symbol({self.sym!r})"
147 def __add__(self, other):
148 """Concatenate Lisp representations with space separator."""
149 return f"{str(self)} {str(other)}"
152class Quote:
153 """Quote a symbol or form.
155 Usage:
156 str(Quote("symbol")) # "'symbol"
157 str(Quote([1, 2, 3])) # "'(1 2 3)"
158 """
160 def __init__(self, form):
161 """Initialize a Quote."""
162 self.form = form
164 def __str__(self):
165 """Return Lisp representation with quote prefix."""
166 if isinstance(self.form, str):
167 return f"'{self.form}"
168 else:
169 return f"'{to_lisp(self.form)}"
171 def __repr__(self):
172 """Return readable representation."""
173 return f"Quote({self.form!r})"
175 def __add__(self, other):
176 """Concatenate Lisp representations with space separator."""
177 return f"{str(self)} {str(other)}"
180class SharpQuote:
181 """Function quote (#') a symbol or form.
183 Usage:
184 str(SharpQuote("lambda")) # "#'lambda"
185 """
187 def __init__(self, form):
188 """Initialize a SharpQuote."""
189 self.form = form
191 def __str__(self):
192 """Return Lisp representation with #' prefix."""
193 if isinstance(self.form, str):
194 return f"#'{self.form}"
195 else:
196 return f"#'{to_lisp(self.form)}"
198 def __repr__(self):
199 """Return readable representation."""
200 return f"SharpQuote({self.form!r})"
202 def __add__(self, other):
203 """Concatenate Lisp representations with space separator."""
204 return f"{str(self)} {str(other)}"
207class Cons:
208 """A cons cell (dotted pair).
210 Usage:
211 str(Cons("a", "b")) # "(a . b)"
212 str(Cons(1, 2)) # "(1 . 2)"
213 """
215 def __init__(self, car, cdr):
216 """Initialize a Cons cell with car and cdr."""
217 self.car = car
218 self.cdr = cdr
220 def __str__(self):
221 """Return Lisp representation as dotted pair."""
222 return f"({to_lisp(self.car)} . {to_lisp(self.cdr)})"
224 def __repr__(self):
225 """Return readable representation."""
226 return f"Cons({self.car!r}, {self.cdr!r})"
228 def __add__(self, other):
229 """Concatenate Lisp representations with space separator."""
230 return f"{str(self)} {str(other)}"
233class Alist:
234 """A Lisp association list.
236 Usage:
237 str(Alist(["A", 2, "B", 5])) # '(("A" . 2) ("B" . 5))'
238 """
240 def __init__(self, lst):
241 """Initialize an alist from a flat list [key1, val1, key2, val2, ...]."""
242 if len(lst) % 2 != 0:
243 raise ValueError("Alist requires an even number of elements")
244 self.list = lst
246 def __str__(self):
247 """Return Lisp representation as association list."""
248 keys = self.list[0::2]
249 vals = self.list[1::2]
250 pairs = [Cons(key, val) for key, val in zip(keys, vals)]
251 return to_lisp(pairs)
253 def __repr__(self):
254 """Return readable representation."""
255 return f"Alist({self.list!r})"
257 def __add__(self, other):
258 """Concatenate Lisp representations with space separator."""
259 return f"{str(self)} {str(other)}"
262class Vector:
263 """A Lisp vector.
265 Usage:
266 str(Vector([1, 2, 3])) # "[1 2 3]"
267 """
269 def __init__(self, lst):
270 """Initialize a vector from a list."""
271 self.list = lst
273 def __str__(self):
274 """Return Lisp representation as vector."""
275 if len(self.list) == 0:
276 return "[]"
277 elements = [to_lisp(x) for x in self.list]
278 return "[" + " ".join(elements) + "]"
280 def __repr__(self):
281 """Return readable representation."""
282 return f"Vector({self.list!r})"
284 def __add__(self, other):
285 """Concatenate Lisp representations with space separator."""
286 return f"{str(self)} {str(other)}"
289class Backquote:
290 """A backquoted form.
292 Usage:
293 str(Backquote([1, 2, 3])) # "`(1 2 3)"
294 """
296 def __init__(self, form):
297 """Initialize a backquote."""
298 self.form = form
300 def __str__(self):
301 """Return Lisp representation with backquote prefix."""
302 return f"`{to_lisp(self.form)}"
304 def __repr__(self):
305 """Return readable representation."""
306 return f"Backquote({self.form!r})"
308 def __add__(self, other):
309 """Concatenate Lisp representations with space separator."""
310 return f"{str(self)} {str(other)}"
313class Comma:
314 """The comma (unquote) operator.
316 Usage:
317 str(Comma(Symbol("x"))) # ",x"
318 """
320 def __init__(self, form):
321 """Initialize a comma operator."""
322 self.form = form
324 def __str__(self):
325 """Return Lisp representation with comma prefix."""
326 return f",{to_lisp(self.form)}"
328 def __repr__(self):
329 """Return readable representation."""
330 return f"Comma({self.form!r})"
332 def __add__(self, other):
333 """Concatenate Lisp representations with space separator."""
334 return f"{str(self)} {str(other)}"
337class Splice:
338 """The splice (,@) operator.
340 Usage:
341 str(Splice([1, 2, 3])) # ",@(1 2 3)"
342 """
344 def __init__(self, form):
345 """Initialize a splice operator."""
346 self.form = form
348 def __str__(self):
349 """Return Lisp representation with ,@ prefix."""
350 return f",@{to_lisp(self.form)}"
352 def __repr__(self):
353 """Return readable representation."""
354 return f"Splice({self.form!r})"
356 def __add__(self, other):
357 """Concatenate Lisp representations with space separator."""
358 return f"{str(self)} {str(other)}"
361class Comment:
362 """A comment in Lisp.
364 Usage:
365 str(Comment("This is a comment")) # "; This is a comment"
366 """
368 def __init__(self, text):
369 """Initialize a comment with text."""
370 self.text = text
372 def __str__(self):
373 """Return Lisp representation as comment."""
374 return f"; {to_lisp(self.text) if not isinstance(self.text, str) else self.text}"
376 def __repr__(self):
377 """Return readable representation."""
378 return f"Comment({self.text!r})"
380 def __add__(self, other):
381 """Concatenate Lisp representations with space separator."""
382 return f"{str(self)} {str(other)}"