491abdccf19889e5e41d6a2a5087042ae4be0212
[nit.git] / lib / sexp.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 # S-Expression parsing facilities
12 module sexp
13
14 intrude import parser_base
15
16 # Any S-Expression entity
17 abstract class SExpEntity
18
19 # Location in the source document
20 var location: nullable Location
21 end
22
23 # A full S-Expression, delimited by `(` and `)`
24 class SExp
25 super SExpEntity
26
27 # Children of a SExp
28 var content = new Array[SExpEntity]
29
30 redef fun to_s do return "({content.join(" ")})"
31
32 # Returns a pretty-printable version of self
33 #
34 # 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)"
35 fun pretty_to_s: String do return recurse_to_s(0)
36
37 private fun recurse_to_s(depth: Int): String do
38 var s = "{"\t" * depth}(\n"
39 for i in content do
40 if i isa SExp then
41 s += i.recurse_to_s(depth + 1)
42 s += "\n"
43 continue
44 end
45 s += "\t" * (depth + 1)
46 s += i.to_s
47 s += "\n"
48 end
49 return s + "{"\t" * depth})"
50 end
51 end
52
53 # A Double-quoted String
54 class SExpDQString
55 super SExpEntity
56
57 # Double-quoted string
58 var content: String
59
60 redef fun to_s do return content
61 end
62
63 # A float-value
64 class SExpFloat
65 super SExpEntity
66
67 # Floating-point value
68 var content: Float
69
70 redef fun to_s do return content.to_precision(2)
71 end
72
73 # Any Identifier, non string and non-float
74 class SExpId
75 super SExpEntity
76
77 # S-Exp compatible identifier
78 var content: String
79
80 redef fun to_s do return content
81 end
82
83 # An error parsing S-Expressions
84 class SExpError
85 super SExpEntity
86
87 # Cause of the error
88 var message: String
89
90 redef fun to_s do return "S-Expression error: {message} at {location or else "unknown location"}"
91 end
92
93 # S-Expression processor
94 class SExpProcessor
95 super StringProcessor
96
97 # Parses an S-Expression entity
98 fun parse_entity: SExpEntity do
99 var srclen = src.length
100 var delims = once ['(', ')', '"']
101 ignore_whitespaces
102 if pos >= srclen then return new SExpError("Empty S-Expression", location = new Location(line, line_offset))
103 var c = src[pos]
104 if pos >= srclen then return new SExpError("Empty S-Expression")
105 if c == '(' then
106 var cnt = new SExp
107 var loc = new Location(line, line_offset)
108 pos += 1
109 while pos < srclen and src[pos] != ')' do
110 var p = parse_entity
111 if p isa SExpError then break
112 cnt.content.add p
113 ignore_whitespaces
114 end
115 if pos < srclen and src[pos] == ')' then
116 pos += 1
117 return cnt
118 else
119 return new SExpError("Incomplete S-Expression", location = loc)
120 end
121 else if c == '"' then
122 var stdq = pos
123 pos += 1
124 ignore_until("\"")
125 pos += 1
126 var endq = pos
127 return new SExpDQString(src.substring(stdq, endq - stdq))
128 else
129 var stid = pos
130 while pos < srclen and not c.is_whitespace and not delims.has(c) do
131 c = src[pos]
132 pos += 1
133 end
134 if delims.has(c) or c.is_whitespace then pos -= 1
135 if pos >= srclen then return new SExpError("Invalid S-Expression")
136 var endid = pos
137 var cntstr = src.substring(stid, endid - stid)
138 var cnt: SExpEntity
139 if cntstr.is_numeric then
140 cnt = new SExpFloat(cntstr.to_f)
141 else
142 cnt = new SExpId(cntstr)
143 end
144 return cnt
145 end
146 end
147 end
148
149 redef class Text
150
151 # Tries to parse `self` as an S-Expression
152 fun to_sexp: SExpEntity do return (new SExpProcessor(self.to_s)).parse_entity
153 end