summaryrefslogtreecommitdiffstats
path: root/lib/puppet/parser/lexer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/parser/lexer.rb')
-rw-r--r--lib/puppet/parser/lexer.rb1004
1 files changed, 502 insertions, 502 deletions
diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb
index 0c95142f9..6a9f1cfc4 100644
--- a/lib/puppet/parser/lexer.rb
+++ b/lib/puppet/parser/lexer.rb
@@ -5,573 +5,573 @@ require 'puppet'
module Puppet
- class LexError < RuntimeError; end
+ class LexError < RuntimeError; end
end
module Puppet::Parser; end
class Puppet::Parser::Lexer
- attr_reader :last, :file, :lexing_context, :token_queue
+ attr_reader :last, :file, :lexing_context, :token_queue
- attr_accessor :line, :indefine
+ attr_accessor :line, :indefine
- def lex_error msg
- raise Puppet::LexError.new(msg)
- end
-
- class Token
- attr_accessor :regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate
-
- def initialize(regex, name)
- if regex.is_a?(String)
- @name, @string = name, regex
- @regex = Regexp.new(Regexp.escape(@string))
- else
- @name, @regex = name, regex
- end
- end
-
- # MQR: Why not just alias?
- %w{skip accumulate}.each do |method|
- define_method(method+"?") do
- self.send(method)
- end
- end
-
- def to_s
- if self.string
- @string
- else
- @name.to_s
- end
- end
-
- def acceptable?(context={})
- # By default tokens are aceeptable in any context
- true
- end
- end
-
- # Maintain a list of tokens.
- class TokenList
- attr_reader :regex_tokens, :string_tokens
-
- def [](name)
- @tokens[name]
- end
-
- # Create a new token.
- def add_token(name, regex, options = {}, &block)
- token = Token.new(regex, name)
- raise(ArgumentError, "Token #{name} already exists") if @tokens.include?(name)
- @tokens[token.name] = token
- if token.string
- @string_tokens << token
- @tokens_by_string[token.string] = token
- else
- @regex_tokens << token
- end
-
- options.each do |name, option|
- token.send(name.to_s + "=", option)
- end
-
- token.meta_def(:convert, &block) if block_given?
-
- token
- end
-
- def initialize
- @tokens = {}
- @regex_tokens = []
- @string_tokens = []
- @tokens_by_string = {}
- end
-
- # Look up a token by its value, rather than name.
- def lookup(string)
- @tokens_by_string[string]
- end
-
- # Define more tokens.
- def add_tokens(hash)
- hash.each do |regex, name|
- add_token(name, regex)
- end
- end
-
- # Sort our tokens by length, so we know once we match, we're done.
- # This helps us avoid the O(n^2) nature of token matching.
- def sort_tokens
- @string_tokens.sort! { |a, b| b.string.length <=> a.string.length }
- end
- end
+ def lex_error msg
+ raise Puppet::LexError.new(msg)
+ end
- TOKENS = TokenList.new
-
- TOKENS.add_tokens(
-
- '[' => :LBRACK,
- ']' => :RBRACK,
- '{' => :LBRACE,
- '}' => :RBRACE,
- '(' => :LPAREN,
-
- ')' => :RPAREN,
- '=' => :EQUALS,
- '+=' => :APPENDS,
- '==' => :ISEQUAL,
- '>=' => :GREATEREQUAL,
- '>' => :GREATERTHAN,
- '<' => :LESSTHAN,
- '<=' => :LESSEQUAL,
- '!=' => :NOTEQUAL,
- '!' => :NOT,
- ',' => :COMMA,
- '.' => :DOT,
- ':' => :COLON,
- '@' => :AT,
- '<<|' => :LLCOLLECT,
- '->' => :IN_EDGE,
- '<-' => :OUT_EDGE,
- '~>' => :IN_EDGE_SUB,
- '<~' => :OUT_EDGE_SUB,
- '|>>' => :RRCOLLECT,
- '<|' => :LCOLLECT,
- '|>' => :RCOLLECT,
- ';' => :SEMIC,
- '?' => :QMARK,
- '\\' => :BACKSLASH,
- '=>' => :FARROW,
- '+>' => :PARROW,
- '+' => :PLUS,
- '-' => :MINUS,
- '/' => :DIV,
- '*' => :TIMES,
- '<<' => :LSHIFT,
- '>>' => :RSHIFT,
- '=~' => :MATCH,
- '!~' => :NOMATCH,
- %r{([a-z][-\w]*)?(::[a-z][-\w]*)+} => :CLASSNAME, # Require '::' in the class name, else we'd compete with NAME
- %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF,
- "<string>" => :STRING,
- "<dqstring up to first interpolation>" => :DQPRE,
- "<dqstring between two interpolations>" => :DQMID,
- "<dqstring after final interpolation>" => :DQPOST,
- "<boolean>" => :BOOLEAN
- )
-
- TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value|
- [TOKENS[:NAME], value]
- end
- #:stopdoc: # Issue #4161
- def (TOKENS[:NUMBER]).acceptable?(context={})
- ![:DQPRE,:DQMID].include? context[:after]
- end
- #:startdoc:
+ class Token
+ attr_accessor :regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate
- TOKENS.add_token :NAME, %r{[a-z0-9][-\w]*} do |lexer, value|
- string_token = self
- # we're looking for keywords here
- if tmp = KEYWORDS.lookup(value)
- string_token = tmp
- if [:TRUE, :FALSE].include?(string_token.name)
- value = eval(value)
- string_token = TOKENS[:BOOLEAN]
- end
- end
- [string_token, value]
+ def initialize(regex, name)
+ if regex.is_a?(String)
+ @name, @string = name, regex
+ @regex = Regexp.new(Regexp.escape(@string))
+ else
+ @name, @regex = name, regex
+ end
end
- [:NAME,:CLASSNAME,:CLASSREF].each { |name_token|
- #:stopdoc: # Issue #4161
- def (TOKENS[name_token]).acceptable?(context={})
- ![:DQPRE,:DQMID].include? context[:after]
- end
- #:startdoc:
- }
- TOKENS.add_token :COMMENT, %r{#.*}, :accumulate => true, :skip => true do |lexer,value|
- value.sub!(/# ?/,'')
- [self, value]
+ # MQR: Why not just alias?
+ %w{skip accumulate}.each do |method|
+ define_method(method+"?") do
+ self.send(method)
+ end
end
- TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :accumulate => true, :skip => true do |lexer, value|
- lexer.line += value.count("\n")
- value.sub!(/^\/\* ?/,'')
- value.sub!(/ ?\*\/$/,'')
- [self,value]
+ def to_s
+ if self.string
+ @string
+ else
+ @name.to_s
+ end
end
- TOKENS.add_token :REGEX, %r{/[^/\n]*/} do |lexer, value|
- # Make sure we haven't matched an escaped /
- while value[-2..-2] == '\\'
- other = lexer.scan_until(%r{/})
- value += other
- end
- regex = value.sub(%r{\A/}, "").sub(%r{/\Z}, '').gsub("\\/", "/")
- [self, Regexp.new(regex)]
- end
-
- #:stopdoc: # Issue #4161
- def (TOKENS[:REGEX]).acceptable?(context={})
- [:NODE,:LBRACE,:RBRACE,:MATCH,:NOMATCH,:COMMA].include? context[:after]
+ def acceptable?(context={})
+ # By default tokens are aceeptable in any context
+ true
end
- #:startdoc:
+ end
- TOKENS.add_token :RETURN, "\n", :skip => true, :incr_line => true, :skip_text => true
+ # Maintain a list of tokens.
+ class TokenList
+ attr_reader :regex_tokens, :string_tokens
- TOKENS.add_token :SQUOTE, "'" do |lexer, value|
- [TOKENS[:STRING], lexer.slurpstring(value).first ]
+ def [](name)
+ @tokens[name]
end
- DQ_initial_token_types = {'$' => :DQPRE,'"' => :STRING}
- DQ_continuation_token_types = {'$' => :DQMID,'"' => :DQPOST}
+ # Create a new token.
+ def add_token(name, regex, options = {}, &block)
+ token = Token.new(regex, name)
+ raise(ArgumentError, "Token #{name} already exists") if @tokens.include?(name)
+ @tokens[token.name] = token
+ if token.string
+ @string_tokens << token
+ @tokens_by_string[token.string] = token
+ else
+ @regex_tokens << token
+ end
- TOKENS.add_token :DQUOTE, /"/ do |lexer, value|
- lexer.tokenize_interpolated_string(DQ_initial_token_types)
- end
+ options.each do |name, option|
+ token.send(name.to_s + "=", option)
+ end
- TOKENS.add_token :DQCONT, /\}/ do |lexer, value|
- lexer.tokenize_interpolated_string(DQ_continuation_token_types)
- end
- #:stopdoc: # Issue #4161
- def (TOKENS[:DQCONT]).acceptable?(context={})
- context[:string_interpolation_depth] > 0
- end
- #:startdoc:
+ token.meta_def(:convert, &block) if block_given?
- TOKENS.add_token :DOLLAR_VAR, %r{\$(\w*::)*\w+} do |lexer, value|
- [TOKENS[:VARIABLE],value[1..-1]]
+ token
end
- TOKENS.add_token :VARIABLE, %r{(\w*::)*\w+}
+ def initialize
+ @tokens = {}
+ @regex_tokens = []
+ @string_tokens = []
+ @tokens_by_string = {}
+ end
+
+ # Look up a token by its value, rather than name.
+ def lookup(string)
+ @tokens_by_string[string]
+ end
+
+ # Define more tokens.
+ def add_tokens(hash)
+ hash.each do |regex, name|
+ add_token(name, regex)
+ end
+ end
+
+ # Sort our tokens by length, so we know once we match, we're done.
+ # This helps us avoid the O(n^2) nature of token matching.
+ def sort_tokens
+ @string_tokens.sort! { |a, b| b.string.length <=> a.string.length }
+ end
+ end
+
+ TOKENS = TokenList.new
+
+ TOKENS.add_tokens(
+
+ '[' => :LBRACK,
+ ']' => :RBRACK,
+ '{' => :LBRACE,
+ '}' => :RBRACE,
+ '(' => :LPAREN,
+
+ ')' => :RPAREN,
+ '=' => :EQUALS,
+ '+=' => :APPENDS,
+ '==' => :ISEQUAL,
+ '>=' => :GREATEREQUAL,
+ '>' => :GREATERTHAN,
+ '<' => :LESSTHAN,
+ '<=' => :LESSEQUAL,
+ '!=' => :NOTEQUAL,
+ '!' => :NOT,
+ ',' => :COMMA,
+ '.' => :DOT,
+ ':' => :COLON,
+ '@' => :AT,
+ '<<|' => :LLCOLLECT,
+ '->' => :IN_EDGE,
+ '<-' => :OUT_EDGE,
+ '~>' => :IN_EDGE_SUB,
+ '<~' => :OUT_EDGE_SUB,
+ '|>>' => :RRCOLLECT,
+ '<|' => :LCOLLECT,
+ '|>' => :RCOLLECT,
+ ';' => :SEMIC,
+ '?' => :QMARK,
+ '\\' => :BACKSLASH,
+ '=>' => :FARROW,
+ '+>' => :PARROW,
+ '+' => :PLUS,
+ '-' => :MINUS,
+ '/' => :DIV,
+ '*' => :TIMES,
+ '<<' => :LSHIFT,
+ '>>' => :RSHIFT,
+ '=~' => :MATCH,
+ '!~' => :NOMATCH,
+ %r{([a-z][-\w]*)?(::[a-z][-\w]*)+} => :CLASSNAME, # Require '::' in the class name, else we'd compete with NAME
+ %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF,
+ "<string>" => :STRING,
+ "<dqstring up to first interpolation>" => :DQPRE,
+ "<dqstring between two interpolations>" => :DQMID,
+ "<dqstring after final interpolation>" => :DQPOST,
+ "<boolean>" => :BOOLEAN
+ )
+
+ TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value|
+ [TOKENS[:NAME], value]
+ end
+ #:stopdoc: # Issue #4161
+ def (TOKENS[:NUMBER]).acceptable?(context={})
+ ![:DQPRE,:DQMID].include? context[:after]
+ end
+ #:startdoc:
+
+ TOKENS.add_token :NAME, %r{[a-z0-9][-\w]*} do |lexer, value|
+ string_token = self
+ # we're looking for keywords here
+ if tmp = KEYWORDS.lookup(value)
+ string_token = tmp
+ if [:TRUE, :FALSE].include?(string_token.name)
+ value = eval(value)
+ string_token = TOKENS[:BOOLEAN]
+ end
+ end
+ [string_token, value]
+ end
+ [:NAME,:CLASSNAME,:CLASSREF].each { |name_token|
#:stopdoc: # Issue #4161
- def (TOKENS[:VARIABLE]).acceptable?(context={})
- [:DQPRE,:DQMID].include? context[:after]
+ def (TOKENS[name_token]).acceptable?(context={})
+ ![:DQPRE,:DQMID].include? context[:after]
end
#:startdoc:
-
-
- TOKENS.sort_tokens
-
- @@pairs = {
- "{" => "}",
- "(" => ")",
- "[" => "]",
- "<|" => "|>",
- "<<|" => "|>>"
+ }
+
+ TOKENS.add_token :COMMENT, %r{#.*}, :accumulate => true, :skip => true do |lexer,value|
+ value.sub!(/# ?/,'')
+ [self, value]
+ end
+
+ TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :accumulate => true, :skip => true do |lexer, value|
+ lexer.line += value.count("\n")
+ value.sub!(/^\/\* ?/,'')
+ value.sub!(/ ?\*\/$/,'')
+ [self,value]
+ end
+
+ TOKENS.add_token :REGEX, %r{/[^/\n]*/} do |lexer, value|
+ # Make sure we haven't matched an escaped /
+ while value[-2..-2] == '\\'
+ other = lexer.scan_until(%r{/})
+ value += other
+ end
+ regex = value.sub(%r{\A/}, "").sub(%r{/\Z}, '').gsub("\\/", "/")
+ [self, Regexp.new(regex)]
+ end
+
+ #:stopdoc: # Issue #4161
+ def (TOKENS[:REGEX]).acceptable?(context={})
+ [:NODE,:LBRACE,:RBRACE,:MATCH,:NOMATCH,:COMMA].include? context[:after]
+ end
+ #:startdoc:
+
+ TOKENS.add_token :RETURN, "\n", :skip => true, :incr_line => true, :skip_text => true
+
+ TOKENS.add_token :SQUOTE, "'" do |lexer, value|
+ [TOKENS[:STRING], lexer.slurpstring(value).first ]
+ end
+
+ DQ_initial_token_types = {'$' => :DQPRE,'"' => :STRING}
+ DQ_continuation_token_types = {'$' => :DQMID,'"' => :DQPOST}
+
+ TOKENS.add_token :DQUOTE, /"/ do |lexer, value|
+ lexer.tokenize_interpolated_string(DQ_initial_token_types)
+ end
+
+ TOKENS.add_token :DQCONT, /\}/ do |lexer, value|
+ lexer.tokenize_interpolated_string(DQ_continuation_token_types)
+ end
+ #:stopdoc: # Issue #4161
+ def (TOKENS[:DQCONT]).acceptable?(context={})
+ context[:string_interpolation_depth] > 0
+ end
+ #:startdoc:
+
+ TOKENS.add_token :DOLLAR_VAR, %r{\$(\w*::)*\w+} do |lexer, value|
+ [TOKENS[:VARIABLE],value[1..-1]]
+ end
+
+ TOKENS.add_token :VARIABLE, %r{(\w*::)*\w+}
+ #:stopdoc: # Issue #4161
+ def (TOKENS[:VARIABLE]).acceptable?(context={})
+ [:DQPRE,:DQMID].include? context[:after]
+ end
+ #:startdoc:
+
+
+ TOKENS.sort_tokens
+
+ @@pairs = {
+ "{" => "}",
+ "(" => ")",
+ "[" => "]",
+ "<|" => "|>",
+ "<<|" => "|>>"
+ }
+
+ KEYWORDS = TokenList.new
+
+
+ KEYWORDS.add_tokens(
+
+ "case" => :CASE,
+ "class" => :CLASS,
+ "default" => :DEFAULT,
+ "define" => :DEFINE,
+ "import" => :IMPORT,
+ "if" => :IF,
+ "elsif" => :ELSIF,
+ "else" => :ELSE,
+ "inherits" => :INHERITS,
+ "node" => :NODE,
+ "and" => :AND,
+ "or" => :OR,
+ "undef" => :UNDEF,
+ "false" => :FALSE,
+ "true" => :TRUE,
+
+ "in" => :IN
+ )
+
+ def clear
+ initvars
+ end
+
+ def expected
+ return nil if @expected.empty?
+ name = @expected[-1]
+ TOKENS.lookup(name) or lex_error "Could not find expected token #{name}"
+ end
+
+ # scan the whole file
+ # basically just used for testing
+ def fullscan
+ array = []
+
+ self.scan { |token, str|
+ # Ignore any definition nesting problems
+ @indefine = false
+ array.push([token,str])
}
-
- KEYWORDS = TokenList.new
-
-
- KEYWORDS.add_tokens(
-
- "case" => :CASE,
- "class" => :CLASS,
- "default" => :DEFAULT,
- "define" => :DEFINE,
- "import" => :IMPORT,
- "if" => :IF,
- "elsif" => :ELSIF,
- "else" => :ELSE,
- "inherits" => :INHERITS,
- "node" => :NODE,
- "and" => :AND,
- "or" => :OR,
- "undef" => :UNDEF,
- "false" => :FALSE,
- "true" => :TRUE,
-
- "in" => :IN
- )
-
- def clear
- initvars
- end
-
- def expected
- return nil if @expected.empty?
- name = @expected[-1]
- TOKENS.lookup(name) or lex_error "Could not find expected token #{name}"
- end
-
- # scan the whole file
- # basically just used for testing
- def fullscan
- array = []
-
- self.scan { |token, str|
- # Ignore any definition nesting problems
- @indefine = false
- array.push([token,str])
- }
- array
- end
-
- def file=(file)
- @file = file
- @line = 1
- @scanner = StringScanner.new(File.read(file))
- end
-
- def shift_token
- @token_queue.shift
- end
-
- def find_string_token
- # We know our longest string token is three chars, so try each size in turn
- # until we either match or run out of chars. This way our worst-case is three
- # tries, where it is otherwise the number of string token we have. Also,
- # the lookups are optimized hash lookups, instead of regex scans.
- #
- s = @scanner.peek(3)
- token = TOKENS.lookup(s[0,3]) || TOKENS.lookup(s[0,2]) || TOKENS.lookup(s[0,1])
- [ token, token && @scanner.scan(token.regex) ]
- end
-
- # Find the next token that matches a regex. We look for these first.
- def find_regex_token
- @regex += 1
- best_token = nil
- best_length = 0
-
- # I tried optimizing based on the first char, but it had
- # a slightly negative affect and was a good bit more complicated.
- TOKENS.regex_tokens.each do |token|
- if length = @scanner.match?(token.regex) and token.acceptable?(lexing_context)
- # We've found a longer match
- if length > best_length
- best_length = length
- best_token = token
- end
- end
+ array
+ end
+
+ def file=(file)
+ @file = file
+ @line = 1
+ @scanner = StringScanner.new(File.read(file))
+ end
+
+ def shift_token
+ @token_queue.shift
+ end
+
+ def find_string_token
+ # We know our longest string token is three chars, so try each size in turn
+ # until we either match or run out of chars. This way our worst-case is three
+ # tries, where it is otherwise the number of string token we have. Also,
+ # the lookups are optimized hash lookups, instead of regex scans.
+ #
+ s = @scanner.peek(3)
+ token = TOKENS.lookup(s[0,3]) || TOKENS.lookup(s[0,2]) || TOKENS.lookup(s[0,1])
+ [ token, token && @scanner.scan(token.regex) ]
+ end
+
+ # Find the next token that matches a regex. We look for these first.
+ def find_regex_token
+ @regex += 1
+ best_token = nil
+ best_length = 0
+
+ # I tried optimizing based on the first char, but it had
+ # a slightly negative affect and was a good bit more complicated.
+ TOKENS.regex_tokens.each do |token|
+ if length = @scanner.match?(token.regex) and token.acceptable?(lexing_context)
+ # We've found a longer match
+ if length > best_length
+ best_length = length
+ best_token = token
end
+ end
+ end
- return best_token, @scanner.scan(best_token.regex) if best_token
- end
-
- # Find the next token, returning the string and the token.
- def find_token
- @find += 1
- shift_token || find_regex_token || find_string_token
- end
+ return best_token, @scanner.scan(best_token.regex) if best_token
+ end
- def indefine?
- if defined?(@indefine)
- @indefine
- else
- false
- end
- end
+ # Find the next token, returning the string and the token.
+ def find_token
+ @find += 1
+ shift_token || find_regex_token || find_string_token
+ end
- def initialize
- @find = 0
- @regex = 0
- initvars
+ def indefine?
+ if defined?(@indefine)
+ @indefine
+ else
+ false
end
+ end
- def initvars
- @line = 1
- @previous_token = nil
- @scanner = nil
- @file = nil
- # AAARRGGGG! okay, regexes in ruby are bloody annoying
- # no one else has "\n" =~ /\s/
- @skip = %r{[ \t\r]+}
-
- @namestack = []
- @token_queue = []
- @indefine = false
- @expected = []
- @commentstack = [ ['', @line] ]
- @lexing_context = {
- :after => nil,
- :start_of_line => true,
- :string_interpolation_depth => 0
- }
- end
+ def initialize
+ @find = 0
+ @regex = 0
+ initvars
+ end
- # Make any necessary changes to the token and/or value.
- def munge_token(token, value)
- @line += 1 if token.incr_line
+ def initvars
+ @line = 1
+ @previous_token = nil
+ @scanner = nil
+ @file = nil
+ # AAARRGGGG! okay, regexes in ruby are bloody annoying
+ # no one else has "\n" =~ /\s/
+ @skip = %r{[ \t\r]+}
- skip if token.skip_text
+ @namestack = []
+ @token_queue = []
+ @indefine = false
+ @expected = []
+ @commentstack = [ ['', @line] ]
+ @lexing_context = {
+ :after => nil,
+ :start_of_line => true,
+ :string_interpolation_depth => 0
+ }
+ end
- return if token.skip and not token.accumulate?
+ # Make any necessary changes to the token and/or value.
+ def munge_token(token, value)
+ @line += 1 if token.incr_line
- token, value = token.convert(self, value) if token.respond_to?(:convert)
+ skip if token.skip_text
- return unless token
+ return if token.skip and not token.accumulate?
- if token.accumulate?
- comment = @commentstack.pop
- comment[0] << value + "\n"
- @commentstack.push(comment)
- end
+ token, value = token.convert(self, value) if token.respond_to?(:convert)
- return if token.skip
+ return unless token
- return token, { :value => value, :line => @line }
+ if token.accumulate?
+ comment = @commentstack.pop
+ comment[0] << value + "\n"
+ @commentstack.push(comment)
end
- # Go up one in the namespace.
- def namepop
- @namestack.pop
- end
+ return if token.skip
- # Collect the current namespace.
- def namespace
- @namestack.join("::")
- end
+ return token, { :value => value, :line => @line }
+ end
- # This value might have :: in it, but we don't care -- it'll be
- # handled normally when joining, and when popping we want to pop
- # this full value, however long the namespace is.
- def namestack(value)
- @namestack << value
- end
-
- def rest
- @scanner.rest
- end
-
- # this is the heart of the lexer
- def scan
- #Puppet.debug("entering scan")
- lex_error "Invalid or empty string" unless @scanner
-
- # Skip any initial whitespace.
- skip
+ # Go up one in the namespace.
+ def namepop
+ @namestack.pop
+ end
- until token_queue.empty? and @scanner.eos? do
- yielded = false
- matched_token, value = find_token
+ # Collect the current namespace.
+ def namespace
+ @namestack.join("::")
+ end
- # error out if we didn't match anything at all
- lex_error "Could not match #{@scanner.rest[/^(\S+|\s+|.*)/]}" unless matched_token
+ # This value might have :: in it, but we don't care -- it'll be
+ # handled normally when joining, and when popping we want to pop
+ # this full value, however long the namespace is.
+ def namestack(value)
+ @namestack << value
+ end
- newline = matched_token.name == :RETURN
+ def rest
+ @scanner.rest
+ end
- # this matches a blank line; eat the previously accumulated comments
- getcomment if lexing_context[:start_of_line] and newline
- lexing_context[:start_of_line] = newline
+ # this is the heart of the lexer
+ def scan
+ #Puppet.debug("entering scan")
+ lex_error "Invalid or empty string" unless @scanner
- final_token, token_value = munge_token(matched_token, value)
+ # Skip any initial whitespace.
+ skip
- unless final_token
- skip
- next
- end
+ until token_queue.empty? and @scanner.eos? do
+ yielded = false
+ matched_token, value = find_token
- lexing_context[:after] = final_token.name unless newline
- lexing_context[:string_interpolation_depth] += 1 if final_token.name == :DQPRE
- lexing_context[:string_interpolation_depth] -= 1 if final_token.name == :DQPOST
+ # error out if we didn't match anything at all
+ lex_error "Could not match #{@scanner.rest[/^(\S+|\s+|.*)/]}" unless matched_token
- value = token_value[:value]
+ newline = matched_token.name == :RETURN
+
+ # this matches a blank line; eat the previously accumulated comments
+ getcomment if lexing_context[:start_of_line] and newline
+ lexing_context[:start_of_line] = newline
- if match = @@pairs[value] and final_token.name != :DQUOTE and final_token.name != :SQUOTE
- @expected << match
- elsif exp = @expected[-1] and exp == value and final_token.name != :DQUOTE and final_token.name != :SQUOTE
- @expected.pop
- end
+ final_token, token_value = munge_token(matched_token, value)
- if final_token.name == :LBRACE
- commentpush
- end
-
- yield [final_token.name, token_value]
+ unless final_token
+ skip
+ next
+ end
- if @previous_token
- namestack(value) if @previous_token.name == :CLASS
+ lexing_context[:after] = final_token.name unless newline
+ lexing_context[:string_interpolation_depth] += 1 if final_token.name == :DQPRE
+ lexing_context[:string_interpolation_depth] -= 1 if final_token.name == :DQPOST
- if @previous_token.name == :DEFINE
- if indefine?
- msg = "Cannot nest definition #{value} inside #{@indefine}"
- self.indefine = false
- raise Puppet::ParseError, msg
- end
+ value = token_value[:value]
- @indefine = value
- end
- end
- @previous_token = final_token
- skip
- end
- @scanner = nil
+ if match = @@pairs[value] and final_token.name != :DQUOTE and final_token.name != :SQUOTE
+ @expected << match
+ elsif exp = @expected[-1] and exp == value and final_token.name != :DQUOTE and final_token.name != :SQUOTE
+ @expected.pop
+ end
- # This indicates that we're done parsing.
- yield [false,false]
- end
+ if final_token.name == :LBRACE
+ commentpush
+ end
- # Skip any skipchars in our remaining string.
- def skip
- @scanner.skip(@skip)
- end
+ yield [final_token.name, token_value]
- # Provide some limited access to the scanner, for those
- # tokens that need it.
- def scan_until(regex)
- @scanner.scan_until(regex)
- end
+ if @previous_token
+ namestack(value) if @previous_token.name == :CLASS
- # we've encountered the start of a string...
- # slurp in the rest of the string and return it
- Valid_escapes_in_strings = %w{ \\ $ ' " n t s }+["\n"]
- def slurpstring(terminators)
- # we search for the next quote that isn't preceded by a
- # backslash; the caret is there to match empty strings
- str = @scanner.scan_until(/([^\\]|^)[#{terminators}]/) or lex_error "Unclosed quote after '#{last}' in '#{rest}'"
- @line += str.count("\n") # literal carriage returns add to the line count.
- str.gsub!(/\\(.)/) {
- case ch=$1
- when 'n'; "\n"
- when 't'; "\t"
- when 's'; " "
- else
- if Valid_escapes_in_strings.include? ch and not (ch == '"' and terminators == "'")
- ch
- else
- Puppet.warning "Unrecognised escape sequence '\\#{ch}'#{file && " in file #{file}"}#{line && " at line #{line}"}"
- "\\#{ch}"
- end
- end
- }
- [ str[0..-2],str[-1,1] ]
- end
+ if @previous_token.name == :DEFINE
+ if indefine?
+ msg = "Cannot nest definition #{value} inside #{@indefine}"
+ self.indefine = false
+ raise Puppet::ParseError, msg
+ end
- def tokenize_interpolated_string(token_type)
- value,terminator = slurpstring('"$')
- token_queue << [TOKENS[token_type[terminator]],value]
- while terminator == '$' and not @scanner.scan(/\{/)
- token_queue << [TOKENS[:VARIABLE],@scanner.scan(%r{(\w*::)*\w+|[0-9]})]
- value,terminator = slurpstring('"$')
- token_queue << [TOKENS[DQ_continuation_token_types[terminator]],value]
+ @indefine = value
end
- token_queue.shift
- end
-
- # just parse a string, not a whole file
- def string=(string)
- @scanner = StringScanner.new(string)
- end
-
- # returns the content of the currently accumulated content cache
- def commentpop
- @commentstack.pop[0]
- end
-
- def getcomment(line = nil)
- comment = @commentstack.last
- if line.nil? or comment[1] <= line
- @commentstack.pop
- @commentstack.push(['', @line])
- return comment[0]
+ end
+ @previous_token = final_token
+ skip
+ end
+ @scanner = nil
+
+ # This indicates that we're done parsing.
+ yield [false,false]
+ end
+
+ # Skip any skipchars in our remaining string.
+ def skip
+ @scanner.skip(@skip)
+ end
+
+ # Provide some limited access to the scanner, for those
+ # tokens that need it.
+ def scan_until(regex)
+ @scanner.scan_until(regex)
+ end
+
+ # we've encountered the start of a string...
+ # slurp in the rest of the string and return it
+ Valid_escapes_in_strings = %w{ \\ $ ' " n t s }+["\n"]
+ def slurpstring(terminators)
+ # we search for the next quote that isn't preceded by a
+ # backslash; the caret is there to match empty strings
+ str = @scanner.scan_until(/([^\\]|^)[#{terminators}]/) or lex_error "Unclosed quote after '#{last}' in '#{rest}'"
+ @line += str.count("\n") # literal carriage returns add to the line count.
+ str.gsub!(/\\(.)/) {
+ case ch=$1
+ when 'n'; "\n"
+ when 't'; "\t"
+ when 's'; " "
+ else
+ if Valid_escapes_in_strings.include? ch and not (ch == '"' and terminators == "'")
+ ch
+ else
+ Puppet.warning "Unrecognised escape sequence '\\#{ch}'#{file && " in file #{file}"}#{line && " at line #{line}"}"
+ "\\#{ch}"
end
- ''
- end
-
- def commentpush
- @commentstack.push(['', @line])
- end
+ end
+ }
+ [ str[0..-2],str[-1,1] ]
+ end
+
+ def tokenize_interpolated_string(token_type)
+ value,terminator = slurpstring('"$')
+ token_queue << [TOKENS[token_type[terminator]],value]
+ while terminator == '$' and not @scanner.scan(/\{/)
+ token_queue << [TOKENS[:VARIABLE],@scanner.scan(%r{(\w*::)*\w+|[0-9]})]
+ value,terminator = slurpstring('"$')
+ token_queue << [TOKENS[DQ_continuation_token_types[terminator]],value]
+ end
+ token_queue.shift
+ end
+
+ # just parse a string, not a whole file
+ def string=(string)
+ @scanner = StringScanner.new(string)
+ end
+
+ # returns the content of the currently accumulated content cache
+ def commentpop
+ @commentstack.pop[0]
+ end
+
+ def getcomment(line = nil)
+ comment = @commentstack.last
+ if line.nil? or comment[1] <= line
+ @commentstack.pop
+ @commentstack.push(['', @line])
+ return comment[0]
+ end
+ ''
+ end
+
+ def commentpush
+ @commentstack.push(['', @line])
+ end
end