3 ## sexpr.py - by Yusuke Shinyama
7 ## from http://www.unixuser.org/~euske/python/index.html:
8 ## The following files are in public domain except where otherwise noted. THESE FILES COME WITH ABSOLUTELY NO WARRANTY.
10 from abstfilter import AbstractFeeder, AbstractFilter, AbstractConsumer
15 class SExprReader(AbstractFilter):
18 reader = SExprReader(consumer)
19 reader.feed("(this is (sexpr))")
31 def __init__(self, next_filter,
32 comment_begin=COMMENT_BEGIN,
33 comment_end=COMMENT_END,
35 paren_begin=PAREN_BEGIN,
39 AbstractFilter.__init__(self, next_filter)
40 self.comment_begin = comment_begin
41 self.comment_end = comment_end
42 self.separator = separator
43 self.paren_begin = paren_begin
44 self.paren_end = paren_end
47 self.special = comment_begin + separator + paren_begin + paren_end + quote + escape
51 # SExprReader ignores any error and
52 # try to continue as long as possible.
53 # if you want to throw exception however,
54 # please modify these methods.
56 # called if redundant parantheses are found.
57 def illegal_close_paren(self, i):
58 print "Ignore a close parenthesis: %d" % i
60 # called if it reaches the end-of-file while the stack is not empty.
61 def premature_eof(self, i, x):
62 print "Premature end of file: %d parens left, partial=%s" % (i, x)
65 # reset the internal states.
67 self.incomment = False # if within a comment.
68 self.inquote = False # if within a quote.
69 self.inescape = False # if within a escape.
70 self.sym = '' # partially constructed symbol.
71 # NOTICE: None != nil (an empty list)
72 self.build = None # partially constructed list.
73 self.build_stack = [] # to store a chain of partial lists.
77 def feed(self, tokens):
78 for (i,c) in enumerate(tokens):
80 # within a comment - skip
81 self.incomment = (c not in self.comment_end)
82 elif self.inescape or (c not in self.special):
83 # add to the current working symbol
86 elif c in self.escape:
89 elif self.inquote and (c not in self.quote):
92 # special character (blanks, parentheses, or comment)
94 # close the current symbol
95 if self.build == None:
96 self.feed_next(self.sym)
98 self.build.append(self.sym)
100 if c in self.comment_begin:
102 self.incomment = True
103 elif c in self.quote:
105 self.inquote = not self.inquote
106 elif c in self.paren_begin:
107 # beginning a new list.
108 self.build_stack.append(self.build)
110 if self.build == None:
111 # begin from a scratch.
114 # begin from the end of the current list.
115 self.build.append(empty)
117 elif c in self.paren_end:
118 # terminating the current list
119 if self.build == None:
120 # there must be a working list.
121 self.illegal_close_paren(i)
123 if len(self.build_stack) == 1:
124 # current working list is the last one in the stack.
125 self.feed_next(self.build)
126 self.build = self.build_stack.pop()
131 # a working list should not exist.
132 if self.build != None:
133 # error - still try to construct a partial structure.
135 self.build.append(self.sym)
137 if len(self.build_stack) == 1:
140 x = self.build_stack[1]
142 self.build_stack = []
143 self.premature_eof(len(self.build_stack), x)
145 # flush the current working symbol.
146 self.feed_next(self.sym)
152 AbstractFilter.close(self)
159 class SExprIllegalClosingParenError(ValueError):
160 """It throws an exception with an ill-structured input."""
162 class SExprPrematureEOFError(ValueError):
164 class StrictSExprReader(SExprReader):
165 def illegal_close_paren(self, i):
166 raise SExprIllegalClosingParenError(i)
167 def premature_eof(self, i, x):
168 raise SExprPrematureEOFError(i, x)
173 class _SExprStrConverter(AbstractConsumer):
176 _SExprStrConverter.results.append(s)
178 _str_converter = SExprReader(_SExprStrConverter())
179 _str_converter_strict = StrictSExprReader(_SExprStrConverter())
182 """parse a string as a sexpr."""
183 _SExprStrConverter.results = []
184 _str_converter.reset().feed(s).terminate()
185 return _SExprStrConverter.results
186 def str2sexpr_strict(s):
187 """parse a string as a sexpr."""
188 _SExprStrConverter.results = []
189 _str_converter_strict.reset().feed(s).terminate()
190 return _SExprStrConverter.results
196 """convert a sexpr into Lisp-like representation."""
197 if not isinstance(e, list):
199 return "("+" ".join(map(sexpr2str, e))+")"
204 assert str2sexpr("(this ;comment\n is (a test (sentences) (des()) (yo)))") == \
205 [["this", "is", ["a", "test", ["sentences"], ["des", []], ["yo"]]]]
206 assert str2sexpr('''(paren\\(\\)theses_in\\#symbol "space in \nsymbol"
207 this\\ way\\ also. "escape is \\"better than\\" quote")''') == \
208 [['paren()theses_in#symbol', 'space in \nsymbol', 'this way also.', 'escape is "better than" quote']]
209 str2sexpr("(this (is (a (parial (sentence")
214 if __name__ == "__main__":