parser: add Location::text
[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 package 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
27
28 # Create a new sourcefile using a filename and a stream
29 init(filename: String, stream: IStream)
30 do
31 self.filename = filename
32 string = stream.read_all
33 line_starts[0] = 0
34 end
35
36 # Position of each line start
37 var line_starts: Array[Int] = new Array[Int]
38 end
39
40 # A location inside a source file
41 class Location
42 super Comparable
43 redef type OTHER: Location
44
45 readable var _file: nullable SourceFile
46 readable var _line_start: Int
47 readable var _line_end: Int
48 readable var _column_start: Int
49 readable var _column_end: Int
50
51 init(f: nullable SourceFile, line_s: Int, line_e: Int, column_s: Int, column_e: Int) do
52 _file = f
53 _line_start = line_s
54 _line_end = line_e
55 _column_start = column_s
56 _column_end = column_e
57 end
58
59 # The verbatim associated text in the source-file
60 fun text: String
61 do
62 var res = self.text_cache
63 if res != null then return res
64 var l = self
65 var pstart = l.file.line_starts[l.line_start-1] + l.column_start-1
66 var pend = l.file.line_starts[l.line_end-1] + l.column_end-1
67 res = l.file.string.substring(pstart, pend-pstart+1)
68 self.text_cache = res
69 return res
70 end
71
72 private var text_cache: nullable String
73
74 init with_file(f: SourceFile) do init(f,0,0,0,0)
75
76 redef fun ==(other: nullable Object): Bool do
77 if other == null then return false
78 if not other isa Location then return false
79
80 if other.file != file then return false
81 if other.line_start != line_start then return false
82 if other.line_end != line_end then return false
83 if other.column_start != column_start then return false
84 if other.column_end != column_end then return false
85
86 return true
87 end
88
89 fun located_in(loc: nullable Location): Bool do
90 if loc == null then return false
91
92 if line_start < loc.line_start then return false
93 if line_start > loc.line_end then return false
94
95 if line_end > loc.line_end then return false
96
97 if line_start == loc.line_start then
98 if column_start < loc.column_start then return false
99 if column_start > loc.column_end then return false
100 end
101
102 if line_end == loc.line_end and column_end > loc.column_end then return false
103
104 return true
105 end
106
107 redef fun to_s: String do
108 var file_part = ""
109 if file != null then
110 file_part = file.filename
111 if file.filename.length > 0 then file_part += ":"
112 end
113
114 if line_start == line_end then
115 if column_start == column_end then
116 return "{file_part}{line_start},{column_start}"
117 else
118 return "{file_part}{line_start},{column_start}--{column_end}"
119 end
120 else
121 return "{file_part}{line_start},{column_start}--{line_end},{column_end}"
122 end
123 end
124
125 fun relative_to(loc: nullable Location): String do
126 var relative: Location
127 if loc != null and loc.file == self.file then
128 relative = new Location(null, self.line_start, self.line_end, self.column_start, self.column_end)
129 else
130 relative = new Location(self.file, self.line_start, self.line_end, self.column_start, self.column_end)
131 end
132 return relative.to_s
133 end
134
135 redef fun <(other: OTHER): Bool do
136 if self == other then return false
137 if self.located_in(other) then return true
138 if other.located_in(self) then return false
139
140 if line_start != other.line_start then return line_start < other.line_start
141 if column_start != other.column_start then return column_start < other.column_start
142 if line_end != other.line_end then return line_end < other.line_end
143
144 return column_end < other.column_end
145 end
146
147 # Return the associated line with the location highlihted with color and a carret under the starting position
148 # `color' must be and terminal escape sequence used as "{escape}[{color}m;"
149 # "0;31" for red
150 # "1;31" for bright red
151 # "0;32" for green
152 fun colored_line(color: String): String
153 do
154 var esc = 27.ascii
155 var def = "{esc}[0m"
156 var col = "{esc}[{color}m"
157
158 var l = self
159 var i = l.line_start
160 var line_start = l.file.line_starts[i-1]
161 var line_end = line_start
162 var string = l.file.string
163 while line_end+1 < string.length and string[line_end+1] != '\n' and string[line_end+1] != '\r' do
164 line_end += 1
165 end
166 var lstart = string.substring(line_start, l.column_start - 1)
167 var cend
168 if i != l.line_end then
169 cend = line_end - line_start + 1
170 else
171 cend = l.column_end
172 end
173 var lmid
174 var lend
175 if line_start + cend <= string.length then
176 lmid = string.substring(line_start + l.column_start - 1, cend - l.column_start + 1)
177 lend = string.substring(line_start + cend, line_end - line_start - cend + 1)
178 else
179 lmid = ""
180 lend = ""
181 end
182 var indent = new Buffer
183 for j in [line_start..line_start+l.column_start-1[ do
184 if string[j] == '\t' then
185 indent.add '\t'
186 else
187 indent.add ' '
188 end
189 end
190 return "\t{lstart}{col}{lmid}{def}{lend}\n\t{indent}^"
191 end
192 end
193