Branch data Line data Source code
1 : : // *****************************************************************************
2 : : /*!
3 : : \file src/Control/CommonGrammar.hpp
4 : : \copyright 2012-2015 J. Bakosi,
5 : : 2016-2018 Los Alamos National Security, LLC.,
6 : : 2019-2021 Triad National Security, LLC.
7 : : All rights reserved. See the LICENSE file for details.
8 : : \brief Generic, low-level grammar, re-used by specific grammars
9 : : \details Generic, low-level grammar. We use the Parsing Expression Grammar
10 : : Template Library (PEGTL) to create the grammar and the associated parser.
11 : : */
12 : : // *****************************************************************************
13 : : #ifndef CommonGrammar_h
14 : : #define CommonGrammar_h
15 : :
16 : : #include <type_traits>
17 : : #include <sstream>
18 : :
19 : : #include <brigand/algorithms/for_each.hpp>
20 : : #include <brigand/functions/logical/or.hpp>
21 : : #include <brigand/sequences/has_key.hpp>
22 : :
23 : : #include "If.hpp"
24 : : #include "Exception.hpp"
25 : : #include "Tags.hpp"
26 : : #include "StatCtr.hpp"
27 : : #include "Options/TxtFloatFormat.hpp"
28 : : #include "Options/Error.hpp"
29 : :
30 : : namespace tk {
31 : : //! Toolkit general purpose grammar definition
32 : : namespace grm {
33 : :
34 : : using namespace tao;
35 : :
36 : : using ncomp_t = kw::ncomp::info::expect::type;
37 : :
38 : : //! Parser's printer: this should be defined once per library in global-scope
39 : : //! (still in namespace, of course) by a parser. It is defined in
40 : : //! Control/[executable]/CmdLine/Parser.C, since every executable has at least
41 : : //! a command line parser.
42 : : extern Print g_print;
43 : :
44 : : // Common auxiliary functions (reused by multiple grammars)
45 : :
46 : : //! C-style enum indicating warning or error (used as template argument)
47 : : enum MsgType { ERROR=0, WARNING };
48 : :
49 : : //! Parser error types
50 : : enum class MsgKey : uint8_t {
51 : : KEYWORD, //!< Unknown keyword
52 : : MOMENT, //!< Unknown Term in a Moment
53 : : QUOTED, //!< String must be double-quoted
54 : : LIST, //!< Unknown value in list
55 : : ALIAS, //!< Alias keyword too long
56 : : MISSING, //!< Required field missing
57 : : PREMATURE, //!< Premature end of line
58 : : UNSUPPORTED, //!< Option not supported
59 : : NOOPTION, //!< Option does not exist
60 : : NOINIT, //!< No (or too many) initialization policy selected
61 : : NOPROBLEM, //!< No test problem type selected
62 : : NOCOEFF, //!< No coefficients policy selected
63 : : NOTSELECTED, //!< Option not selected upstream
64 : : NOSOLVE, //!< Dependent variable to solve for has not been spec'd
65 : : NOTALPHA, //!< Variable must be alphanumeric
66 : : POINTEXISTS, //!< Point identifier already defined
67 : : BADPRECISION, //!< Floating point precision specification incorrect
68 : : BOUNDS, //!< Specified value out of bounds
69 : : PRECISIONBOUNDS, //!< Floating point precision spec out of bounds
70 : : UNFINISHED, //!< Unfinished block
71 : : CHARMARG, //!< Argument inteded for the Charm++ runtime system
72 : : OPTIONAL }; //!< Message key used to indicate of something optional
73 : :
74 : : //! Associate parser errors to error messages
75 : : static const std::map< MsgKey, std::string > message{
76 : : { MsgKey::KEYWORD, "Unknown keyword or keyword unrecognized in this "
77 : : "block." },
78 : : { MsgKey::MOMENT, "Unknown term in moment." },
79 : : { MsgKey::QUOTED, "Must be double-quoted." },
80 : : { MsgKey::LIST, "Unknown value in list." },
81 : : { MsgKey::ALIAS, "Alias keyword too long. Use either a full-length keyword "
82 : : "with double-hyphens, e.g., --keyword, or its alias, a single character, "
83 : : "with a single hyphen, e.g., -k." },
84 : : { MsgKey::MISSING, "Required field missing." },
85 : : { MsgKey::PREMATURE, "Premature end of line." },
86 : : { MsgKey::UNSUPPORTED, "Option not supported." },
87 : : { MsgKey::NOOPTION, "Option does not exist." },
88 : : { MsgKey::NOTSELECTED, "Option is not among the selected ones. The keyword "
89 : : "here is appropriate, but in order to use this keyword in this context, "
90 : : "the option must be selected upstream." },
91 : : { MsgKey::NOTALPHA, "Variable not alphanumeric." },
92 : : { MsgKey::NOSOLVE, "Dependent variable to solve for not specified within "
93 : : "the block preceding this position. This is mandatory for the preceding "
94 : : "block. Use the keyword 'solve' to specify the type of the dependent "
95 : : "variable to solve for." },
96 : : { MsgKey::NOINIT, "No (or too many) initialization policy (or policies) "
97 : : "has been specified within the block preceding this position. An "
98 : : "initialization policy (and only one) is mandatory for the preceding "
99 : : "block. Use the keyword 'init' to specify an initialization policy." },
100 : : { MsgKey::NOPROBLEM, "No test problem has been specified within the "
101 : : "block preceding this position. This is mandatory for the preceding "
102 : : "block. Use the keyword 'problem' to specify a test problem." },
103 : : { MsgKey::NOCOEFF, "No coefficients policy has been specified within the "
104 : : "block preceding this position. This is mandatory for the preceding "
105 : : "block. Use the keyword 'coeff' to specify an coefficients policy." },
106 : : { MsgKey::POINTEXISTS, "Point already exists. Point identifiers must be "
107 : : "unique."},
108 : : { MsgKey::BADPRECISION, "Precision specification invalid. It should be a "
109 : : "positive integer or the word \'max\', selecting the maximum number of "
110 : : "digits for the underyling floating point type."},
111 : : { MsgKey::BOUNDS, "Specified value out of bounds. For the bounds on a "
112 : : "keyword, run '<executable> -H <keyword>'."},
113 : : { MsgKey::PRECISIONBOUNDS, "Precision specification out of bounds. It "
114 : : "should be a positive integer between 1 and the maximum number of digits "
115 : : "for the underyling floating point type on the machine. (Set \'max\' for "
116 : : "the maximum.)"},
117 : : { MsgKey::UNFINISHED, "Block started but not finished by the 'end' "
118 : : "keyword." },
119 : : { MsgKey::CHARMARG, "Arguments starting with '+' are assumed to be inteded "
120 : : "for the Charm++ runtime system. Did you forget to prefix the command "
121 : : "line with charmrun? If this warning persists even after running with "
122 : : "charmrun, then Charm++ does not understand it either. See the Charm++ "
123 : : "manual at http://charm.cs.illinois.edu/manuals/html/charm++/"
124 : : "manual.html." },
125 : : { MsgKey::OPTIONAL, "This is not really an error message and thus it "
126 : : "should not be used as one. But its key can be used to indicate "
127 : : "something optional (which is not an error), which in some situations is "
128 : : "not optional (which is an error)." }
129 : : };
130 : :
131 : : //! Parser error and warning message handler.
132 : : //! \details This function is used to associated and dispatch an error or a
133 : : //! warning during parsing. After finding the error message corresponding to
134 : : //! a key, it pushes back the message to a std::vector of std::string, which
135 : : //! then will be diagnosed later by tk::FileParser::diagnostics. The
136 : : //! template arguments define (1) the grammar stack (Stack, a tagged tuple)
137 : : //! to operate on, (2) the message type (error or warning), and (3) the
138 : : //! message key used to look up the error message associated with the key.
139 : : //! \param[in,out] stack Grammar stack (a tagged tuple) to operate on
140 : : //! \param[in] in Last parsed PEGTL input token (can be empty, depending on
141 : : //! what context this function gets called.
142 : : template< class Stack, MsgType type, MsgKey key, class Input >
143 : 0 : static void Message( Stack& stack, const Input& in ) {
144 : : const auto& msg = message.find(key);
145 [ - - ]: 0 : if (msg != message.end()) {
146 : 0 : std::stringstream ss;
147 [ - - ][ - - ]: 0 : const std::string typestr( type == MsgType::ERROR ? "Error" : "Warning" );
148 : : auto pos = in.position();
149 [ - - ]: 0 : if (!in.empty()) {
150 : 0 : ss << typestr << " while parsing '" << in.string() << "' at "
151 [ - - ][ - - ]: 0 : << pos.line << ',' << pos.byte_in_line << ". " << msg->second;
152 : : } else {
153 [ - - ]: 0 : ss << typestr << " while parsing at " << pos.line << ','
154 [ - - ]: 0 : << pos.byte_in_line << ". " << msg->second;
155 : : }
156 [ - - ][ - - ]: 0 : stack.template get< tag::error >().push_back( ss.str() );
157 : : } else {
158 [ - - ][ - - ]: 0 : stack.template get< tag::error >().push_back(
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ]
159 : : std::string("Unknown parser ") +
160 : : (type == MsgType::ERROR ? "error" : "warning" ) +
161 : : " with no location information." );
162 : : }
163 : 0 : }
164 : :
165 : : // Common PEGTL actions (PEGTL actions reused by multiple grammars)
166 : :
167 : : //! PEGTL action base: do nothing by default
168 : : //! \details This base is specialized to different actions duing parsing.
169 : : template< typename Rule >
170 : : struct action : pegtl::nothing< Rule > {};
171 : :
172 : : //! Helper for calling action::apply for multiple actions
173 : : template< typename... As >
174 : : struct call {
175 : : template< typename Input, typename State >
176 : : static void apply( const Input& in, State& state ) {
177 : : using swallow = bool[];
178 [ + - ][ - - ]: 509 : (void)swallow{ ( action< As >::apply( in, state ), true )..., true };
[ + - ][ - - ]
[ + - ][ + - ]
[ - - ][ + - ]
[ - - ][ + - ]
179 : : }
180 : : };
181 : :
182 : : //! Rule used to trigger action(s) for a rule
183 : : template< class rule, class... actions >
184 : : struct act : rule {};
185 : :
186 : : //! \details Specialization of action for act< rule, actions... >
187 : : template< class rule, class... actions >
188 : : struct action< act< rule, actions... > > : call< actions... > {};
189 : :
190 : : //! Rule used to trigger action
191 : : template< MsgType, MsgKey > struct msg : pegtl::success {};
192 : : //! Error message dispatch
193 : : //! \details This struct and its apply function are used to dispatch a message
194 : : //! (e.g., error, waring) from the parser. It is simply an interface to
195 : : //! Message. See This struct is practically used as a functor, i.e., a
196 : : //! struct or class that defines the function call operator, but instead the
197 : : //! function call operator, PEGTL uses the apply() member function for the
198 : : //! actions. Thus this struct can be passed to, i.e., specialize a template,
199 : : //! such as tk::grm::unknown, injecting in it the desired behavior.
200 : : template< MsgType type, MsgKey key >
201 : : struct action< msg< type, key > > {
202 : : template< typename Input, typename Stack >
203 : : static void apply( const Input& in, Stack& stack ) {
204 [ - - ][ - - ]: 0 : Message< Stack, type, key >( stack, in );
[ - - ][ - - ]
[ - - ]
205 : : }
206 : : };
207 : :
208 : : //! Rule used to trigger action
209 : : template< typename tag, typename... tags > struct Store : pegtl::success {};
210 : : //! Put value in state at position given by tags with conversion
211 : : //! \details This struct and its apply function are used as a functor-like
212 : : //! wrapper for calling the store member function of the underlying grammar
213 : : //! stack, tk::Control::store.
214 : : template< typename tag, typename... tags >
215 : : struct action< Store< tag, tags... > > {
216 : : template< typename Input, typename Stack >
217 : 509 : static void apply( const Input& in, Stack& stack ) {
218 [ + - ]: 509 : if (!in.string().empty())
219 : 1018 : stack.template store< tag, tags... >( in.string() );
220 : : else
221 : 0 : Message< Stack, ERROR, MsgKey::MISSING >( stack, in );
222 : 509 : }
223 : : };
224 : :
225 : : //! Rule used to trigger action
226 : : template< typename... tags >
227 : : struct Invert_switch : pegtl::success {};
228 : : //! Invert bool in switch at position given by tags
229 : : //! \details This struct and its apply function are used as a functor-like
230 : : //! wrapper for setting a boolean value to true in the underlying grammar
231 : : //! stack via the member function tk::Control::set.
232 : : template< typename... tags >
233 : : struct action< Invert_switch< tags... > > {
234 : : template< typename Input, typename Stack >
235 : : static void apply( const Input&, Stack& stack ) {
236 : 448 : stack.template get< tags... >() = !stack.template get< tags... >();
237 : : }
238 : : };
239 : :
240 : : //! Rule used to trigger action
241 : : struct helpkw : pegtl::success {};
242 : : //! \brief Find keyword among all keywords and if found, store the keyword
243 : : //! and its info on which help was requested behind tag::helpkw in Stack
244 : : //! \details This struct and its apply function are used as a functor-like
245 : : //! wrapper to search for a keyword in the pool of registered keywords
246 : : //! recognized by a grammar and store the keyword and its info on which
247 : : //! help was requested behind tag::helpkw. Note that this functor assumes
248 : : //! a specific location for the std::maps of the command-line and control
249 : : //! file keywords pools (behind tag::cmdinfo and tag::ctrinfo,
250 : : //! respectively), and for the keyword and its info on which help was
251 : : //! requested (behind tag::helpkw). This is the structure of CmdLine
252 : : //! objects, thus this functor should be called from command line parsers.
253 : : template<>
254 : : struct action< helpkw > {
255 : : template< typename Input, typename Stack >
256 : 0 : static void apply( const Input& in, Stack& stack ) {
257 : : const auto& cmdinfo = stack.template get< tag::cmdinfo >();
258 : : const auto& ctrinfo = stack.template get< tag::ctrinfo >();
259 [ - - ]: 0 : auto it = cmdinfo.find( in.string() );
260 [ - - ]: 0 : if (it != cmdinfo.end()) {
261 : : // store keyword and its info on which help was requested
262 : 0 : stack.template get< tag::helpkw >() = { it->first, it->second, true };
263 : : } else {
264 [ - - ]: 0 : it = ctrinfo.find( in.string() );
265 [ - - ]: 0 : if (it != ctrinfo.end())
266 : : // store keyword and its info on which help was requested
267 : 0 : stack.template get< tag::helpkw >() = { it->first, it->second, false };
268 : : else
269 : 0 : Message< Stack, ERROR, MsgKey::KEYWORD >( stack, in );
270 : : }
271 : 0 : }
272 : : };
273 : :
274 : : //! Rule used to trigger action
275 : : template< class keyword, typename tag, typename... tags >
276 : : struct check_lower_bound : pegtl::success {};
277 : : //! Check if value is larger than lower bound
278 : : template< class keyword, typename tag, typename... tags >
279 : : struct action< check_lower_bound< keyword, tag, tags... > > {
280 : : template< typename Input, typename Stack >
281 : : static void apply( const Input& in, Stack& stack ) {
282 : : auto lower = keyword::info::expect::lower;
283 : 99 : auto val = stack.template get< tag, tags... >();
284 [ - - ][ - - ]: 99 : if (val < lower) Message< Stack, ERROR, MsgKey::BOUNDS >( stack, in );
[ - + ][ - - ]
[ - + ][ - - ]
285 : : }
286 : : };
287 : :
288 : : //! Rule used to trigger action
289 : : template< class keyword, typename tag, typename... tags >
290 : : struct check_upper_bound : pegtl::success {};
291 : : //! Check if value is lower than upper bound
292 : : template< class keyword, typename tag, typename... tags >
293 : : struct action< check_upper_bound< keyword, tag, tags... > > {
294 : : template< typename Input, typename Stack >
295 : : static void apply( const Input& in, Stack& stack ) {
296 : : auto upper = keyword::info::expect::upper;
297 : 99 : auto val = stack.template get< tag, tags... >();
298 [ - - ][ - - ]: 99 : if (val > upper) Message< Stack, ERROR, MsgKey::BOUNDS >( stack, in );
[ - + ][ - - ]
[ - + ][ - - ]
299 : : }
300 : : };
301 : :
302 : : // Common grammar (grammar that is reused by multiple grammars)
303 : :
304 : : //! Read 'token' until 'erased' trimming, i.e., not consuming, 'erased'
305 : : template< class token, class erased >
306 : : struct trim :
307 : : pegtl::seq< token,
308 : : pegtl::sor<
309 : : pegtl::until< pegtl::at< erased > >,
310 : : msg< ERROR, MsgKey::PREMATURE > > > {};
311 : :
312 : : //! Match unknown keyword and handle error
313 : : template< MsgType type, MsgKey key >
314 : : struct unknown :
315 : : pegtl::pad< pegtl::seq< trim< pegtl::any, pegtl::space >,
316 : : msg< type, key > >,
317 : : pegtl::blank,
318 : : pegtl::space > {};
319 : :
320 : : //! Match alias cmdline keyword
321 : : //! \details An alias command line keyword is prefixed by a single dash, '-'.
322 : : template< class keyword >
323 : : struct alias :
324 : : pegtl::seq<
325 : : pegtl::one< '-' >,
326 : : typename keyword::info::alias::type,
327 : : pegtl::sor< pegtl::space,
328 : : msg< ERROR, MsgKey::ALIAS > > > {};
329 : :
330 : : //! Match verbose cmdline keyword
331 : : //! \details A verbose command line keyword is prefixed by a double-dash,
332 : : //! '--'.
333 : : template< class keyword >
334 : : struct verbose :
335 : : pegtl::seq< pegtl::string<'-','-'>,
336 : : typename keyword::pegtl_string,
337 : : pegtl::space > {};
338 : :
339 : : //! Read keyword 'token' padded by blank at left and space at right
340 : : template< class token >
341 : : struct readkw :
342 : : pegtl::pad< trim< token, pegtl::space >,
343 : : pegtl::blank,
344 : : pegtl::space > {};
345 : :
346 : : //! Read command line 'keyword' in verbose form, i.e., '--keyword'
347 : : //! \details This version is used if no alias is defined for the given keyword
348 : : template< class keyword, typename = void >
349 : : struct readcmd :
350 : : verbose< keyword > {};
351 : :
352 : : //! Read command line 'keyword' in either verbose or alias form
353 : : //! \details This version is used if an alias is defined for the given
354 : : //! keyword, in which case either the verbose or the alias form of the
355 : : //! keyword is matched, i.e., either '--keyword' or '-a', where 'a' is the
356 : : //! single-character alias for the longer 'keyword'. This is a partial
357 : : //! specialization of the simpler verbose-only readcmd, which attempts to
358 : : //! find the typedef 'alias' in keyword::info. If it finds it, it uses this
359 : : //! specialization. If it fails, it is [SFINAE]
360 : : //! (http://en.cppreference.com/w/cpp/language/sfinae), and falls back to
361 : : //! the verbose-only definition. Credit goes to David Rodriguez at
362 : : //! stackoverflow.com. This allows not having to change this client-code to
363 : : //! the keywords definitions: a keyword either defines an alias or not, and
364 : : //! the grammar here will do the right thing: if there is an alias, it will
365 : : //! build the grammar that optionally parses for it.
366 : : //! \see tk::if_
367 : : //! \see Control/Keyword.h and Control/Keywords.h
368 : : //! \see http://en.cppreference.com/w/cpp/language/sfinae
369 : : //! \see http://stackoverflow.com/a/11814074
370 : : template< class keyword >
371 : : struct readcmd< keyword,
372 : : typename if_< false, typename keyword::info::alias >::type > :
373 : : pegtl::sor< verbose< keyword >, alias< keyword > > {};
374 : :
375 : : //! \brief Scan input padded by blank at left and space at right and if it
376 : : //! matches 'keyword', apply 'actions'
377 : : //! \details As opposed to scan_until this rule, allows multiple actions
378 : : template< class keyword, class... actions >
379 : : struct scan :
380 : : pegtl::pad< act< trim< keyword, pegtl::space >, actions... >,
381 : : pegtl::blank,
382 : : pegtl::space > {};
383 : :
384 : : //! Parse a number: an optional sign followed by digits
385 : : struct number :
386 : : pegtl::seq< pegtl::opt< pegtl::sor< pegtl::one<'+'>,
387 : : pegtl::one<'-'> > >,
388 : : pegtl::digit > {};
389 : :
390 : : //! \brief Process command line 'keyword' and call its 'insert' action if
391 : : //! matches 'kw_type'
392 : : template< template< class > class use, class keyword, class insert,
393 : : class kw_type, class tag, class... tags >
394 : : struct process_cmd :
395 : : pegtl::if_must<
396 : : readcmd< use< keyword > >,
397 : : scan< pegtl::sor< kw_type, msg< ERROR, MsgKey::MISSING > >, insert >,
398 : : typename std::conditional<
399 : : tk::HasVar_expect_lower< typename keyword::info >::value,
400 : : check_lower_bound< keyword, tag, tags... >,
401 : : pegtl::success >::type,
402 : : typename std::conditional<
403 : : tk::HasVar_expect_upper< typename keyword::info >::value,
404 : : check_upper_bound< keyword, tag, tags... >,
405 : : pegtl::success >::type > {};
406 : :
407 : : //! Process command line switch 'keyword'
408 : : //! \details The value of a command line switch is a boolean, i.e., it can be
409 : : //! either set or unset.
410 : : template< template< class > class use, class keyword, typename tag,
411 : : typename... tags >
412 : : struct process_cmd_switch :
413 : : pegtl::seq<readcmd< use<keyword> >, Invert_switch< tag, tags... >> {};
414 : :
415 : : //! Process but ignore Charm++'s charmrun arguments starting with '+'
416 : : struct charmarg :
417 : : pegtl::seq< pegtl::one<'+'>,
418 : : unknown< WARNING, MsgKey::CHARMARG > > {};
419 : :
420 : : //! Generic string parser entry point: parse 'keywords' until end of string
421 : : template< typename keywords >
422 : : struct read_string :
423 : : pegtl::until< pegtl::eof,
424 : : pegtl::sor<
425 : : keywords,
426 : : charmarg,
427 : : unknown< ERROR, MsgKey::KEYWORD > > > {};
428 : :
429 : : //! \brief Ensure that a grammar only uses keywords from a pool of
430 : : //! pre-defined keywords
431 : : //! \details In grammar definitions, every keyword should be wrapped around
432 : : //! this use template, which conditionally inherits the keyword type its
433 : : //! templated on (as defined in Control/Keywords.h) if the keyword is listed
434 : : //! in any of the pools of keywords passed in as the required pool template
435 : : //! argument and zero or more additional pools. If the keyword is not in any
436 : : //! of the pools, a compiler error is generated, since the struct cannot
437 : : //! inherit from base class 'char'. In that case, simply add the new keyword
438 : : //! into one of the pools of keywords corresponding to the given grammar.
439 : : //! The rationale behind this wrapper is to force the developer to maintain
440 : : //! the keywords pool for a grammar. The pools are brigand::set and are
441 : : //! used to provide help on command line arguments for a given executable.
442 : : //! They allow compile-time iteration with brigand::for_each or
443 : : //! generating a run-time std::map associating, e.g., keywords to their help
444 : : //! strings.
445 : : //! \warning Note that an even more elegant solution to the problem this
446 : : //! wrapper is intended to solve is to use a metaprogram that collects all
447 : : //! occurrences of the keywords in a grammar. However, that does not seem to
448 : : //! be possible without extensive macro-trickery. Instead, if all keywords
449 : : //! in all grammar definitions are wrapped inside this use template (or one
450 : : //! of its specializations), we make sure that only those keywords can be
451 : : //! used by a grammar that are listed in the pool corresponding to a
452 : : //! grammar. However, this is still only a partial solution, since listing
453 : : //! more keywords in the pool than those used in the grammar is still
454 : : //! possible, which would result in those keywords included in, e.g., the
455 : : //! on-screen help generated even though some of the keywords may not be
456 : : //! implemented by the given grammar. So please don't abuse and don't list
457 : : //! keywords in the pool only if they are implemented in the grammar.
458 : : //! \see For example usage see the template typedef
459 : : //! walker::cmd::use in Control/Walker/CmdLine/Grammar.h and its keywords
460 : : //! pool, walker::ctr::CmdLine::keywords, in
461 : : //! Control/Walker/CmdLine/CmdLine.h.
462 : : //! \see http://en.cppreference.com/w/cpp/types/conditional
463 : : //! TODO It still would be nice to generate a more developer-friendly
464 : : //! compiler error if the keyword is not in the pool.
465 : : template< typename keyword, typename pool >
466 : : struct use :
467 : : std::conditional< brigand::or_<
468 : : brigand::has_key< pool, keyword > >::value,
469 : : keyword,
470 : : char >::type {};
471 : :
472 : : } // grm::
473 : : } // tk::
474 : :
475 : : #endif // CommonGrammar_h
|