xref: /netbsd-src/external/bsd/kyua-cli/dist/utils/text/templates.cpp (revision 6b3a42af15b5e090c339512c790dd68f3d11a9d8)
1*6b3a42afSjmmv // Copyright 2012 Google Inc.
2*6b3a42afSjmmv // All rights reserved.
3*6b3a42afSjmmv //
4*6b3a42afSjmmv // Redistribution and use in source and binary forms, with or without
5*6b3a42afSjmmv // modification, are permitted provided that the following conditions are
6*6b3a42afSjmmv // met:
7*6b3a42afSjmmv //
8*6b3a42afSjmmv // * Redistributions of source code must retain the above copyright
9*6b3a42afSjmmv //   notice, this list of conditions and the following disclaimer.
10*6b3a42afSjmmv // * Redistributions in binary form must reproduce the above copyright
11*6b3a42afSjmmv //   notice, this list of conditions and the following disclaimer in the
12*6b3a42afSjmmv //   documentation and/or other materials provided with the distribution.
13*6b3a42afSjmmv // * Neither the name of Google Inc. nor the names of its contributors
14*6b3a42afSjmmv //   may be used to endorse or promote products derived from this software
15*6b3a42afSjmmv //   without specific prior written permission.
16*6b3a42afSjmmv //
17*6b3a42afSjmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*6b3a42afSjmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*6b3a42afSjmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*6b3a42afSjmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*6b3a42afSjmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*6b3a42afSjmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*6b3a42afSjmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*6b3a42afSjmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*6b3a42afSjmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*6b3a42afSjmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*6b3a42afSjmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*6b3a42afSjmmv 
29*6b3a42afSjmmv #include "utils/text/templates.hpp"
30*6b3a42afSjmmv 
31*6b3a42afSjmmv #include <algorithm>
32*6b3a42afSjmmv #include <fstream>
33*6b3a42afSjmmv #include <sstream>
34*6b3a42afSjmmv #include <stack>
35*6b3a42afSjmmv 
36*6b3a42afSjmmv #include "utils/format/macros.hpp"
37*6b3a42afSjmmv #include "utils/noncopyable.hpp"
38*6b3a42afSjmmv #include "utils/sanity.hpp"
39*6b3a42afSjmmv #include "utils/text/exceptions.hpp"
40*6b3a42afSjmmv #include "utils/text/operations.ipp"
41*6b3a42afSjmmv 
42*6b3a42afSjmmv namespace text = utils::text;
43*6b3a42afSjmmv 
44*6b3a42afSjmmv 
45*6b3a42afSjmmv namespace {
46*6b3a42afSjmmv 
47*6b3a42afSjmmv 
48*6b3a42afSjmmv /// Definition of a template statement.
49*6b3a42afSjmmv ///
50*6b3a42afSjmmv /// A template statement is a particular line in the input file that is
51*6b3a42afSjmmv /// preceeded by a template marker.  This class provides a high-level
52*6b3a42afSjmmv /// representation of the contents of such statement and a mechanism to parse
53*6b3a42afSjmmv /// the textual line into this high-level representation.
54*6b3a42afSjmmv class statement_def {
55*6b3a42afSjmmv public:
56*6b3a42afSjmmv     /// Types of the known statements.
57*6b3a42afSjmmv     enum statement_type {
58*6b3a42afSjmmv         /// Alternative clause of a conditional.
59*6b3a42afSjmmv         ///
60*6b3a42afSjmmv         /// Takes no arguments.
61*6b3a42afSjmmv         type_else,
62*6b3a42afSjmmv 
63*6b3a42afSjmmv         /// End of conditional marker.
64*6b3a42afSjmmv         ///
65*6b3a42afSjmmv         /// Takes no arguments.
66*6b3a42afSjmmv         type_endif,
67*6b3a42afSjmmv 
68*6b3a42afSjmmv         /// End of loop marker.
69*6b3a42afSjmmv         ///
70*6b3a42afSjmmv         /// Takes no arguments.
71*6b3a42afSjmmv         type_endloop,
72*6b3a42afSjmmv 
73*6b3a42afSjmmv         /// Beginning of a conditional.
74*6b3a42afSjmmv         ///
75*6b3a42afSjmmv         /// Takes a single argument, which denotes the name of the variable or
76*6b3a42afSjmmv         /// vector to check for existence.  This is the only expression
77*6b3a42afSjmmv         /// supported.
78*6b3a42afSjmmv         type_if,
79*6b3a42afSjmmv 
80*6b3a42afSjmmv         /// Beginning of a loop over all the elements of a vector.
81*6b3a42afSjmmv         ///
82*6b3a42afSjmmv         /// Takes two arguments: the name of the vector over which to iterate
83*6b3a42afSjmmv         /// and the name of the iterator to later index this vector.
84*6b3a42afSjmmv         type_loop,
85*6b3a42afSjmmv     };
86*6b3a42afSjmmv 
87*6b3a42afSjmmv private:
88*6b3a42afSjmmv     /// Internal data describing the structure of a particular statement type.
89*6b3a42afSjmmv     struct type_descriptor {
90*6b3a42afSjmmv         /// The native type of the statement.
91*6b3a42afSjmmv         statement_type type;
92*6b3a42afSjmmv 
93*6b3a42afSjmmv         /// The expected number of arguments.
94*6b3a42afSjmmv         unsigned int n_arguments;
95*6b3a42afSjmmv 
96*6b3a42afSjmmv         /// Constructs a new type descriptor.
97*6b3a42afSjmmv         ///
98*6b3a42afSjmmv         /// \param type_ The native type of the statement.
99*6b3a42afSjmmv         /// \param n_arguments_ The expected number of arguments.
type_descriptor__anonbbfdf9370111::statement_def::type_descriptor100*6b3a42afSjmmv         type_descriptor(const statement_type type_,
101*6b3a42afSjmmv                         const unsigned int n_arguments_)
102*6b3a42afSjmmv             : type(type_), n_arguments(n_arguments_)
103*6b3a42afSjmmv         {
104*6b3a42afSjmmv         }
105*6b3a42afSjmmv     };
106*6b3a42afSjmmv 
107*6b3a42afSjmmv     /// Mapping of statement type names to their definitions.
108*6b3a42afSjmmv     typedef std::map< std::string, type_descriptor > types_map;
109*6b3a42afSjmmv 
110*6b3a42afSjmmv     /// Description of the different statement types.
111*6b3a42afSjmmv     ///
112*6b3a42afSjmmv     /// This static map is initialized once and reused later for any statement
113*6b3a42afSjmmv     /// lookup.  Unfortunately, we cannot perform this initialization in a
114*6b3a42afSjmmv     /// static manner without C++11.
115*6b3a42afSjmmv     static types_map _types;
116*6b3a42afSjmmv 
117*6b3a42afSjmmv     /// Generates a new types definition map.
118*6b3a42afSjmmv     ///
119*6b3a42afSjmmv     /// \return A new types definition map, to be assigned to _types.
120*6b3a42afSjmmv     static types_map
generate_types_map(void)121*6b3a42afSjmmv     generate_types_map(void)
122*6b3a42afSjmmv     {
123*6b3a42afSjmmv         // If you change this, please edit the comments in the enum above.
124*6b3a42afSjmmv         types_map types;
125*6b3a42afSjmmv         types.insert(types_map::value_type(
126*6b3a42afSjmmv             "else", type_descriptor(type_else, 0)));
127*6b3a42afSjmmv         types.insert(types_map::value_type(
128*6b3a42afSjmmv             "endif", type_descriptor(type_endif, 0)));
129*6b3a42afSjmmv         types.insert(types_map::value_type(
130*6b3a42afSjmmv             "endloop", type_descriptor(type_endloop, 0)));
131*6b3a42afSjmmv         types.insert(types_map::value_type(
132*6b3a42afSjmmv             "if", type_descriptor(type_if, 1)));
133*6b3a42afSjmmv         types.insert(types_map::value_type(
134*6b3a42afSjmmv             "loop", type_descriptor(type_loop, 2)));
135*6b3a42afSjmmv         return types;
136*6b3a42afSjmmv     }
137*6b3a42afSjmmv 
138*6b3a42afSjmmv public:
139*6b3a42afSjmmv     /// The type of the statement.
140*6b3a42afSjmmv     statement_type type;
141*6b3a42afSjmmv 
142*6b3a42afSjmmv     /// The arguments to the statement, in textual form.
143*6b3a42afSjmmv     const std::vector< std::string > arguments;
144*6b3a42afSjmmv 
145*6b3a42afSjmmv     /// Creates a new statement.
146*6b3a42afSjmmv     ///
147*6b3a42afSjmmv     /// \param type_ The type of the statement.
148*6b3a42afSjmmv     /// \param arguments_ The arguments to the statement.
statement_def(const statement_type & type_,const std::vector<std::string> & arguments_)149*6b3a42afSjmmv     statement_def(const statement_type& type_,
150*6b3a42afSjmmv                   const std::vector< std::string >& arguments_) :
151*6b3a42afSjmmv         type(type_), arguments(arguments_)
152*6b3a42afSjmmv     {
153*6b3a42afSjmmv #if !defined(NDEBUG)
154*6b3a42afSjmmv         for (types_map::const_iterator iter = _types.begin();
155*6b3a42afSjmmv              iter != _types.end(); ++iter) {
156*6b3a42afSjmmv             const type_descriptor& descriptor = (*iter).second;
157*6b3a42afSjmmv             if (descriptor.type == type_) {
158*6b3a42afSjmmv                 PRE(descriptor.n_arguments == arguments_.size());
159*6b3a42afSjmmv                 return;
160*6b3a42afSjmmv             }
161*6b3a42afSjmmv         }
162*6b3a42afSjmmv         UNREACHABLE;
163*6b3a42afSjmmv #endif
164*6b3a42afSjmmv     }
165*6b3a42afSjmmv 
166*6b3a42afSjmmv     /// Parses a statement.
167*6b3a42afSjmmv     ///
168*6b3a42afSjmmv     /// \param line The textual representation of the statement without any
169*6b3a42afSjmmv     ///     prefix.
170*6b3a42afSjmmv     ///
171*6b3a42afSjmmv     /// \return The parsed statement.
172*6b3a42afSjmmv     ///
173*6b3a42afSjmmv     /// \throw text::syntax_error If the statement is not correctly defined.
174*6b3a42afSjmmv     static statement_def
parse(const std::string & line)175*6b3a42afSjmmv     parse(const std::string& line)
176*6b3a42afSjmmv     {
177*6b3a42afSjmmv         if (_types.empty())
178*6b3a42afSjmmv             _types = generate_types_map();
179*6b3a42afSjmmv 
180*6b3a42afSjmmv         const std::vector< std::string > words = text::split(line, ' ');
181*6b3a42afSjmmv         if (words.empty())
182*6b3a42afSjmmv             throw text::syntax_error("Empty statement");
183*6b3a42afSjmmv 
184*6b3a42afSjmmv         const types_map::const_iterator iter = _types.find(words[0]);
185*6b3a42afSjmmv         if (iter == _types.end())
186*6b3a42afSjmmv             throw text::syntax_error(F("Unknown statement '%s'") % words[0]);
187*6b3a42afSjmmv         const type_descriptor& descriptor = (*iter).second;
188*6b3a42afSjmmv 
189*6b3a42afSjmmv         if (words.size() - 1 != descriptor.n_arguments)
190*6b3a42afSjmmv             throw text::syntax_error(F("Invalid number of arguments for "
191*6b3a42afSjmmv                                        "statement '%s'") % words[0]);
192*6b3a42afSjmmv 
193*6b3a42afSjmmv         std::vector< std::string > new_arguments;
194*6b3a42afSjmmv         new_arguments.resize(words.size() - 1);
195*6b3a42afSjmmv         std::copy(words.begin() + 1, words.end(), new_arguments.begin());
196*6b3a42afSjmmv 
197*6b3a42afSjmmv         return statement_def(descriptor.type, new_arguments);
198*6b3a42afSjmmv     }
199*6b3a42afSjmmv };
200*6b3a42afSjmmv 
201*6b3a42afSjmmv 
202*6b3a42afSjmmv statement_def::types_map statement_def::_types;
203*6b3a42afSjmmv 
204*6b3a42afSjmmv 
205*6b3a42afSjmmv /// Definition of a loop.
206*6b3a42afSjmmv ///
207*6b3a42afSjmmv /// This simple structure is used to keep track of the parameters of a loop.
208*6b3a42afSjmmv struct loop_def {
209*6b3a42afSjmmv     /// The name of the vector over which this loop is iterating.
210*6b3a42afSjmmv     std::string vector;
211*6b3a42afSjmmv 
212*6b3a42afSjmmv     /// The name of the iterator defined by this loop.
213*6b3a42afSjmmv     std::string iterator;
214*6b3a42afSjmmv 
215*6b3a42afSjmmv     /// Position in the input to which to rewind to on looping.
216*6b3a42afSjmmv     ///
217*6b3a42afSjmmv     /// This position points to the line after the loop statement, not the loop
218*6b3a42afSjmmv     /// itself.  This is one of the reasons why we have this structure, so that
219*6b3a42afSjmmv     /// we can maintain the data about the loop without having to re-process it.
220*6b3a42afSjmmv     std::istream::pos_type position;
221*6b3a42afSjmmv 
222*6b3a42afSjmmv     /// Constructs a new loop definition.
223*6b3a42afSjmmv     ///
224*6b3a42afSjmmv     /// \param vector_ The name of the vector (first argument).
225*6b3a42afSjmmv     /// \param iterator_ The name of the iterator (second argumnet).
226*6b3a42afSjmmv     /// \param position_ Position of the next line after the loop statement.
loop_def__anonbbfdf9370111::loop_def227*6b3a42afSjmmv     loop_def(const std::string& vector_, const std::string& iterator_,
228*6b3a42afSjmmv              const std::istream::pos_type position_) :
229*6b3a42afSjmmv         vector(vector_), iterator(iterator_), position(position_)
230*6b3a42afSjmmv     {
231*6b3a42afSjmmv     }
232*6b3a42afSjmmv };
233*6b3a42afSjmmv 
234*6b3a42afSjmmv 
235*6b3a42afSjmmv /// Stateful class to instantiate the templates in an input stream.
236*6b3a42afSjmmv ///
237*6b3a42afSjmmv /// The goal of this parser is to scan the input once and not buffer anything in
238*6b3a42afSjmmv /// memory.  The only exception are loops: loops are reinterpreted on every
239*6b3a42afSjmmv /// iteration from the same input file by rewidining the stream to the
240*6b3a42afSjmmv /// appropriate position.
241*6b3a42afSjmmv class templates_parser : utils::noncopyable {
242*6b3a42afSjmmv     /// The templates to apply.
243*6b3a42afSjmmv     ///
244*6b3a42afSjmmv     /// Note that this is not const because the parser has to have write access
245*6b3a42afSjmmv     /// to the templates.  In particular, it needs to be able to define the
246*6b3a42afSjmmv     /// iterators as regular variables.
247*6b3a42afSjmmv     text::templates_def _templates;
248*6b3a42afSjmmv 
249*6b3a42afSjmmv     /// Prefix that marks a line as a statement.
250*6b3a42afSjmmv     const std::string _prefix;
251*6b3a42afSjmmv 
252*6b3a42afSjmmv     /// Delimiter to surround an expression instantiation.
253*6b3a42afSjmmv     const std::string _delimiter;
254*6b3a42afSjmmv 
255*6b3a42afSjmmv     /// Whether to skip incoming lines or not.
256*6b3a42afSjmmv     ///
257*6b3a42afSjmmv     /// The top of the stack is true whenever we encounter a conditional that
258*6b3a42afSjmmv     /// evaluates to false or a loop that does not have any iterations left.
259*6b3a42afSjmmv     /// Under these circumstances, we need to continue scanning the input stream
260*6b3a42afSjmmv     /// until we find the matching closing endif or endloop construct.
261*6b3a42afSjmmv     ///
262*6b3a42afSjmmv     /// This is a stack rather than a plain boolean to allow us deal with
263*6b3a42afSjmmv     /// if-else clauses.
264*6b3a42afSjmmv     std::stack< bool > _skip;
265*6b3a42afSjmmv 
266*6b3a42afSjmmv     /// Current count of nested conditionals.
267*6b3a42afSjmmv     unsigned int _if_level;
268*6b3a42afSjmmv 
269*6b3a42afSjmmv     /// Level of the top-most conditional that evaluated to false.
270*6b3a42afSjmmv     unsigned int _exit_if_level;
271*6b3a42afSjmmv 
272*6b3a42afSjmmv     /// Current count of nested loops.
273*6b3a42afSjmmv     unsigned int _loop_level;
274*6b3a42afSjmmv 
275*6b3a42afSjmmv     /// Level of the top-most loop that does not have any iterations left.
276*6b3a42afSjmmv     unsigned int _exit_loop_level;
277*6b3a42afSjmmv 
278*6b3a42afSjmmv     /// Information about all the nested loops up to the current point.
279*6b3a42afSjmmv     std::stack< loop_def > _loops;
280*6b3a42afSjmmv 
281*6b3a42afSjmmv     /// Checks if a line is a statement or not.
282*6b3a42afSjmmv     ///
283*6b3a42afSjmmv     /// \param line The line to validate.
284*6b3a42afSjmmv     ///
285*6b3a42afSjmmv     /// \return True if the line looks like a statement, which is determined by
286*6b3a42afSjmmv     /// checking if the line starts by the predefined prefix.
287*6b3a42afSjmmv     bool
is_statement(const std::string & line)288*6b3a42afSjmmv     is_statement(const std::string& line)
289*6b3a42afSjmmv     {
290*6b3a42afSjmmv         return ((line.length() >= _prefix.length() &&
291*6b3a42afSjmmv                  line.substr(0, _prefix.length()) == _prefix) &&
292*6b3a42afSjmmv                 (line.length() < _delimiter.length() ||
293*6b3a42afSjmmv                  line.substr(0, _delimiter.length()) != _delimiter));
294*6b3a42afSjmmv     }
295*6b3a42afSjmmv 
296*6b3a42afSjmmv     /// Parses a given statement line into a statement definition.
297*6b3a42afSjmmv     ///
298*6b3a42afSjmmv     /// \param line The line to validate; it must be a valid statement.
299*6b3a42afSjmmv     ///
300*6b3a42afSjmmv     /// \return The parsed statement.
301*6b3a42afSjmmv     ///
302*6b3a42afSjmmv     /// \throw text::syntax_error If the input is not a valid statement.
303*6b3a42afSjmmv     statement_def
parse_statement(const std::string & line)304*6b3a42afSjmmv     parse_statement(const std::string& line)
305*6b3a42afSjmmv     {
306*6b3a42afSjmmv         PRE(is_statement(line));
307*6b3a42afSjmmv         return statement_def::parse(line.substr(_prefix.length()));
308*6b3a42afSjmmv     }
309*6b3a42afSjmmv 
310*6b3a42afSjmmv     /// Processes a line from the input when not in skip mode.
311*6b3a42afSjmmv     ///
312*6b3a42afSjmmv     /// \param line The line to be processed.
313*6b3a42afSjmmv     /// \param input The input stream from which the line was read.  The current
314*6b3a42afSjmmv     ///     position in the stream must be after the line being processed.
315*6b3a42afSjmmv     /// \param output The output stream into which to write the results.
316*6b3a42afSjmmv     ///
317*6b3a42afSjmmv     /// \throw text::syntax_error If the input is not valid.
318*6b3a42afSjmmv     void
handle_normal(const std::string & line,std::istream & input,std::ostream & output)319*6b3a42afSjmmv     handle_normal(const std::string& line, std::istream& input,
320*6b3a42afSjmmv                   std::ostream& output)
321*6b3a42afSjmmv     {
322*6b3a42afSjmmv         if (!is_statement(line)) {
323*6b3a42afSjmmv             // Fast path.  Mostly to avoid an indentation level for the big
324*6b3a42afSjmmv             // chunk of code below.
325*6b3a42afSjmmv             output << line << '\n';
326*6b3a42afSjmmv             return;
327*6b3a42afSjmmv         }
328*6b3a42afSjmmv 
329*6b3a42afSjmmv         const statement_def statement = parse_statement(line);
330*6b3a42afSjmmv 
331*6b3a42afSjmmv         switch (statement.type) {
332*6b3a42afSjmmv         case statement_def::type_else:
333*6b3a42afSjmmv             _skip.top() = !_skip.top();
334*6b3a42afSjmmv             break;
335*6b3a42afSjmmv 
336*6b3a42afSjmmv         case statement_def::type_endif:
337*6b3a42afSjmmv             _if_level--;
338*6b3a42afSjmmv             break;
339*6b3a42afSjmmv 
340*6b3a42afSjmmv         case statement_def::type_endloop: {
341*6b3a42afSjmmv             PRE(_loops.size() == _loop_level);
342*6b3a42afSjmmv             loop_def& loop = _loops.top();
343*6b3a42afSjmmv 
344*6b3a42afSjmmv             const std::size_t next_index = 1 + text::to_type< std::size_t >(
345*6b3a42afSjmmv                 _templates.get_variable(loop.iterator));
346*6b3a42afSjmmv 
347*6b3a42afSjmmv             if (next_index < _templates.get_vector(loop.vector).size()) {
348*6b3a42afSjmmv                 _templates.add_variable(loop.iterator, F("%s") % next_index);
349*6b3a42afSjmmv                 input.seekg(loop.position);
350*6b3a42afSjmmv             } else {
351*6b3a42afSjmmv                 _loop_level--;
352*6b3a42afSjmmv                 _loops.pop();
353*6b3a42afSjmmv                 _templates.remove_variable(loop.iterator);
354*6b3a42afSjmmv             }
355*6b3a42afSjmmv         } break;
356*6b3a42afSjmmv 
357*6b3a42afSjmmv         case statement_def::type_if: {
358*6b3a42afSjmmv             _if_level++;
359*6b3a42afSjmmv             const std::string value = _templates.evaluate(
360*6b3a42afSjmmv                 statement.arguments[0]);
361*6b3a42afSjmmv             if (value.empty() || value == "0" || value == "false") {
362*6b3a42afSjmmv                 _exit_if_level = _if_level;
363*6b3a42afSjmmv                 _skip.push(true);
364*6b3a42afSjmmv             } else {
365*6b3a42afSjmmv                 _skip.push(false);
366*6b3a42afSjmmv             }
367*6b3a42afSjmmv         } break;
368*6b3a42afSjmmv 
369*6b3a42afSjmmv         case statement_def::type_loop: {
370*6b3a42afSjmmv             _loop_level++;
371*6b3a42afSjmmv 
372*6b3a42afSjmmv             const loop_def loop(statement.arguments[0], statement.arguments[1],
373*6b3a42afSjmmv                                 input.tellg());
374*6b3a42afSjmmv             if (_templates.get_vector(loop.vector).empty()) {
375*6b3a42afSjmmv                 _exit_loop_level = _loop_level;
376*6b3a42afSjmmv                 _skip.push(true);
377*6b3a42afSjmmv             } else {
378*6b3a42afSjmmv                 _templates.add_variable(loop.iterator, "0");
379*6b3a42afSjmmv                 _loops.push(loop);
380*6b3a42afSjmmv                 _skip.push(false);
381*6b3a42afSjmmv             }
382*6b3a42afSjmmv         } break;
383*6b3a42afSjmmv         }
384*6b3a42afSjmmv     }
385*6b3a42afSjmmv 
386*6b3a42afSjmmv     /// Processes a line from the input when in skip mode.
387*6b3a42afSjmmv     ///
388*6b3a42afSjmmv     /// \param line The line to be processed.
389*6b3a42afSjmmv     ///
390*6b3a42afSjmmv     /// \throw text::syntax_error If the input is not valid.
391*6b3a42afSjmmv     void
handle_skip(const std::string & line)392*6b3a42afSjmmv     handle_skip(const std::string& line)
393*6b3a42afSjmmv     {
394*6b3a42afSjmmv         PRE(_skip.top());
395*6b3a42afSjmmv 
396*6b3a42afSjmmv         if (!is_statement(line))
397*6b3a42afSjmmv             return;
398*6b3a42afSjmmv 
399*6b3a42afSjmmv         const statement_def statement = parse_statement(line);
400*6b3a42afSjmmv         switch (statement.type) {
401*6b3a42afSjmmv         case statement_def::type_else:
402*6b3a42afSjmmv             if (_exit_if_level == _if_level)
403*6b3a42afSjmmv                 _skip.top() = !_skip.top();
404*6b3a42afSjmmv             break;
405*6b3a42afSjmmv 
406*6b3a42afSjmmv         case statement_def::type_endif:
407*6b3a42afSjmmv             INV(_if_level >= _exit_if_level);
408*6b3a42afSjmmv             if (_if_level == _exit_if_level)
409*6b3a42afSjmmv                 _skip.top() = false;
410*6b3a42afSjmmv             _if_level--;
411*6b3a42afSjmmv             _skip.pop();
412*6b3a42afSjmmv             break;
413*6b3a42afSjmmv 
414*6b3a42afSjmmv         case statement_def::type_endloop:
415*6b3a42afSjmmv             INV(_loop_level >= _exit_loop_level);
416*6b3a42afSjmmv             if (_loop_level == _exit_loop_level)
417*6b3a42afSjmmv                 _skip.top() = false;
418*6b3a42afSjmmv             _loop_level--;
419*6b3a42afSjmmv             _skip.pop();
420*6b3a42afSjmmv             break;
421*6b3a42afSjmmv 
422*6b3a42afSjmmv         case statement_def::type_if:
423*6b3a42afSjmmv             _if_level++;
424*6b3a42afSjmmv             _skip.push(true);
425*6b3a42afSjmmv             break;
426*6b3a42afSjmmv 
427*6b3a42afSjmmv         case statement_def::type_loop:
428*6b3a42afSjmmv             _loop_level++;
429*6b3a42afSjmmv             _skip.push(true);
430*6b3a42afSjmmv             break;
431*6b3a42afSjmmv 
432*6b3a42afSjmmv         default:
433*6b3a42afSjmmv             break;
434*6b3a42afSjmmv         }
435*6b3a42afSjmmv     }
436*6b3a42afSjmmv 
437*6b3a42afSjmmv     /// Evaluates expressions on a given input line.
438*6b3a42afSjmmv     ///
439*6b3a42afSjmmv     /// An expression is surrounded by _delimiter on both sides.  We scan the
440*6b3a42afSjmmv     /// string from left to right finding any expressions that may appear, yank
441*6b3a42afSjmmv     /// them out and call templates_def::evaluate() to get their value.
442*6b3a42afSjmmv     ///
443*6b3a42afSjmmv     /// Lonely or unbalanced appearances of _delimiter on the input line are
444*6b3a42afSjmmv     /// not considered an error, given that the user may actually want to supply
445*6b3a42afSjmmv     /// that character sequence without being interpreted as a template.
446*6b3a42afSjmmv     ///
447*6b3a42afSjmmv     /// \param in_line The input line from which to evaluate expressions.
448*6b3a42afSjmmv     ///
449*6b3a42afSjmmv     /// \return The evaluated line.
450*6b3a42afSjmmv     ///
451*6b3a42afSjmmv     /// \throw text::syntax_error If the expressions in the line are malformed.
452*6b3a42afSjmmv     std::string
evaluate(const std::string & in_line)453*6b3a42afSjmmv     evaluate(const std::string& in_line)
454*6b3a42afSjmmv     {
455*6b3a42afSjmmv         std::string out_line;
456*6b3a42afSjmmv 
457*6b3a42afSjmmv         std::string::size_type last_pos = 0;
458*6b3a42afSjmmv         while (last_pos != std::string::npos) {
459*6b3a42afSjmmv             const std::string::size_type open_pos = in_line.find(
460*6b3a42afSjmmv                 _delimiter, last_pos);
461*6b3a42afSjmmv             if (open_pos == std::string::npos) {
462*6b3a42afSjmmv                 out_line += in_line.substr(last_pos);
463*6b3a42afSjmmv                 last_pos = std::string::npos;
464*6b3a42afSjmmv             } else {
465*6b3a42afSjmmv                 const std::string::size_type close_pos = in_line.find(
466*6b3a42afSjmmv                     _delimiter, open_pos + _delimiter.length());
467*6b3a42afSjmmv                 if (close_pos == std::string::npos) {
468*6b3a42afSjmmv                     out_line += in_line.substr(last_pos);
469*6b3a42afSjmmv                     last_pos = std::string::npos;
470*6b3a42afSjmmv                 } else {
471*6b3a42afSjmmv                     out_line += in_line.substr(last_pos, open_pos - last_pos);
472*6b3a42afSjmmv                     out_line += _templates.evaluate(in_line.substr(
473*6b3a42afSjmmv                         open_pos + _delimiter.length(),
474*6b3a42afSjmmv                         close_pos - open_pos - _delimiter.length()));
475*6b3a42afSjmmv                     last_pos = close_pos + _delimiter.length();
476*6b3a42afSjmmv                 }
477*6b3a42afSjmmv             }
478*6b3a42afSjmmv         }
479*6b3a42afSjmmv 
480*6b3a42afSjmmv         return out_line;
481*6b3a42afSjmmv     }
482*6b3a42afSjmmv 
483*6b3a42afSjmmv public:
484*6b3a42afSjmmv     /// Constructs a new template parser.
485*6b3a42afSjmmv     ///
486*6b3a42afSjmmv     /// \param templates_ The templates to apply to the processed file.
487*6b3a42afSjmmv     /// \param prefix_ The prefix that identifies lines as statements.
488*6b3a42afSjmmv     /// \param delimiter_ Delimiter to surround a variable instantiation.
templates_parser(const text::templates_def & templates_,const std::string & prefix_,const std::string & delimiter_)489*6b3a42afSjmmv     templates_parser(const text::templates_def& templates_,
490*6b3a42afSjmmv                      const std::string& prefix_,
491*6b3a42afSjmmv                      const std::string& delimiter_) :
492*6b3a42afSjmmv         _templates(templates_),
493*6b3a42afSjmmv         _prefix(prefix_),
494*6b3a42afSjmmv         _delimiter(delimiter_),
495*6b3a42afSjmmv         _if_level(0),
496*6b3a42afSjmmv         _exit_if_level(0),
497*6b3a42afSjmmv         _loop_level(0),
498*6b3a42afSjmmv         _exit_loop_level(0)
499*6b3a42afSjmmv     {
500*6b3a42afSjmmv     }
501*6b3a42afSjmmv 
502*6b3a42afSjmmv     /// Applies the templates to a given input.
503*6b3a42afSjmmv     ///
504*6b3a42afSjmmv     /// \param input The stream to which to apply the templates.
505*6b3a42afSjmmv     /// \param output The stream into which to write the results.
506*6b3a42afSjmmv     ///
507*6b3a42afSjmmv     /// \throw text::syntax_error If the input is not valid.  Note that the
508*6b3a42afSjmmv     ///     is not guaranteed to be unmodified on exit if an error is
509*6b3a42afSjmmv     ///     encountered.
510*6b3a42afSjmmv     void
instantiate(std::istream & input,std::ostream & output)511*6b3a42afSjmmv     instantiate(std::istream& input, std::ostream& output)
512*6b3a42afSjmmv     {
513*6b3a42afSjmmv         std::string line;
514*6b3a42afSjmmv         while (std::getline(input, line).good()) {
515*6b3a42afSjmmv             if (!_skip.empty() && _skip.top())
516*6b3a42afSjmmv                 handle_skip(line);
517*6b3a42afSjmmv             else
518*6b3a42afSjmmv                 handle_normal(evaluate(line), input, output);
519*6b3a42afSjmmv         }
520*6b3a42afSjmmv     }
521*6b3a42afSjmmv };
522*6b3a42afSjmmv 
523*6b3a42afSjmmv 
524*6b3a42afSjmmv }  // anonymous namespace
525*6b3a42afSjmmv 
526*6b3a42afSjmmv 
527*6b3a42afSjmmv /// Constructs an empty templates definition.
templates_def(void)528*6b3a42afSjmmv text::templates_def::templates_def(void)
529*6b3a42afSjmmv {
530*6b3a42afSjmmv }
531*6b3a42afSjmmv 
532*6b3a42afSjmmv 
533*6b3a42afSjmmv /// Sets a string variable in the templates.
534*6b3a42afSjmmv ///
535*6b3a42afSjmmv /// If the variable already exists, its value is replaced.  This behavior is
536*6b3a42afSjmmv /// required to implement iterators, but client code should really not be
537*6b3a42afSjmmv /// redefining variables.
538*6b3a42afSjmmv ///
539*6b3a42afSjmmv /// \pre The variable must not already exist as a vector.
540*6b3a42afSjmmv ///
541*6b3a42afSjmmv /// \param name The name of the variable to set.
542*6b3a42afSjmmv /// \param value The value to set the given variable to.
543*6b3a42afSjmmv void
add_variable(const std::string & name,const std::string & value)544*6b3a42afSjmmv text::templates_def::add_variable(const std::string& name,
545*6b3a42afSjmmv                                   const std::string& value)
546*6b3a42afSjmmv {
547*6b3a42afSjmmv     PRE(_vectors.find(name) == _vectors.end());
548*6b3a42afSjmmv     _variables[name] = value;
549*6b3a42afSjmmv }
550*6b3a42afSjmmv 
551*6b3a42afSjmmv 
552*6b3a42afSjmmv /// Unsets a string variable from the templates.
553*6b3a42afSjmmv ///
554*6b3a42afSjmmv /// Client code has no reason to use this.  This is only required to implement
555*6b3a42afSjmmv /// proper scoping of loop iterators.
556*6b3a42afSjmmv ///
557*6b3a42afSjmmv /// \pre The variable must exist.
558*6b3a42afSjmmv ///
559*6b3a42afSjmmv /// \param name The name of the variable to remove from the templates.
560*6b3a42afSjmmv void
remove_variable(const std::string & name)561*6b3a42afSjmmv text::templates_def::remove_variable(const std::string& name)
562*6b3a42afSjmmv {
563*6b3a42afSjmmv     PRE(_variables.find(name) != _variables.end());
564*6b3a42afSjmmv     _variables.erase(_variables.find(name));
565*6b3a42afSjmmv }
566*6b3a42afSjmmv 
567*6b3a42afSjmmv 
568*6b3a42afSjmmv /// Creates a new vector in the templates.
569*6b3a42afSjmmv ///
570*6b3a42afSjmmv /// If the vector already exists, it is cleared.  Client code should really not
571*6b3a42afSjmmv /// be redefining variables.
572*6b3a42afSjmmv ///
573*6b3a42afSjmmv /// \pre The vector must not already exist as a variable.
574*6b3a42afSjmmv ///
575*6b3a42afSjmmv /// \param name The name of the vector to set.
576*6b3a42afSjmmv void
add_vector(const std::string & name)577*6b3a42afSjmmv text::templates_def::add_vector(const std::string& name)
578*6b3a42afSjmmv {
579*6b3a42afSjmmv     PRE(_variables.find(name) == _variables.end());
580*6b3a42afSjmmv     _vectors[name] = strings_vector();
581*6b3a42afSjmmv }
582*6b3a42afSjmmv 
583*6b3a42afSjmmv 
584*6b3a42afSjmmv /// Adds a value to an existing vector in the templates.
585*6b3a42afSjmmv ///
586*6b3a42afSjmmv /// \pre name The vector must exist.
587*6b3a42afSjmmv ///
588*6b3a42afSjmmv /// \param name The name of the vector to append the value to.
589*6b3a42afSjmmv /// \param value The textual value to append to the vector.
590*6b3a42afSjmmv void
add_to_vector(const std::string & name,const std::string & value)591*6b3a42afSjmmv text::templates_def::add_to_vector(const std::string& name,
592*6b3a42afSjmmv                                    const std::string& value)
593*6b3a42afSjmmv {
594*6b3a42afSjmmv     PRE(_variables.find(name) == _variables.end());
595*6b3a42afSjmmv     PRE(_vectors.find(name) != _vectors.end());
596*6b3a42afSjmmv     _vectors[name].push_back(value);
597*6b3a42afSjmmv }
598*6b3a42afSjmmv 
599*6b3a42afSjmmv 
600*6b3a42afSjmmv /// Checks whether a given identifier exists as a variable or a vector.
601*6b3a42afSjmmv ///
602*6b3a42afSjmmv /// This is used to implement the evaluation of conditions in if clauses.
603*6b3a42afSjmmv ///
604*6b3a42afSjmmv /// \param name The name of the variable or vector.
605*6b3a42afSjmmv ///
606*6b3a42afSjmmv /// \return True if the given name exists as a variable or a vector; false
607*6b3a42afSjmmv /// otherwise.
608*6b3a42afSjmmv bool
exists(const std::string & name) const609*6b3a42afSjmmv text::templates_def::exists(const std::string& name) const
610*6b3a42afSjmmv {
611*6b3a42afSjmmv     return (_variables.find(name) != _variables.end() ||
612*6b3a42afSjmmv             _vectors.find(name) != _vectors.end());
613*6b3a42afSjmmv }
614*6b3a42afSjmmv 
615*6b3a42afSjmmv 
616*6b3a42afSjmmv /// Gets the value of a variable.
617*6b3a42afSjmmv ///
618*6b3a42afSjmmv /// \param name The name of the variable.
619*6b3a42afSjmmv ///
620*6b3a42afSjmmv /// \return The value of the requested variable.
621*6b3a42afSjmmv ///
622*6b3a42afSjmmv /// \throw text::syntax_error If the variable does not exist.
623*6b3a42afSjmmv const std::string&
get_variable(const std::string & name) const624*6b3a42afSjmmv text::templates_def::get_variable(const std::string& name) const
625*6b3a42afSjmmv {
626*6b3a42afSjmmv     const variables_map::const_iterator iter = _variables.find(name);
627*6b3a42afSjmmv     if (iter == _variables.end())
628*6b3a42afSjmmv         throw text::syntax_error(F("Unknown variable '%s'") % name);
629*6b3a42afSjmmv     return (*iter).second;
630*6b3a42afSjmmv }
631*6b3a42afSjmmv 
632*6b3a42afSjmmv 
633*6b3a42afSjmmv /// Gets a vector.
634*6b3a42afSjmmv ///
635*6b3a42afSjmmv /// \param name The name of the vector.
636*6b3a42afSjmmv ///
637*6b3a42afSjmmv /// \return A reference to the requested vector.
638*6b3a42afSjmmv ///
639*6b3a42afSjmmv /// \throw text::syntax_error If the vector does not exist.
640*6b3a42afSjmmv const text::templates_def::strings_vector&
get_vector(const std::string & name) const641*6b3a42afSjmmv text::templates_def::get_vector(const std::string& name) const
642*6b3a42afSjmmv {
643*6b3a42afSjmmv     const vectors_map::const_iterator iter = _vectors.find(name);
644*6b3a42afSjmmv     if (iter == _vectors.end())
645*6b3a42afSjmmv         throw text::syntax_error(F("Unknown vector '%s'") % name);
646*6b3a42afSjmmv     return (*iter).second;
647*6b3a42afSjmmv }
648*6b3a42afSjmmv 
649*6b3a42afSjmmv 
650*6b3a42afSjmmv /// Indexes a vector and gets the value.
651*6b3a42afSjmmv ///
652*6b3a42afSjmmv /// \param name The name of the vector to index.
653*6b3a42afSjmmv /// \param index_name The name of a variable representing the index to use.
654*6b3a42afSjmmv ///     This must be convertible to a natural.
655*6b3a42afSjmmv ///
656*6b3a42afSjmmv /// \return The value of the vector at the given index.
657*6b3a42afSjmmv ///
658*6b3a42afSjmmv /// \throw text::syntax_error If the vector does not existor if the index is out
659*6b3a42afSjmmv ///     of range.
660*6b3a42afSjmmv const std::string&
get_vector(const std::string & name,const std::string & index_name) const661*6b3a42afSjmmv text::templates_def::get_vector(const std::string& name,
662*6b3a42afSjmmv                                 const std::string& index_name) const
663*6b3a42afSjmmv {
664*6b3a42afSjmmv     const strings_vector& vector = get_vector(name);
665*6b3a42afSjmmv     const std::string& index_str = get_variable(index_name);
666*6b3a42afSjmmv 
667*6b3a42afSjmmv     std::size_t index;
668*6b3a42afSjmmv     try {
669*6b3a42afSjmmv         index = text::to_type< std::size_t >(index_str);
670*6b3a42afSjmmv     } catch (const text::syntax_error& e) {
671*6b3a42afSjmmv         throw text::syntax_error(F("Index '%s' not an integer, value '%s'") %
672*6b3a42afSjmmv                                  index_name % index_str);
673*6b3a42afSjmmv     }
674*6b3a42afSjmmv     if (index >= vector.size())
675*6b3a42afSjmmv         throw text::syntax_error(F("Index '%s' out of range at position '%s'") %
676*6b3a42afSjmmv                                  index_name % index);
677*6b3a42afSjmmv 
678*6b3a42afSjmmv     return vector[index];
679*6b3a42afSjmmv }
680*6b3a42afSjmmv 
681*6b3a42afSjmmv 
682*6b3a42afSjmmv /// Evaluates a expression using these templates.
683*6b3a42afSjmmv ///
684*6b3a42afSjmmv /// An expression is a query on the current templates to fetch a particular
685*6b3a42afSjmmv /// value.  The value is always returned as a string, as this is how templates
686*6b3a42afSjmmv /// are internally stored.
687*6b3a42afSjmmv ///
688*6b3a42afSjmmv /// \param expression The expression to evaluate.  This should not include any
689*6b3a42afSjmmv ///     of the delimiters used in the user input, as otherwise the expression
690*6b3a42afSjmmv ///     will not be evaluated properly.
691*6b3a42afSjmmv ///
692*6b3a42afSjmmv /// \return The result of the expression evaluation as a string.
693*6b3a42afSjmmv ///
694*6b3a42afSjmmv /// \throw text::syntax_error If there is any problem while evaluating the
695*6b3a42afSjmmv ///     expression.
696*6b3a42afSjmmv std::string
evaluate(const std::string & expression) const697*6b3a42afSjmmv text::templates_def::evaluate(const std::string& expression) const
698*6b3a42afSjmmv {
699*6b3a42afSjmmv     const std::string::size_type paren_open = expression.find('(');
700*6b3a42afSjmmv     if (paren_open == std::string::npos) {
701*6b3a42afSjmmv         return get_variable(expression);
702*6b3a42afSjmmv     } else {
703*6b3a42afSjmmv         const std::string::size_type paren_close = expression.find(
704*6b3a42afSjmmv             ')', paren_open);
705*6b3a42afSjmmv         if (paren_close == std::string::npos)
706*6b3a42afSjmmv             throw text::syntax_error(F("Expected ')' in expression '%s')") %
707*6b3a42afSjmmv                                      expression);
708*6b3a42afSjmmv         if (paren_close != expression.length() - 1)
709*6b3a42afSjmmv             throw text::syntax_error(F("Unexpected text found after ')' in "
710*6b3a42afSjmmv                                        "expression '%s'") % expression);
711*6b3a42afSjmmv 
712*6b3a42afSjmmv         const std::string arg0 = expression.substr(0, paren_open);
713*6b3a42afSjmmv         const std::string arg1 = expression.substr(
714*6b3a42afSjmmv             paren_open + 1, paren_close - paren_open - 1);
715*6b3a42afSjmmv         if (arg0 == "defined") {
716*6b3a42afSjmmv             return exists(arg1) ? "true" : "false";
717*6b3a42afSjmmv         } else if (arg0 == "length") {
718*6b3a42afSjmmv             return F("%s") % get_vector(arg1).size();
719*6b3a42afSjmmv         } else {
720*6b3a42afSjmmv             return get_vector(arg0, arg1);
721*6b3a42afSjmmv         }
722*6b3a42afSjmmv     }
723*6b3a42afSjmmv }
724*6b3a42afSjmmv 
725*6b3a42afSjmmv 
726*6b3a42afSjmmv /// Applies a set of templates to an input stream.
727*6b3a42afSjmmv ///
728*6b3a42afSjmmv /// \param templates The templates to use.
729*6b3a42afSjmmv /// \param input The input to process.
730*6b3a42afSjmmv /// \param output The stream to which to write the processed text.
731*6b3a42afSjmmv ///
732*6b3a42afSjmmv /// \throw text::syntax_error If there is any problem processing the input.
733*6b3a42afSjmmv void
instantiate(const templates_def & templates,std::istream & input,std::ostream & output)734*6b3a42afSjmmv text::instantiate(const templates_def& templates,
735*6b3a42afSjmmv                   std::istream& input, std::ostream& output)
736*6b3a42afSjmmv {
737*6b3a42afSjmmv     templates_parser parser(templates, "%", "%%");
738*6b3a42afSjmmv     parser.instantiate(input, output);
739*6b3a42afSjmmv }
740*6b3a42afSjmmv 
741*6b3a42afSjmmv 
742*6b3a42afSjmmv /// Applies a set of templates to an input file and writes an output file.
743*6b3a42afSjmmv ///
744*6b3a42afSjmmv /// \param templates The templates to use.
745*6b3a42afSjmmv /// \param input_file The path to the input to process.
746*6b3a42afSjmmv /// \param output_file The path to the file into which to write the output.
747*6b3a42afSjmmv ///
748*6b3a42afSjmmv /// \throw text::error If the input or output files cannot be opened.
749*6b3a42afSjmmv /// \throw text::syntax_error If there is any problem processing the input.
750*6b3a42afSjmmv void
instantiate(const templates_def & templates,const fs::path & input_file,const fs::path & output_file)751*6b3a42afSjmmv text::instantiate(const templates_def& templates,
752*6b3a42afSjmmv                   const fs::path& input_file, const fs::path& output_file)
753*6b3a42afSjmmv {
754*6b3a42afSjmmv     std::ifstream input(input_file.c_str());
755*6b3a42afSjmmv     if (!input)
756*6b3a42afSjmmv         throw text::error(F("Failed to open %s for read") % input_file);
757*6b3a42afSjmmv 
758*6b3a42afSjmmv     std::ofstream output(output_file.c_str());
759*6b3a42afSjmmv     if (!output)
760*6b3a42afSjmmv         throw text::error(F("Failed to open %s for write") % output_file);
761*6b3a42afSjmmv 
762*6b3a42afSjmmv     instantiate(templates, input, output);
763*6b3a42afSjmmv }
764