--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# S-Expression parsing facilities
+module sexp
+
+intrude import parser_base
+
+# Any S-Expression entity
+abstract class SExpEntity
+
+ # Location in the source document
+ var location: nullable Location
+end
+
+# A full S-Expression, delimited by `(` and `)`
+class SExp
+ super SExpEntity
+
+ # Children of a SExp
+ var content = new Array[SExpEntity]
+
+ redef fun to_s do return "({content.join(" ")})"
+
+ # Returns a pretty-printable version of self
+ #
+ # assert "( ( sp 12.3 ) \"DQString\")".to_sexp.as(SExp).pretty_to_s == "(\n\t(\n\t\tsp\n\t\t12.30\n\t)\n\t\"DQString\"\n)"
+ fun pretty_to_s: String do return recurse_to_s(0)
+
+ private fun recurse_to_s(depth: Int): String do
+ var s = "{"\t" * depth}(\n"
+ for i in content do
+ if i isa SExp then
+ s += i.recurse_to_s(depth + 1)
+ s += "\n"
+ continue
+ end
+ s += "\t" * (depth + 1)
+ s += i.to_s
+ s += "\n"
+ end
+ return s + "{"\t" * depth})"
+ end
+end
+
+# A Double-quoted String
+class SExpDQString
+ super SExpEntity
+
+ # Double-quoted string
+ var content: String
+
+ redef fun to_s do return content
+end
+
+# A float-value
+class SExpFloat
+ super SExpEntity
+
+ # Floating-point value
+ var content: Float
+
+ redef fun to_s do return content.to_precision(2)
+end
+
+# Any Identifier, non string and non-float
+class SExpId
+ super SExpEntity
+
+ # S-Exp compatible identifier
+ var content: String
+
+ redef fun to_s do return content
+end
+
+# An error parsing S-Expressions
+class SExpError
+ super SExpEntity
+
+ # Cause of the error
+ var message: String
+
+ redef fun to_s do return "S-Expression error: {message} at {location or else "unknown location"}"
+end
+
+# S-Expression processor
+class SExpProcessor
+ super StringProcessor
+
+ # Parses an S-Expression entity
+ fun parse_entity: SExpEntity do
+ var srclen = src.length
+ var delims = once ['(', ')', '"']
+ ignore_whitespaces
+ if pos >= srclen then return new SExpError("Empty S-Expression", location = new Location(line, line_offset))
+ var c = src[pos]
+ if pos >= srclen then return new SExpError("Empty S-Expression")
+ if c == '(' then
+ var cnt = new SExp
+ var loc = new Location(line, line_offset)
+ pos += 1
+ while pos < srclen and src[pos] != ')' do
+ var p = parse_entity
+ if p isa SExpError then break
+ cnt.content.add p
+ ignore_whitespaces
+ end
+ if pos < srclen and src[pos] == ')' then
+ pos += 1
+ return cnt
+ else
+ return new SExpError("Incomplete S-Expression", location = loc)
+ end
+ else if c == '"' then
+ var stdq = pos
+ pos += 1
+ ignore_until("\"")
+ pos += 1
+ var endq = pos
+ return new SExpDQString(src.substring(stdq, endq - stdq))
+ else
+ var stid = pos
+ while pos < srclen and not c.is_whitespace and not delims.has(c) do
+ c = src[pos]
+ pos += 1
+ end
+ if delims.has(c) or c.is_whitespace then pos -= 1
+ if pos >= srclen then return new SExpError("Invalid S-Expression")
+ var endid = pos
+ var cntstr = src.substring(stid, endid - stid)
+ var cnt: SExpEntity
+ if cntstr.is_numeric then
+ cnt = new SExpFloat(cntstr.to_f)
+ else
+ cnt = new SExpId(cntstr)
+ end
+ return cnt
+ end
+ end
+end
+
+redef class Text
+
+ # Tries to parse `self` as an S-Expression
+ fun to_sexp: SExpEntity do return (new SExpProcessor(self.to_s)).parse_entity
+end