src/location: add doc to `Location::column_start`
[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 # This module is used to model 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: IStream
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 var file: nullable SourceFile
55 var line_start: Int
56 var line_end: Int
57
58 # Start of this location on `line_start`
59 #
60 # A `column_start` of 1 means the first column or character.
61 #
62 # If `column_start == 0` this location concerns the whole line.
63 #
64 # Require: `column_start >= 0`
65 var column_start: Int
66
67 var column_end: Int
68
69 # The index in the start character in the source
70 fun pstart: Int do return file.line_starts[line_start-1] + column_start-1
71
72 # The index on the end character in the source
73 fun pend: Int do return file.line_starts[line_end-1] + column_end-1
74
75 # The verbatim associated text in the source-file
76 fun text: String
77 do
78 var res = self.text_cache
79 if res != null then return res
80 var l = self
81 var pstart = self.pstart
82 var pend = self.pend
83 res = l.file.string.substring(pstart, pend-pstart+1)
84 self.text_cache = res
85 return res
86 end
87
88 private var text_cache: nullable String = null
89
90 init with_file(f: SourceFile) do init(f,0,0,0,0)
91
92 redef fun ==(other: nullable Object): Bool do
93 if other == null then return false
94 if not other isa Location then return false
95
96 if other.file != file then return false
97 if other.line_start != line_start then return false
98 if other.line_end != line_end then return false
99 if other.column_start != column_start then return false
100 if other.column_end != column_end then return false
101
102 return true
103 end
104
105 # Is `self` included (or equals) to `loc`?
106 fun located_in(loc: nullable Location): Bool do
107 if loc == null then return false
108
109 if line_start < loc.line_start then return false
110 if line_start > loc.line_end then return false
111
112 if line_end > loc.line_end then return false
113
114 if line_start == loc.line_start then
115 if column_start < loc.column_start then return false
116 if column_start > loc.column_end then return false
117 end
118
119 if line_end == loc.line_end and column_end > loc.column_end then return false
120
121 return true
122 end
123
124 redef fun to_s: String do
125 var file_part = ""
126 if file != null then
127 file_part = file.filename
128 if file.filename.length > 0 then file_part += ":"
129 end
130
131 if line_start == line_end then
132 if column_start == column_end then
133 return "{file_part}{line_start},{column_start}"
134 else
135 return "{file_part}{line_start},{column_start}--{column_end}"
136 end
137 else
138 return "{file_part}{line_start},{column_start}--{line_end},{column_end}"
139 end
140 end
141
142 # Return a location message according to an observer.
143 #
144 # Currently, if both are in the same file, the file information is not present in the result.
145 fun relative_to(loc: nullable Location): String do
146 var relative: Location
147 if loc != null and loc.file == self.file then
148 relative = new Location(null, self.line_start, self.line_end, self.column_start, self.column_end)
149 else
150 relative = new Location(self.file, self.line_start, self.line_end, self.column_start, self.column_end)
151 end
152 return relative.to_s
153 end
154
155 redef fun <(other: OTHER): Bool do
156 if self == other then return false
157 if self.located_in(other) then return true
158 if other.located_in(self) then return false
159
160 if line_start != other.line_start then return line_start < other.line_start
161 if column_start != other.column_start then return column_start < other.column_start
162 if line_end != other.line_end then return line_end < other.line_end
163
164 return column_end < other.column_end
165 end
166
167 # Return the associated line with the location highlighted with color and a caret under the starting position
168 # `color` must be and terminal escape sequence used as `"{escape}[{color}m;"`
169 # * `"0;31"` for red
170 # * `"1;31"` for bright red
171 # * `"0;32"` for green
172 fun colored_line(color: String): String
173 do
174 var esc = 27.ascii
175 var def = "{esc}[0m"
176 var col = "{esc}[{color}m"
177
178 var l = self
179 var i = l.line_start
180 var line_start = l.file.line_starts[i-1]
181 var line_end = line_start
182 var string = l.file.string
183 while line_end+1 < string.length and string.chars[line_end+1] != '\n' and string.chars[line_end+1] != '\r' do
184 line_end += 1
185 end
186 var lstart
187 if l.column_start > 0 then
188 lstart = string.substring(line_start, l.column_start - 1)
189 else
190 lstart = ""
191 end
192 var cend
193 if i != l.line_end then
194 cend = line_end - line_start + 1
195 else
196 cend = l.column_end
197 end
198 var lmid
199 var lend
200 if line_start + cend <= string.length then
201 lmid = string.substring(line_start + l.column_start - 1, cend - l.column_start + 1)
202 lend = string.substring(line_start + cend, line_end - line_start - cend + 1)
203 else
204 lmid = ""
205 lend = ""
206 end
207 var indent = new FlatBuffer
208 for j in [line_start..line_start+l.column_start-1[ do
209 if string.chars[j] == '\t' then
210 indent.add '\t'
211 else
212 indent.add ' '
213 end
214 end
215 return "\t{lstart}{col}{lmid}{def}{lend}\n\t{indent}^"
216 end
217 end