1 /*
2  * The Doomsday Engine Project -- libcore
3  *
4  * Copyright © 2004-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
5  *
6  * @par License
7  * LGPL: http://www.gnu.org/licenses/lgpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
15  * General Public License for more details. You should have received a copy of
16  * the GNU Lesser General Public License along with this program; if not, see:
17  * http://www.gnu.org/licenses</small>
18  */
19 
20 #ifndef LIBDENG2_TOKENRANGE_H
21 #define LIBDENG2_TOKENRANGE_H
22 
23 #include "../String"
24 #include "../TokenBuffer"
25 
26 namespace de {
27 
28 /**
29  * Utility class for handling a range of tokens inside a token buffer.
30  * Parsers use this for keeping track of which section of the tokens is
31  * being analyzed.
32  *
33  * "Indices" refer to indices in the token buffer. "Positions" refer to
34  * locations relative to the beginning of the range, starting from zero.
35  *
36  * @ingroup script
37  */
38 class DENG2_PUBLIC TokenRange
39 {
40 public:
41     /// The token range is unexpectedly empty. @ingroup errors
42     DENG2_ERROR(EmptyRangeError);
43 
44     /// A position outside the range is accessed. @ingroup errors
45     DENG2_ERROR(OutOfBoundsError);
46 
47     /// A matching bracket cannot be located within the range. @ingroup errors
48     DENG2_ERROR(MismatchedBracketError);
49 
50 public:
51     TokenRange();
52 
53     /// Constructor that uses the entire range of tokens.
54     TokenRange(TokenBuffer const &tokens);
55 
56     /// Constructor that uses a specific set of tokens.
57     TokenRange(TokenBuffer const &tokens, duint startIndex, duint endIndex);
58 
buffer()59     TokenBuffer const &buffer() const {
60         return *_tokens;
61     }
62 
63     /// Determines the length of the range.
64     /// @return Number of tokens in the range.
size()65     duint size() const {
66         return _end - _start;
67     }
68 
isEmpty()69     bool isEmpty() const { return !size(); }
70 
71     TokenRange undefinedRange() const;
72 
73     bool undefined() const;
74 
75     /// Converts a position within the range to an index in the buffer.
76     duint tokenIndex(duint pos) const;
77 
78     duint tokenPos(duint index) const;
79 
80     /// Returns a specific token from the token buffer.
81     /// @param pos Position of the token within the range.
82     Token const &token(duint pos) const;
83 
84     Token const &firstToken() const;
85 
86     Token const &lastToken() const;
87 
88     /// Determines whether the range begins with a specific token.
89     bool beginsWith(QChar const *token) const;
90 
91     /**
92      * Composes a subrange that starts from a specific position.
93      *
94      * @param pos  Start position of subrange.
95      *
96      * @return Subrange that starts from position @a pos and continues until
97      *         the end of the range.
98      */
99     TokenRange startingFrom(duint pos) const;
100 
101     /**
102      * Composes a subrange that ends at a specific position.
103      *
104      * @param pos  End position of subrange.
105      *
106      * @return  Subrange that starts from the beginning of this range
107      *          and ends to @a pos.
108      */
109     TokenRange endingTo(duint pos) const;
110 
111     TokenRange between(duint startPos, duint endPos) const;
112 
shrink(duint count)113     TokenRange shrink(duint count) const {
114         return between(count, size() - count);
115     }
116 
117     /**
118      * Determines if the range contains a specific token.
119      *
120      * @param token Token to look for.
121      *
122      * @return @c true, if token was found, otherwise @c false.
123      */
has(QChar const * token)124     bool has(QChar const *token) const {
125         return find(token) >= 0;
126     }
127 
128     /**
129      * Determines if the range contains a specific token, but only if
130      * it is outside any brackets.
131      *
132      * @param token Token to look for.
133      *
134      * @return @c true, if token was found, otherwise @c false.
135      */
hasBracketless(QChar const * token)136     bool hasBracketless(QChar const *token) const {
137         return findIndexSkippingBrackets(token, _start) >= 0;
138     }
139 
140     /**
141      * Finds the position of a specific token within the range.
142      *
143      * @param token Token to find.
144      * @param startPos Position where to start looking.
145      *
146      * @return Position of the token, or -1 if not found.
147      */
148     dint find(QChar const *token, dint startPos = 0) const;
149 
150     dint findBracketless(QChar const *token, dint startPos = 0) const;
151 
152     /**
153      * Finds the index of a specific token within the range. When
154      * an opening bracket is encountered, its contents are skipped.
155      *
156      * @param token Token to find.
157      * @param startIndex Index where to start looking.
158      *
159      * @return Index of the token, or -1 if not found.
160      */
161     dint findIndexSkippingBrackets(QChar const *token, dint startIndex) const;
162 
163     /**
164      * Finds the next token subrange which is delimited with @c
165      * delimiter. @c subrange is adjusted so that if @c true is
166      * returned, it will contain the correct subrange. When calling
167      * this the first time, set the subrange to <code>undefinedRange()</code>.
168      *
169      * @param delimiter Delimiting token.
170      * @param subrange Token range that receives the delimiting subrange.
171      *
172      * @return  @c true, if the next delimited subrange found successfully.
173      *          Otherwise @c false.
174      */
175     bool getNextDelimited(QChar const *delimiter, TokenRange &subrange) const;
176 
177     /**
178      * Locates the matching closing bracket. If the matching bracket
179      * is not found, an exception is thrown.
180      *
181      * @param openBracketPos Position of the opening bracket.
182      *
183      * @return Position of the closing bracket.
184      */
185     duint closingBracket(duint openBracketPos) const;
186 
187     /**
188      * Locates the matching opening bracket. If the matching bracket
189      * is not found, an exception is thrown.
190      *
191      * @param closeBracketPos Position of the closing bracket.
192      *
193      * @return Position of the opening bracket.
194      */
195     duint openingBracket(duint closeBracketPos) const;
196 
197     /**
198      * Composes a string representation of the token range. Intended
199      * for error reporting.
200      *
201      * @return String containing the text of the tokens.
202      */
203     String asText() const;
204 
205 public:
206     static void bracketTokens(Token const &openingToken,
207         QChar const * &opening, QChar const * &closing);
208 
209 private:
210     TokenBuffer const *_tokens;
211 
212     /// Index of the start of the range. This is the first token in
213     /// the range.
214     duint _start;
215 
216     /// Index of the end of the range, plus one. This is the token
217     /// just after the last token of the range.
218     duint _end;
219 };
220 
221 } // namespace de
222 
223 #endif /* LIBDENG2_TOKENRANGE_H */
224