Parser

class
Superclass
Object
Extended With
Loggability

This is the parser for Inversion templates. It takes template source and returns a tree of Inversion::Template::Node objects (if parsing is successful).

Constants

DEFAULT_OPTIONS

Default values for parser configuration options.

MATCHING_BRACKETS

Valid tagends by tagstart

TAG_OPEN

The pattern for matching a tag opening

TAG_PATTERN

The pattern for matching a tag.

Attributes

options[R]

The parser's config options

Public Class Methods

anchor
new( template, options={} )

Create a new Inversion::Parser with the specified config options.

# File lib/inversion/parser.rb, line 48
def initialize( template, options={} )
        @template = template
        @options  = DEFAULT_OPTIONS.merge( options )
end

Public Instance Methods

anchor
parse( source, inherited_state=nil )

Parse the given source into one or more Inversion::Template::Nodes and return it as an Array.

# File lib/inversion/parser.rb, line 64
def parse( source, inherited_state=nil )
        state = nil

        if inherited_state
                inherited_state.template = @template
                state = inherited_state
        else
                state = Inversion::Parser::State.new( @template, self.options )
        end

        self.log.debug "Starting parse of template source (%0.2fK, %s)" %
                [ source.bytesize/1024.0, source.encoding ]

        t0 = Time.now
        last_pos = last_linenum = last_colnum = 0
        source.scan( TAG_PATTERN ) do |*|
                match = Regexp.last_match
                start_pos, end_pos = match.offset( 0 )
                linenum            = match.pre_match.count( "\n" ) + 1
                colnum             = match.pre_match.length - (match.pre_match.rindex("\n") || -1)

                # Error on <?...?] and vice-versa.
                unless match[:tagend] == MATCHING_BRACKETS[match[:tagstart]]
                        raise Inversion::ParseError,
                                "malformed tag %p: mismatched start and end brackets at line %d, column %d" %
                                [ match[0], linenum, colnum ]
                end

                # Check for nested tags
                if match[0].index( TAG_OPEN, 2 )
                        raise Inversion::ParseError, "unclosed or nested tag %p at line %d, column %d" %
                                [ match[0], linenum, colnum ]
                end

                # self.log.debug "  found a tag at offset: %d (%p) (line %d, col %d)" %
                #     [ start_pos, abbrevstring(match[0]), linenum, colnum ]

                # If there were characters between the end of the last match and
                # the beginning of the tag, create a text node with them
                unless last_pos == start_pos
                        text = match.pre_match[ last_pos..-1 ]
                        # self.log.debug "  adding literal text node: %p" % [ abbrevstring(text) ]
                        state << Inversion::Template::TextNode.new( text, last_linenum, last_colnum )
                end

                # self.log.debug "  creating tag with tagname: %p, body: %p" %
                #    [ match[:tagname], match[:body] ]

                tag = Inversion::Template::Tag.create( match[:tagname], match[:body], linenum, colnum )
                if tag.nil?
                        unless state.options[ :ignore_unknown_tags ]
                                raise Inversion::ParseError, "Unknown tag %p at line %d, column %d" %
                                        [ match[:tagname], linenum, colnum ]
                        end

                    tag = Inversion::Template::TextNode.new( match[0], linenum, colnum )
                end

                # self.log.debug "  created tag: %p" % [ tag ]
                state << tag

                # Keep offsets for the next match
                last_pos     = end_pos
                last_linenum = linenum + match[0].count( "\n" )
                last_colnum  = match[0].length - ( match[0].rindex("\n") || -1 )
        end

        # If there are any characters left over after the last tag
        remainder = source[ last_pos..-1 ]
        if remainder && !remainder.empty?
                # self.log.debug "Remainder after last tag: %p" % [ abbrevstring(remainder) ]

                # Detect unclosed tags
                if remainder.index( "<?" ) || remainder.index( "[?" )
                        raise Inversion::ParseError,
                                "unclosed tag after line %d, column %d" % [ last_linenum, last_colnum ]
                end

                # Add any remaining text as a text node
                state << Inversion::Template::TextNode.new( remainder, last_linenum, last_colnum )
        end
        self.log.debug "  done parsing: %0.5fs" % [ Time.now - t0 ]

        return state.tree
end