Class: Arrow::Template::Parser
- Includes:
- HashUtilities, Patterns
- Defined in:
- lib/arrow/template/parser.rb
Overview
The Arrow::Template::Parser class, a derivative of Arrow::Object. This is the default parser class for the default Arrow templating system.
Authors
-
Michael Granger <[email protected]>
Please see the file LICENSE in the top-level directory for licensing details.
Defined Under Namespace
Modules: Patterns Classes: State
Constant Summary collapse
- Defaults =
Default configuration hash
{ :strict_end_tags => false, :ignore_unknown_PIs => true, }
Constants included from Patterns
Patterns::ALTERNATION, Patterns::ARGDEFAULT, Patterns::ARGUMENT, Patterns::CAPTURE, Patterns::COMMA, Patterns::DBLQSTRING, Patterns::DOT, Patterns::EQUALS, Patterns::IDENTIFIER, Patterns::INFIX, Patterns::LBRACKET, Patterns::NUMBER, Patterns::PATHNAME, Patterns::QUOTEDSTRING, Patterns::RBRACKET, Patterns::REBINDOP, Patterns::REGEXP, Patterns::SLASHQSTRING, Patterns::SYMBOL, Patterns::TAGCLOSE, Patterns::TAGMIDDLE, Patterns::TAGOPEN, Patterns::TICKQSTRING, Patterns::VARIABLE, Patterns::WHITESPACE
Constants included from HashUtilities
HashUtilities::HashMergeFunction
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
The configuration object which contains the parser’s config.
Instance Method Summary collapse
-
#initialize(config = {}) ⇒ Parser
constructor
Create a new parser using the specified
config
. -
#initialize_copy(original) ⇒ Object
Initialize a duplicate of the
original
parser. -
#parse(string, template, initialData = {}) ⇒ Object
Parse and return a template syntax tree from the given
string
. -
#scan_directive(state, context = nil) ⇒ Object
Given the specified parse
state
which is pointing past the opening of a directive tag, parse the directive. -
#scan_for_arglist(state, skip_whitespace = true) ⇒ Object
Given the specified
state
(an Arrow::Template::Parser::State object), scan for and return two Arrays of identifiers. -
#scan_for_identifier(state, skip_whitespace = true) ⇒ Object
Given the specified
state
(an Arrow::Template::Parser::State object), scan for and return an indentifier. -
#scan_for_methodchain(state) ⇒ Object
Given the specified
state
(an Arrow::Template::Parser::State object), scan for and return a methodchain. -
#scan_for_nodes(state, context = nil, node = nil) ⇒ Object
Use the specified
state
(a StringScanner object) to scan for directive and plain-text nodes. -
#scan_for_pathname(state, skip_whitespace = true) ⇒ Object
Given the specified
state
(an Arrow::Template::Parser::State object), scan for and return a valid-looking file pathname. -
#scan_for_quoted_string(state, skip_whitespace = true) ⇒ Object
Given the specified
state
(an Arrow::Template::Parser::State object), scan for and return a quoted string, including the quotes. -
#scan_for_tag_ending(state, skip_whitespace = true) ⇒ Object
Given the specified
state
(an Arrow::Template::Parser::State object), scan for and return the current tag ending.
Methods included from HashUtilities
stringify_keys, symbolify_keys
Methods inherited from Object
deprecate_class_method, deprecate_method, inherited
Constructor Details
#initialize(config = {}) ⇒ Parser
Create a new parser using the specified config
. The config
can contain one or more of the following keys:
- :strict_end_tags
-
Raise an error if the optional name associated with an <?end?> tag doesn’t match the directive it closes. Defaults to
false
. - :ignore_unknown_PIs
-
When this is set to
true
, any processing instructions found in a template that don’t parse will be kept as-is in the output. If this isfalse
, unrecognized PIs will raise an error at parse time. Defaults totrue
.
248 249 250 |
# File 'lib/arrow/template/parser.rb', line 248 def initialize( config={} ) @config = Defaults.merge( config, &HashMergeFunction ) end |
Instance Attribute Details
#config ⇒ Object (readonly)
The configuration object which contains the parser’s config.
265 266 267 |
# File 'lib/arrow/template/parser.rb', line 265 def config @config end |
Instance Method Details
#initialize_copy(original) ⇒ Object
Initialize a duplicate of the original
parser.
254 255 256 257 |
# File 'lib/arrow/template/parser.rb', line 254 def initialize_copy( original ) super @config = original.config.dup end |
#parse(string, template, initialData = {}) ⇒ Object
Parse and return a template syntax tree from the given string
.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/arrow/template/parser.rb', line 269 def parse( string, template, initialData={} ) # Create a new parse state and build the parse tree with it. begin state = State.new( string, template, initialData ) syntax_tree = self.scan_for_nodes( state ) rescue Arrow::TemplateError => err Kernel.raise( err ) unless defined? state state.scanner.unscan if state.scanner.matched? #<- segfaults # Get the linecount and chunk of erroring content errorContent = get_parse_context( state.scanner ) msg = err..split( /:/ ).uniq.join( ':' ) + %{ at line %d of %s: %s...} % [ state.line, template._file, errorContent ] Kernel.raise( err.class, msg ) end return syntax_tree end |
#scan_directive(state, context = nil) ⇒ Object
Given the specified parse state
which is pointing past the opening of a directive tag, parse the directive.
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/arrow/template/parser.rb', line 340 def scan_directive( state, context=nil ) scanner = state.scanner # Set the patterns in the parse state to compliment the # opening tag. state.set_tag_patterns( scanner.matched ) tag_begin = state.line # Scan for the directive name; if no valid name can be # found, handle an unknown PI/directive. scanner.skip( WHITESPACE ) unless (( tag = scanner.scan( IDENTIFIER ) )) #self.log.debug "No identifier at '%s...'" % scanner.rest[0,20] # If the tag_open is <?, then this is a PI that we don't # grok. The reaction to this is configurable, so decide what to # do. if state.tag_open == '<?' return handle_unknown_pi( state ) # ...otherwise, it's just a malformed non-PI tag, which # is always an error. else raise Arrow::ParseError, "malformed directive name" end end # If it's anything but an 'end' tag, create a directive object. unless tag == 'end' begin node = Arrow::Template::Directive.create( tag, self, state ) rescue ::FactoryError => err return self.handle_unknown_pi( state, tag ) end # If it's an 'end', else #self.log.debug "Found end tag." # If this scan is occuring in a recursive parse, make sure the # 'end' is closing the correct thing and break out of the node # search. Note that the trailing '?>' is left in the scanner to # match at the end of the loop that opened this recursion. if context scanner.skip( WHITESPACE ) closed_tag = scanner.scan( IDENTIFIER ) #self.log.debug "End found for #{closed_tag}" # If strict end tags is turned on, check to be sure we # got the correct 'end'. if @config[:strict_end_tags] raise Arrow::ParseError, "missing or malformed closing tag name" if closed_tag.nil? raise Arrow::ParseError, "mismatched closing tag name '#{closed_tag}'" unless closed_tag.downcase == context.downcase end # Jump out of the loop in #scan_for_nodes... throw :endscan else raise Arrow::ParseError, "dangling end" end end # Skip to the end of the tag self.scan_for_tag_ending( state ) or raise Arrow::ParseError, "malformed tag starting at line %d: no closing tag "\ "delimiters %p found" % [ tag_begin, state.tag_close ] return node end |
#scan_for_arglist(state, skip_whitespace = true) ⇒ Object
Given the specified state
(an Arrow::Template::Parser::State object), scan for and return two Arrays of identifiers. The first is the list of parsed arguments as they appeared in the source, and the second is the same list with all non-word characters removed. Given an arglist like:
foo, bar=baz, *bim, &boozle
the returned arrays will contain:
["foo", "bar=baz", "*bim", "&boozle"]
and
["foo", "bar", "bim", "boozle"]
respectively.
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 |
# File 'lib/arrow/template/parser.rb', line 495 def scan_for_arglist( state, skip_whitespace=true ) scanner = state.scanner #self.log.debug "Scanning for arglist at %p" % # scanner.rest[0,20] args = [] pureargs = [] scanner.skip( WHITESPACE ) if skip_whitespace while (( rval = scanner.scan(ARGUMENT) )) args << rval pureargs << rval.gsub( /\W+/, '' ) scanner.skip( WHITESPACE ) if (( rval = scanner.scan(ARGDEFAULT) )) args.last << rval end break unless scanner.skip( WHITESPACE + COMMA + WHITESPACE ) end return nil if args.empty? #self.log.debug "Found args: %p, pureargs: %p" % # [ args, pureargs ] return args, pureargs end |
#scan_for_identifier(state, skip_whitespace = true) ⇒ Object
Given the specified state
(an Arrow::Template::Parser::State object), scan for and return an indentifier. If skip_whitespace
is true
, any leading whitespace characters will be skipped. Returns nil
if no identifier is found.
420 421 422 423 424 425 426 427 428 429 |
# File 'lib/arrow/template/parser.rb', line 420 def scan_for_identifier( state, skip_whitespace=true ) #self.log.debug "Scanning for identifier at %p" % # state.scanner.rest[0,20] state.scanner.skip( WHITESPACE ) if skip_whitespace rval = state.scanner.scan( IDENTIFIER ) or return nil #self.log.debug "Found identifier %p" % rval return rval end |
#scan_for_methodchain(state) ⇒ Object
Given the specified state
(an Arrow::Template::Parser::State object), scan for and return a methodchain. Returns nil
if no methodchain is found.
453 454 455 456 457 458 459 460 461 462 463 464 |
# File 'lib/arrow/template/parser.rb', line 453 def scan_for_methodchain( state ) scanner = state.scanner #self.log.debug "Scanning for methodchain at %p" % # scanner.rest[0,20] rval = scanner.scan( INFIX ) || '' rval << (scanner.scan( WHITESPACE ) || '') rval << (scanner.scan( state.tag_middle ) || '') #self.log.debug "Found methodchain %p" % rval return rval end |
#scan_for_nodes(state, context = nil, node = nil) ⇒ Object
Use the specified state
(a StringScanner object) to scan for directive and plain-text nodes. The context
argument, if set, indicates a recursive call for the directive named. The node
will be used as the branch node in the parse state.
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/arrow/template/parser.rb', line 297 def scan_for_nodes( state, context=nil, node=nil ) return state.branch( node ) do scanner = state.scanner # Scan until the scanner reaches the end of its string. Early exits # 'break' of this loop. catch( :endscan ) { until scanner.eos? startpos = scanner.pos #self.log.debug %{Scanning from %d:%p} % # [ scanner.pos, scanner.rest[0,20] + '..' ] # Scan for the next directive. When the scanner reaches # the end of the parsed string, just append any plain # text that's left and stop scanning. if scanner.skip_until( TAGOPEN ) # Add the literal String node leading up to the tag # as a text node :FIXME: Have to do it this way # because StringScanner#pre_match does weird crap if # skip_until skips only one or no character/s. if ( scanner.pos - startpos > scanner.matched.length ) offset = scanner.pos - scanner.matched.length - 1 state << Arrow::Template::TextNode. new( scanner.string[startpos..offset] ) #self.log.debug "Added text node %p" % # scanner.string[startpos..offset] end # Now scan the directive that was found state << self.scan_directive( state, context ) else state << Arrow::Template::TextNode.new( scanner.rest ) scanner.terminate end end } end end |
#scan_for_pathname(state, skip_whitespace = true) ⇒ Object
Given the specified state
(an Arrow::Template::Parser::State object), scan for and return a valid-looking file pathname.
523 524 525 526 527 528 529 530 531 532 533 534 |
# File 'lib/arrow/template/parser.rb', line 523 def scan_for_pathname( state, skip_whitespace=true ) scanner = state.scanner #self.log.debug "Scanning for file path at %p" % # scanner.rest[0,20] scanner.skip( WHITESPACE ) if skip_whitespace rval = scanner.scan( PATHNAME ) or return nil #self.log.debug "Found path: %p" % rval return rval end |
#scan_for_quoted_string(state, skip_whitespace = true) ⇒ Object
Given the specified state
(an Arrow::Template::Parser::State object), scan for and return a quoted string, including the quotes. If skip_whitespace
is true
, any leading whitespace characters will be skipped. Returns nil
if no quoted string is found.
437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/arrow/template/parser.rb', line 437 def scan_for_quoted_string( state, skip_whitespace=true ) #self.log.debug "Scanning for quoted string at %p" % # state.scanner.rest[0,20] state.scanner.skip( WHITESPACE ) if skip_whitespace rval = state.scanner.scan( QUOTEDSTRING ) or return nil #self.log.debug "Found quoted string %p" % rval return rval end |
#scan_for_tag_ending(state, skip_whitespace = true) ⇒ Object
Given the specified state
(an Arrow::Template::Parser::State object), scan for and return the current tag ending. Returns nil
if no tag ending is found.
470 471 472 473 474 475 476 477 478 479 480 481 |
# File 'lib/arrow/template/parser.rb', line 470 def scan_for_tag_ending( state, skip_whitespace=true ) scanner = state.scanner #self.log.debug "Scanning for tag ending at %p" % # scanner.rest[0,20] scanner.skip( WHITESPACE ) if skip_whitespace rval = scanner.scan( state.tag_close ) or return nil #self.log.debug "Found tag ending %p" % rval return rval end |