README: document nit_env.sh
[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(new Location(line, line_offset), "Empty S-Expression")
103 var c = src[pos]
104 if c == '(' then
105 var cnt = new SExp
106 var loc = new Location(line, line_offset)
107 pos += 1
108 while pos < srclen and src[pos] != ')' do
109 var p = parse_entity
110 if p isa SExpError then break
111 cnt.content.add p
112 ignore_whitespaces
113 end
114 if pos < srclen and src[pos] == ')' then
115 pos += 1
116 return cnt
117 else
118 return new SExpError(loc, "Incomplete S-Expression")
119 end
120 else if c == '"' then
121 var stdq = pos
122 var loc = new Location(line, line_offset)
123 pos += 1
124 ignore_until("\"")
125 pos += 1
126 var endq = pos
127 return new SExpDQString(loc, src.substring(stdq, endq - stdq))
128 else
129 var stid = pos
130 var loc = new Location(line, line_offset)
131 while pos < srclen and not c.is_whitespace and not delims.has(c) do
132 c = src[pos]
133 pos += 1
134 end
135 if delims.has(c) or c.is_whitespace then pos -= 1
136 if pos >= srclen then return new SExpError(loc, "Invalid S-Expression")
137 var endid = pos
138 var cntstr = src.substring(stid, endid - stid)
139 var cnt: SExpEntity
140 if cntstr.is_numeric then
141 cnt = new SExpFloat(loc, cntstr.to_f)
142 else
143 cnt = new SExpId(loc, cntstr)
144 end
145 return cnt
146 end
147 end
148 end
149
150 redef class Text
151
152 # Tries to parse `self` as an S-Expression
153 fun to_sexp: SExpEntity do return (new SExpProcessor(self.to_s)).parse_entity
154 end