loader: build_module_importation invalidates the mmodule on errors
[nit.git] / src / location.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2009 Jean-Sebastien Gelinas <calestar@gmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Nit source-file and locations in source-file
18 module location
19
20 # A raw text Nit source file
21 class SourceFile
22 # The path of the source
23 var filename: String
24
25 # The content of the source
26 var string: String is noinit
27
28 # The original stream used to initialize `string`
29 var stream: Reader
30
31 init
32 do
33 string = stream.read_all
34 line_starts[0] = 0
35 end
36
37 # Create a new sourcefile using a dummy filename and a given content
38 init from_string(filename: String, string: String)
39 do
40 self.filename = filename
41 self.string = string
42 line_starts[0] = 0
43 end
44
45 # Position of each line start
46 var line_starts = new Array[Int]
47 end
48
49 # A location inside a source file
50 class Location
51 super Comparable
52 redef type OTHER: Location
53
54 # The associated source-file
55 var file: nullable SourceFile
56
57 # The starting line number (starting from 1)
58 #
59 # If `line_start==0` then the whole file is considered
60 var line_start: Int
61
62 # The stopping line number (starting from 1)
63 var line_end: Int
64
65 # Start of this location on `line_start`
66 #
67 # A `column_start` of 1 means the first column or character.
68 #
69 # If `column_start == 0` this location concerns the whole line.
70 #
71 # Require: `column_start >= 0`
72 var column_start: Int
73
74 # End of this location on `line_end`
75 var column_end: Int
76
77 # The index in the start character in the source
78 fun pstart: Int do return file.line_starts[line_start-1] + column_start-1
79
80 # The index on the end character in the source
81 fun pend: Int do return file.line_starts[line_end-1] + column_end-1
82
83 # The verbatim associated text in the source-file
84 fun text: String
85 do
86 var res = self.text_cache
87 if res != null then return res
88 var l = self
89 var pstart = self.pstart
90 var pend = self.pend
91 res = l.file.string.substring(pstart, pend-pstart+1)
92 self.text_cache = res
93 return res
94 end
95
96 private var text_cache: nullable String = null
97
98 redef fun ==(other: nullable Object): Bool do
99 if other == null then return false
100 if not other isa Location then return false
101
102 if other.file != file then return false
103 if other.line_start != line_start then return false
104 if other.line_end != line_end then return false
105 if other.column_start != column_start then return false
106 if other.column_end != column_end then return false
107
108 return true
109 end
110
111 # Is `self` included (or equals) to `loc`?
112 fun located_in(loc: nullable Location): Bool do
113 if loc == null then return false
114
115 if line_start < loc.line_start then return false
116 if line_start > loc.line_end then return false
117
118 if line_end > loc.line_end then return false
119
120 if line_start == loc.line_start then
121 if column_start < loc.column_start then return false
122 if column_start > loc.column_end then return false
123 end
124
125 if line_end == loc.line_end and column_end > loc.column_end then return false
126
127 return true
128 end
129
130 redef fun to_s: String do
131 var file_part = ""
132 if file != null then
133 file_part = file.filename
134 end
135
136 if line_start <= 0 then return file_part
137
138 if file != null and file.filename.length > 0 then file_part += ":"
139
140 if line_start == line_end then
141 if column_start == column_end then
142 return "{file_part}{line_start},{column_start}"
143 else
144 return "{file_part}{line_start},{column_start}--{column_end}"
145 end
146 else
147 return "{file_part}{line_start},{column_start}--{line_end},{column_end}"
148 end
149 end
150
151 # Return a location message according to an observer.
152 #
153 # Currently, if both are in the same file, the file information is not present in the result.
154 fun relative_to(loc: nullable Location): String do
155 var relative: Location
156 if loc != null and loc.file == self.file then
157 relative = new Location(null, self.line_start, self.line_end, self.column_start, self.column_end)
158 else
159 relative = new Location(self.file, self.line_start, self.line_end, self.column_start, self.column_end)
160 end
161 return relative.to_s
162 end
163
164 redef fun <(other: OTHER): Bool do
165 if self == other then return false
166 if self.located_in(other) then return true
167 if other.located_in(self) then return false
168
169 if line_start != other.line_start then return line_start < other.line_start
170 if column_start != other.column_start then return column_start < other.column_start
171 if line_end != other.line_end then return line_end < other.line_end
172
173 return column_end < other.column_end
174 end
175
176 # Return the associated line with the location highlighted with color and a caret under the starting position
177 # `color` must be and terminal escape sequence used as `"{escape}[{color}m;"`
178 # * `"0;31"` for red
179 # * `"1;31"` for bright red
180 # * `"0;32"` for green
181 fun colored_line(color: String): String
182 do
183 var esc = 27.ascii
184 var def = "{esc}[0m"
185 var col = "{esc}[{color}m"
186
187 var l = self
188 var i = l.line_start
189 if i <= 0 then return ""
190
191 var line_start = l.file.line_starts[i-1]
192 var line_end = line_start
193 var string = l.file.string
194 while line_end+1 < string.length and string.chars[line_end+1] != '\n' and string.chars[line_end+1] != '\r' do
195 line_end += 1
196 end
197 var lstart
198 if l.column_start > 0 then
199 lstart = string.substring(line_start, l.column_start - 1)
200 else
201 lstart = ""
202 end
203 var cend
204 if i != l.line_end then
205 cend = line_end - line_start + 1
206 else
207 cend = l.column_end
208 end
209 var lmid
210 var lend
211 if line_start + cend <= string.length then
212 lmid = string.substring(line_start + l.column_start - 1, cend - l.column_start + 1)
213 lend = string.substring(line_start + cend, line_end - line_start - cend + 1)
214 else
215 lmid = ""
216 lend = ""
217 end
218 var indent = new FlatBuffer
219 for j in [line_start..line_start+l.column_start-1[ do
220 if string.chars[j] == '\t' then
221 indent.add '\t'
222 else
223 indent.add ' '
224 end
225 end
226 return "\t{lstart}{col}{lmid}{def}{lend}\n\t{indent}^"
227 end
228 end