sexpr.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/usr/bin/env python
  2. # Copyright (c) 2014 Ingo Ruhnke <grumbel@gmail.com>
  3. #
  4. # This software is provided 'as-is', without any express or implied
  5. # warranty. In no event will the authors be held liable for any damages
  6. # arising from the use of this software.
  7. #
  8. # Permission is granted to anyone to use this software for any purpose,
  9. # including commercial applications, and to alter it and redistribute it
  10. # freely, subject to the following restrictions:
  11. #
  12. # 1. The origin of this software must not be misrepresented; you must not
  13. # claim that you wrote the original software. If you use this software
  14. # in a product, an acknowledgment in the product documentation would be
  15. # appreciated but is not required.
  16. # 2. Altered source versions must be plainly marked as such, and must not be
  17. # misrepresented as being the original software.
  18. # 3. This notice may not be removed or altered from any source distribution.
  19. # Taken from Flexlay
  20. def sexpr_read_from_file(filename):
  21. with open(filename, "rt") as fin:
  22. content = fin.read()
  23. return parse(content, filename)
  24. def parse(string, context=None):
  25. parser = SExprParser(string)
  26. try:
  27. return parser.parse()
  28. except Exception as e:
  29. raise SExprParseError(context, parser.line, parser.column, str(e))
  30. def num(s):
  31. try:
  32. return int(s)
  33. except ValueError:
  34. return float(s)
  35. class SExprParseError(Exception):
  36. def __init__(self, context, line, column, message):
  37. super().__init__("%s:%d:%d: error: %s" % (context, line, column, message))
  38. self.context = context
  39. self.line = line
  40. self.column = column
  41. class SExprParser:
  42. def __init__(self, text):
  43. self.text = text
  44. def state_list(self, c):
  45. if c is None:
  46. pass # handled in parse()
  47. elif c == '(':
  48. self.stack.append([])
  49. elif c == ')':
  50. self.stack[-2].append(self.stack.pop())
  51. elif c == "\"":
  52. self.state = self.state_string
  53. self.atom = ""
  54. elif c == ";":
  55. self.state = self.state_comment
  56. self.atom = None
  57. elif c == "#":
  58. self.state = self.state_bool
  59. self.atom = "#"
  60. elif c.isdigit() or c in "-+":
  61. self.state = self.state_number
  62. self.atom = c
  63. elif c.isalpha() or c in "-_":
  64. self.state = self.state_symbol
  65. self.atom = c
  66. elif c.isspace():
  67. pass
  68. else:
  69. raise Exception("unexpected character: '%s'" % c)
  70. def state_comment(self, c):
  71. if c == '\n':
  72. self.state = self.state_list
  73. else:
  74. pass
  75. def state_string(self, c):
  76. if c is None:
  77. raise Exception("string not closed at end of file")
  78. elif c == "\\":
  79. self.index += 1
  80. c = self.text[self.index]
  81. if c == "n":
  82. self.atom += "\n"
  83. elif c == "t":
  84. self.atom += "\t"
  85. elif c == "\\":
  86. self.atom += "\\"
  87. elif c == "\"":
  88. self.atom += "\""
  89. else:
  90. self.atom += "\\"
  91. self.atom += c
  92. elif c == "\"":
  93. self.stack[-1].append(self.atom)
  94. self.atom = None
  95. self.state = self.state_list
  96. else:
  97. self.atom += c
  98. def state_bool(self, c):
  99. if len(self.atom) == 2:
  100. if self.atom == "#f":
  101. self.stack[-1].append(False)
  102. elif self.atom == "#t":
  103. self.stack[-1].append(True)
  104. else:
  105. raise Exception("unknown token: %s" % self.atom)
  106. self.atom = None
  107. self.state = self.state_list
  108. self.index -= 1
  109. elif c is None:
  110. raise Exception("incomplete bool: %s" % self.atom)
  111. else:
  112. self.atom += c
  113. def state_number(self, c):
  114. if c is None or (not c.isdigit() and c != "."):
  115. self.stack[-1].append(num(self.atom))
  116. self.atom = None
  117. self.state = self.state_list
  118. self.index -= 1
  119. else:
  120. self.atom += c
  121. def state_symbol(self, c):
  122. if c is None or c.isspace() or c == '(' or c == ')':
  123. self.stack[-1].append(self.atom)
  124. self.atom = None
  125. self.state = self.state_list
  126. self.index -= 1
  127. else:
  128. self.atom += c
  129. def parse(self):
  130. self.atom = None
  131. self.stack = [[]]
  132. self.state = self.state_list
  133. self.line = 1
  134. self.column = 0
  135. self.index = 0
  136. while self.index < len(self.text):
  137. c = self.text[self.index]
  138. if c == '\n':
  139. self.line += 1
  140. self.column = 0
  141. else:
  142. self.column += 1
  143. self.state(c)
  144. self.index += 1
  145. self.state(None)
  146. if len(self.stack) == 1:
  147. return self.stack[0]
  148. else:
  149. raise Exception("list not closed")
  150. # EOF #