Skip to content

Commit bc8f15b

Browse files
committed
Move code from pyorg package
1 parent c9bbcf2 commit bc8f15b

6 files changed

Lines changed: 700 additions & 0 deletions

File tree

emacs/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66
__author__ = 'Jared Lumpe'
77
__email__ = 'mjlumpe@gmail.com'
88
__version__ = '0.1'
9+
10+
11+
from .emacs import Emacs, EmacsException

emacs/elisp/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""Build and print Emacs Lisp abstract syntax trees in Python."""
2+
3+
from .ast import *
4+
from .dsl import E

emacs/elisp/ast.py

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
"""Base classes for Emacs Lisp abstract syntax trees."""
2+
3+
import re
4+
from functools import singledispatch
5+
6+
7+
__all__ = ['ElispAstNode', 'Form', 'Literal', 'Symbol', 'Cons', 'List', 'Quote',
8+
'Raw', 'to_elisp', 'make_list', 'symbols', 'quote', 'cons']
9+
10+
11+
class ElispAstNode:
12+
"""Abstract base class for Elisp AST nodes."""
13+
14+
def __repr__(self):
15+
return '<el %s>' % self
16+
17+
18+
class Form(ElispAstNode):
19+
"""Pretty much everything is a form, right?"""
20+
21+
22+
class Literal(Form):
23+
"""Basic self-evaluating forms like strings, numbers, etc.
24+
25+
Attributes
26+
----------
27+
pyvalue
28+
The Python value of the literal.
29+
"""
30+
31+
PY_TYPES = (str, int, float)
32+
33+
def __init__(self, pyvalue):
34+
if not isinstance(pyvalue, self.PY_TYPES):
35+
raise TypeError('Instances of %s not allowed as Elisp literals' % type(pyvalue))
36+
self.pyvalue = pyvalue
37+
38+
def __eq__(self, other):
39+
return isinstance(other, Literal) \
40+
and type(other.pyvalue) is type(self.pyvalue) \
41+
and other.pyvalue == self.pyvalue
42+
43+
def __str__(self):
44+
if isinstance(self.pyvalue, str):
45+
return print_elisp_string(self.pyvalue)
46+
else:
47+
return str(self.pyvalue)
48+
49+
50+
class Symbol(Form):
51+
"""Elisp symbol.
52+
53+
Attributes
54+
----------
55+
name : str
56+
"""
57+
58+
def __init__(self, name):
59+
assert isinstance(name, str) and name
60+
self.name = name
61+
62+
def __eq__(self, other):
63+
return isinstance(other, Symbol) and other.name == self.name
64+
65+
@property
66+
def isconst(self):
67+
return self.name.startswith(':') or self.name in ('nil', 't')
68+
69+
def __call__(self, *args, **kwargs):
70+
"""Produce a function call node from this symbol."""
71+
items = [self, *map(to_elisp, args)]
72+
for key, value in kwargs.items():
73+
items.extend([Symbol(':' + key), to_elisp(value)])
74+
return List(items)
75+
76+
def __str__(self):
77+
return self.name
78+
79+
80+
81+
class Cons(Form):
82+
"""A cons cell.
83+
84+
Attributes
85+
----------
86+
car : .ElispAstNode
87+
cdr : .ElispAstNode
88+
"""
89+
90+
def __init__(self, car, cdr):
91+
self.car = to_elisp(car)
92+
self.cdr = to_elisp(cdr)
93+
94+
def __eq__(self, other):
95+
return isinstance(other, Cons) \
96+
and other.car == self.car \
97+
and other.cdr == self.cdr
98+
99+
def __str__(self):
100+
return '(%s . %s)' % (self.car, self.cdr)
101+
102+
103+
class List(Form):
104+
"""A list...
105+
106+
Attributes
107+
----------
108+
items : tuple of .ElispAstNode
109+
Items in the list
110+
"""
111+
112+
def __init__(self, items):
113+
self.items = tuple(map(to_elisp, items))
114+
115+
def __eq__(self, other):
116+
return isinstance(other, List) and other.items == self.items
117+
118+
def __str__(self):
119+
return '(%s)' % ' '.join(map(str, self.items))
120+
121+
122+
class Quote(Form):
123+
"""A quoted Elisp form.
124+
125+
Attributes
126+
----------
127+
form : .ElispAstNode
128+
The quoted Elisp form.
129+
"""
130+
131+
def __init__(self, form):
132+
self.form = to_elisp(form)
133+
134+
def __eq__(self, other):
135+
return isinstance(other, Quote) and other.form == self.form
136+
137+
def __str__(self):
138+
return "'%s" % self.form
139+
140+
141+
class Raw(ElispAstNode):
142+
"""Just raw Elisp code to be pasted in at this point.
143+
144+
Attributes
145+
----------
146+
src : str
147+
Raw Elisp source code.
148+
"""
149+
150+
def __init__(self, src):
151+
self.src = src
152+
153+
def __eq__(self, other):
154+
return isinstance(other, Raw) and other.src == self.src
155+
156+
def __str__(self):
157+
return self.src
158+
159+
160+
@singledispatch
161+
def to_elisp(value):
162+
"""Convert a Python value to an Elisp AST node.
163+
164+
Parameters
165+
----------
166+
value
167+
Python value to convert.
168+
169+
Returns
170+
-------
171+
.ElispAstNode
172+
"""
173+
if isinstance(value, ElispAstNode):
174+
return value
175+
raise TypeError('Cannot convert object of type %s to Elisp' % type(value).__name__)
176+
177+
178+
@to_elisp.register(bool)
179+
@to_elisp.register(type(None))
180+
def _bool_to_elisp(value):
181+
return Symbol('t') if value else Symbol('nil')
182+
183+
184+
# Register literal types
185+
for type_ in Literal.PY_TYPES:
186+
to_elisp.register(type_, Literal)
187+
188+
189+
# Convert Python lists to quoted Emacs lists
190+
@to_elisp.register(list)
191+
def _py_list_to_el_list(pylist):
192+
return Quote(make_list(pylist))
193+
194+
195+
@to_elisp.register(tuple)
196+
def make_list(items):
197+
"""Make an Elisp list from a Python sequence, first converting its elements to Elisp.
198+
199+
Parameters
200+
----------
201+
items : Iterable of objects to convert to list.
202+
203+
Returns
204+
-------
205+
.List
206+
"""
207+
return List(map(to_elisp, items))
208+
209+
210+
def quote(value):
211+
"""Quote value, converting Python strings to symbols.
212+
213+
Parameters
214+
----------
215+
value : Elisp value to quote.
216+
217+
Returns
218+
-------
219+
.Quote
220+
"""
221+
if isinstance(value, str):
222+
form = Symbol(value)
223+
else:
224+
form = to_elisp(value)
225+
226+
return Quote(form)
227+
228+
229+
def cons(car, cds):
230+
"""Create a Cons cell, converting arguments.
231+
232+
Returns
233+
-------
234+
.Cons
235+
"""
236+
return Cons(to_elisp(car), to_elisp(cds))
237+
238+
239+
def symbols(*names):
240+
"""Create a list of symbols.
241+
242+
Returns
243+
-------
244+
.List
245+
"""
246+
247+
s = []
248+
249+
for name in names:
250+
if isinstance(name, str):
251+
s.append(Symbol(name))
252+
elif isinstance(name, Symbol):
253+
s.append(name)
254+
else:
255+
raise TypeError('Expected str or Symbol, got %s' % type(name).__name__)
256+
257+
return List(s)
258+
259+
260+
261+
def print_elisp_string(string):
262+
"""Print string to Elisp, properly escaping it (maybe).
263+
264+
Parameters
265+
----------
266+
string : str
267+
268+
Returns
269+
-------
270+
str
271+
"""
272+
return '"%s"' % re.sub(r'([\\\"])', r'\\\1', string)
273+
274+

emacs/elisp/dsl.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""A DSL for writing Elisp in Python.
2+
3+
God help us all.
4+
"""
5+
6+
from .ast import *
7+
8+
9+
class ElispSingleton:
10+
"""Singleton object which implements the Elisp DSL.
11+
12+
13+
"""
14+
15+
__instance = None
16+
17+
def __new__(cls):
18+
if cls.__instance is None:
19+
cls.__instance = object.__new__(cls)
20+
return cls.__instance
21+
22+
def __getitem__(self, name):
23+
"""Indexing with string gets a Symbol."""
24+
return Symbol(name)
25+
26+
def _convert_symbol_name(self, name):
27+
"""Convert symbol name from Python style to Elisp style."""
28+
return name.replace('_', '-')
29+
30+
def __getattr__(self, name):
31+
"""Attribute access with lower-case name gets a symbol."""
32+
if name[0] == name[0].lower() and not name.startswith('__'):
33+
return Symbol(self._convert_symbol_name(name))
34+
35+
return object.__getattribute__(self, name)
36+
37+
def __call__(self, value):
38+
"""Calling as function converts value."""
39+
return to_elisp(value)
40+
41+
Q = staticmethod(quote)
42+
C = staticmethod(cons)
43+
S = staticmethod(symbols)
44+
R = staticmethod(Raw)
45+
46+
47+
E = ElispSingleton()

0 commit comments

Comments
 (0)