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

1"""Library to convert Python data structures to lisp data structures. 

2 

3This module provides a lightweight wrapper and helper classes to convert 

4Python objects to Lisp representations. 

5 

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 

12 

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] 

21 

22Usage: 

23 from pycse.lisp import L, Symbol, Quote 

24 

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)" 

29 

30 # Using helper classes 

31 str(Symbol("lambda")) # "lambda" 

32 str(Quote("symbol")) # "'symbol" 

33 

34 # Concatenating with + operator 

35 Symbol("defun") + Symbol("square") # "defun square" 

36 L([1, 2]) + Symbol("x") # "(1 2) x" 

37 

38http://kitchingroup.cheme.cmu.edu/blog/2015/05/16/Python-data-structures-to-lisp/ 

39""" 

40 

41import numpy as np 

42 

43 

44def to_lisp(obj): 

45 """Convert a Python object to a Lisp representation string. 

46 

47 Args: 

48 obj: Python object to convert (str, int, float, list, tuple, dict, etc.) 

49 

50 Returns: 

51 str: Lisp representation of the object 

52 

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) 

64 

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) + ")" 

71 

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) + ")" 

78 

79 # Handle None 

80 elif obj is None: 

81 return "nil" 

82 

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) 

86 

87 else: 

88 raise TypeError(f"Cannot convert {type(obj).__name__} to Lisp: {obj}") 

89 

90 

91class L: 

92 """Lightweight wrapper for converting Python objects to Lisp representation. 

93 

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 """ 

100 

101 __slots__ = ("_obj",) 

102 

103 def __init__(self, obj): 

104 """Initialize wrapper with a Python object.""" 

105 self._obj = obj 

106 

107 def __str__(self): 

108 """Return Lisp representation as string.""" 

109 return to_lisp(self._obj) 

110 

111 def __repr__(self): 

112 """Return readable representation.""" 

113 return f"L({self._obj!r})" 

114 

115 def __add__(self, other): 

116 """Concatenate Lisp representations with space separator.""" 

117 return f"{str(self)} {str(other)}" 

118 

119 

120# Helper classes for generating Lisp code 

121 

122 

123class Symbol: 

124 """A Lisp symbol. 

125 

126 Symbols are used to print strings without double quotes. 

127 

128 Usage: 

129 str(Symbol("lambda")) # "lambda" 

130 Symbol("defun") + Symbol("x") # "defun x" 

131 """ 

132 

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 

138 

139 def __str__(self): 

140 """Return Lisp representation of symbol.""" 

141 return self.sym 

142 

143 def __repr__(self): 

144 """Return readable representation.""" 

145 return f"Symbol({self.sym!r})" 

146 

147 def __add__(self, other): 

148 """Concatenate Lisp representations with space separator.""" 

149 return f"{str(self)} {str(other)}" 

150 

151 

152class Quote: 

153 """Quote a symbol or form. 

154 

155 Usage: 

156 str(Quote("symbol")) # "'symbol" 

157 str(Quote([1, 2, 3])) # "'(1 2 3)" 

158 """ 

159 

160 def __init__(self, form): 

161 """Initialize a Quote.""" 

162 self.form = form 

163 

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)}" 

170 

171 def __repr__(self): 

172 """Return readable representation.""" 

173 return f"Quote({self.form!r})" 

174 

175 def __add__(self, other): 

176 """Concatenate Lisp representations with space separator.""" 

177 return f"{str(self)} {str(other)}" 

178 

179 

180class SharpQuote: 

181 """Function quote (#') a symbol or form. 

182 

183 Usage: 

184 str(SharpQuote("lambda")) # "#'lambda" 

185 """ 

186 

187 def __init__(self, form): 

188 """Initialize a SharpQuote.""" 

189 self.form = form 

190 

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)}" 

197 

198 def __repr__(self): 

199 """Return readable representation.""" 

200 return f"SharpQuote({self.form!r})" 

201 

202 def __add__(self, other): 

203 """Concatenate Lisp representations with space separator.""" 

204 return f"{str(self)} {str(other)}" 

205 

206 

207class Cons: 

208 """A cons cell (dotted pair). 

209 

210 Usage: 

211 str(Cons("a", "b")) # "(a . b)" 

212 str(Cons(1, 2)) # "(1 . 2)" 

213 """ 

214 

215 def __init__(self, car, cdr): 

216 """Initialize a Cons cell with car and cdr.""" 

217 self.car = car 

218 self.cdr = cdr 

219 

220 def __str__(self): 

221 """Return Lisp representation as dotted pair.""" 

222 return f"({to_lisp(self.car)} . {to_lisp(self.cdr)})" 

223 

224 def __repr__(self): 

225 """Return readable representation.""" 

226 return f"Cons({self.car!r}, {self.cdr!r})" 

227 

228 def __add__(self, other): 

229 """Concatenate Lisp representations with space separator.""" 

230 return f"{str(self)} {str(other)}" 

231 

232 

233class Alist: 

234 """A Lisp association list. 

235 

236 Usage: 

237 str(Alist(["A", 2, "B", 5])) # '(("A" . 2) ("B" . 5))' 

238 """ 

239 

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 

245 

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) 

252 

253 def __repr__(self): 

254 """Return readable representation.""" 

255 return f"Alist({self.list!r})" 

256 

257 def __add__(self, other): 

258 """Concatenate Lisp representations with space separator.""" 

259 return f"{str(self)} {str(other)}" 

260 

261 

262class Vector: 

263 """A Lisp vector. 

264 

265 Usage: 

266 str(Vector([1, 2, 3])) # "[1 2 3]" 

267 """ 

268 

269 def __init__(self, lst): 

270 """Initialize a vector from a list.""" 

271 self.list = lst 

272 

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) + "]" 

279 

280 def __repr__(self): 

281 """Return readable representation.""" 

282 return f"Vector({self.list!r})" 

283 

284 def __add__(self, other): 

285 """Concatenate Lisp representations with space separator.""" 

286 return f"{str(self)} {str(other)}" 

287 

288 

289class Backquote: 

290 """A backquoted form. 

291 

292 Usage: 

293 str(Backquote([1, 2, 3])) # "`(1 2 3)" 

294 """ 

295 

296 def __init__(self, form): 

297 """Initialize a backquote.""" 

298 self.form = form 

299 

300 def __str__(self): 

301 """Return Lisp representation with backquote prefix.""" 

302 return f"`{to_lisp(self.form)}" 

303 

304 def __repr__(self): 

305 """Return readable representation.""" 

306 return f"Backquote({self.form!r})" 

307 

308 def __add__(self, other): 

309 """Concatenate Lisp representations with space separator.""" 

310 return f"{str(self)} {str(other)}" 

311 

312 

313class Comma: 

314 """The comma (unquote) operator. 

315 

316 Usage: 

317 str(Comma(Symbol("x"))) # ",x" 

318 """ 

319 

320 def __init__(self, form): 

321 """Initialize a comma operator.""" 

322 self.form = form 

323 

324 def __str__(self): 

325 """Return Lisp representation with comma prefix.""" 

326 return f",{to_lisp(self.form)}" 

327 

328 def __repr__(self): 

329 """Return readable representation.""" 

330 return f"Comma({self.form!r})" 

331 

332 def __add__(self, other): 

333 """Concatenate Lisp representations with space separator.""" 

334 return f"{str(self)} {str(other)}" 

335 

336 

337class Splice: 

338 """The splice (,@) operator. 

339 

340 Usage: 

341 str(Splice([1, 2, 3])) # ",@(1 2 3)" 

342 """ 

343 

344 def __init__(self, form): 

345 """Initialize a splice operator.""" 

346 self.form = form 

347 

348 def __str__(self): 

349 """Return Lisp representation with ,@ prefix.""" 

350 return f",@{to_lisp(self.form)}" 

351 

352 def __repr__(self): 

353 """Return readable representation.""" 

354 return f"Splice({self.form!r})" 

355 

356 def __add__(self, other): 

357 """Concatenate Lisp representations with space separator.""" 

358 return f"{str(self)} {str(other)}" 

359 

360 

361class Comment: 

362 """A comment in Lisp. 

363 

364 Usage: 

365 str(Comment("This is a comment")) # "; This is a comment" 

366 """ 

367 

368 def __init__(self, text): 

369 """Initialize a comment with text.""" 

370 self.text = text 

371 

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}" 

375 

376 def __repr__(self): 

377 """Return readable representation.""" 

378 return f"Comment({self.text!r})" 

379 

380 def __add__(self, other): 

381 """Concatenate Lisp representations with space separator.""" 

382 return f"{str(self)} {str(other)}"