#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # This is a special matcher to match easily lexer output RSpec::Matchers.define :be_like do |*expected| match do |actual| diffable expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) } end end __ = nil module EgrammarLexerSpec def self.tokens_scanned_from(s) lexer = Puppet::Pops::Parser::Lexer.new lexer.string = s lexer.fullscan[0..-2] end end describe Puppet::Pops::Parser::Lexer do include EgrammarLexerSpec describe "when reading strings" do before { @lexer = Puppet::Pops::Parser::Lexer.new } it "should increment the line count for every carriage return in the string" do @lexer.string = "'this\nis\natest'" @lexer.fullscan[0..-2] line = @lexer.line line.should == 3 end it "should not increment the line count for escapes in the string" do @lexer.string = "'this\\nis\\natest'" @lexer.fullscan[0..-2] @lexer.line.should == 1 end it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do @lexer.string = "'here\nis\nthe\nstring\\\\'with\nextra\njunk" @lexer.fullscan[0..-2] @lexer.line.should == 6 end { 'r' => "\r", 'n' => "\n", 't' => "\t", 's' => " " }.each do |esc, expected_result| it "should recognize \\#{esc} sequence" do @lexer.string = "\\#{esc}'" @lexer.slurpstring("'")[0].should == expected_result end end end end describe Puppet::Pops::Parser::Lexer::Token, "when initializing" do it "should create a regex if the first argument is a string" do Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something} end it "should set the string if the first argument is one" do Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).string.should == "something" end it "should set the regex if the first argument is one" do Puppet::Pops::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something} end end describe Puppet::Pops::Parser::Lexer::TokenList do before do @list = Puppet::Pops::Parser::Lexer::TokenList.new end it "should have a method for retrieving tokens by the name" do token = @list.add_token :name, "whatever" @list[:name].should equal(token) end it "should have a method for retrieving string tokens by the string" do token = @list.add_token :name, "whatever" @list.lookup("whatever").should equal(token) end it "should add tokens to the list when directed" do token = @list.add_token :name, "whatever" @list[:name].should equal(token) end it "should have a method for adding multiple tokens at once" do @list.add_tokens "whatever" => :name, "foo" => :bar @list[:name].should_not be_nil @list[:bar].should_not be_nil end it "should fail to add tokens sharing a name with an existing token" do @list.add_token :name, "whatever" expect { @list.add_token :name, "whatever" }.to raise_error(ArgumentError) end it "should set provided options on tokens being added" do token = @list.add_token :name, "whatever", :skip_text => true token.skip_text.should == true end it "should define any provided blocks as a :convert method" do token = @list.add_token(:name, "whatever") do "foo" end token.convert.should == "foo" end it "should store all string tokens in the :string_tokens list" do one = @list.add_token(:name, "1") @list.string_tokens.should be_include(one) end it "should store all regex tokens in the :regex_tokens list" do one = @list.add_token(:name, %r{one}) @list.regex_tokens.should be_include(one) end it "should not store string tokens in the :regex_tokens list" do one = @list.add_token(:name, "1") @list.regex_tokens.should_not be_include(one) end it "should not store regex tokens in the :string_tokens list" do one = @list.add_token(:name, %r{one}) @list.string_tokens.should_not be_include(one) end it "should sort the string tokens inversely by length when asked" do one = @list.add_token(:name, "1") two = @list.add_token(:other, "12") @list.sort_tokens @list.string_tokens.should == [two, one] end end describe Puppet::Pops::Parser::Lexer::TOKENS do before do @lexer = Puppet::Pops::Parser::Lexer.new end { :LBRACK => '[', :RBRACK => ']', # :LBRACE => '{', :RBRACE => '}', :LPAREN => '(', :RPAREN => ')', :EQUALS => '=', :ISEQUAL => '==', :GREATEREQUAL => '>=', :GREATERTHAN => '>', :LESSTHAN => '<', :LESSEQUAL => '<=', :NOTEQUAL => '!=', :NOT => '!', :COMMA => ',', :DOT => '.', :COLON => ':', :AT => '@', :LLCOLLECT => '<<|', :RRCOLLECT => '|>>', :LCOLLECT => '<|', :RCOLLECT => '|>', :SEMIC => ';', :QMARK => '?', :BACKSLASH => '\\', :FARROW => '=>', :PARROW => '+>', :APPENDS => '+=', :PLUS => '+', :MINUS => '-', :DIV => '/', :TIMES => '*', :LSHIFT => '<<', :RSHIFT => '>>', :MATCH => '=~', :NOMATCH => '!~', :IN_EDGE => '->', :OUT_EDGE => '<-', :IN_EDGE_SUB => '~>', :OUT_EDGE_SUB => '<~', :PIPE => '|', }.each do |name, string| it "should have a token named #{name.to_s}" do Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil end it "should match '#{string}' for the token #{name.to_s}" do Puppet::Pops::Parser::Lexer::TOKENS[name].string.should == string end end { "case" => :CASE, "class" => :CLASS, "default" => :DEFAULT, "define" => :DEFINE, # "import" => :IMPORT, # done as a function in egrammar "if" => :IF, "elsif" => :ELSIF, "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, "and" => :AND, "or" => :OR, "undef" => :UNDEF, "false" => :FALSE, "true" => :TRUE, "in" => :IN, "unless" => :UNLESS, }.each do |string, name| it "should have a keyword named #{name.to_s}" do Puppet::Pops::Parser::Lexer::KEYWORDS[name].should_not be_nil end it "should have the keyword for #{name.to_s} set to #{string}" do Puppet::Pops::Parser::Lexer::KEYWORDS[name].string.should == string end end # These tokens' strings don't matter, just that the tokens exist. [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, :LBRACE, :LAMBDA, :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name| it "should have a token named #{name.to_s}" do Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil end end end describe Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] do before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] } it "should match against single upper-case alpha-numeric terms" do @token.regex.should =~ "One" end it "should match against upper-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "One::Two" end it "should match against many upper-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "One::Two::Three::Four::Five" end it "should match against upper-case alpha-numeric terms prefixed by double colons" do @token.regex.should =~ "::One" end end describe Puppet::Pops::Parser::Lexer::TOKENS[:NAME] do before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:NAME] } it "should match against lower-case alpha-numeric terms" do @token.regex.should =~ "one-two" end it "should return itself and the value if the matched term is not a keyword" do Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil) @token.convert(stub("lexer"), "myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:NAME], "myval"] end it "should return the keyword token and the value if the matched term is a keyword" do keyword = stub 'keyword', :name => :testing Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub("lexer"), "myval").should == [keyword, "myval"] end it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do keyword = stub 'keyword', :name => :TRUE Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub('lexer'), "true").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], true] end it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do keyword = stub 'keyword', :name => :FALSE Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub('lexer'), "false").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], false] end it "should match against lower-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "one::two" end it "should match against many lower-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "one::two::three::four::five" end it "should match against lower-case alpha-numeric terms prefixed by double colons" do @token.regex.should =~ "::one" end it "should match against nested terms starting with numbers" do @token.regex.should =~ "::1one::2two::3three" end end describe Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] do before do @token = Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] @regex = @token.regex end it "should match against numeric terms" do @regex.should =~ "2982383139" end it "should match against float terms" do @regex.should =~ "29823.235" end it "should match against hexadecimal terms" do @regex.should =~ "0xBEEF0023" end it "should match against float with exponent terms" do @regex.should =~ "10e23" end it "should match against float terms with negative exponents" do @regex.should =~ "10e-23" end it "should match against float terms with fractional parts and exponent" do @regex.should =~ "1.234e23" end end describe Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] do before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] } it "should match against lines starting with '#'" do @token.regex.should =~ "# this is a comment" end it "should be marked to get skipped" do @token.skip?.should be_true end it "'s block should return the comment without the #" do @token.convert(@lexer,"# this is a comment")[1].should == "this is a comment" end end describe Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] do before do @token = Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] @lexer = stub 'lexer', :line => 0 end it "should match against lines enclosed with '/*' and '*/'" do @token.regex.should =~ "/* this is a comment */" end it "should match multiple lines enclosed with '/*' and '*/'" do @token.regex.should =~ """/* this is a comment */""" end # # TODO: REWRITE THIS TEST TO NOT BE BASED ON INTERNALS # it "should increase the lexer current line number by the amount of lines spanned by the comment" do # @lexer.expects(:line=).with(2) # @token.convert(@lexer, "1\n2\n3") # end it "should not greedily match comments" do match = @token.regex.match("/* first */ word /* second */") match[1].should == " first " end it "'s block should return the comment without the comment marks" do @lexer.stubs(:line=).with(0) @token.convert(@lexer,"/* this is a comment */")[1].should == "this is a comment" end end describe Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] do before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] } it "should match against carriage returns" do @token.regex.should =~ "\n" end it "should be marked to initiate text skipping" do @token.skip_text.should be_true end end shared_examples_for "handling `-` in standard variable names for egrammar" do |prefix| # Watch out - a regex might match a *prefix* on these, not just the whole # word, so make sure you don't have false positive or negative results based # on that. legal = %w{f foo f::b foo::b f::bar foo::bar 3 foo3 3foo} illegal = %w{f- f-o -f f::-o f::o- f::o-o} ["", "::"].each do |global_scope| legal.each do |name| var = prefix + global_scope + name it "should accept #{var.inspect} as a valid variable name" do (subject.regex.match(var) || [])[0].should == var end end illegal.each do |name| var = prefix + global_scope + name it "when `variable_with_dash` is disabled it should NOT accept #{var.inspect} as a valid variable name" do Puppet[:allow_variables_with_dashes] = false (subject.regex.match(var) || [])[0].should_not == var end it "when `variable_with_dash` is enabled it should NOT accept #{var.inspect} as a valid variable name" do Puppet[:allow_variables_with_dashes] = true (subject.regex.match(var) || [])[0].should_not == var end end end end describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do its(:skip_text) { should be_false } it_should_behave_like "handling `-` in standard variable names for egrammar", '$' end describe Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE] do its(:skip_text) { should be_false } it_should_behave_like "handling `-` in standard variable names for egrammar", '' end describe "the horrible deprecation / compatibility variables with dashes" do ENamesWithDashes = %w{f- f-o -f f::-o f::o- f::o-o} { Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR_WITH_DASH] => '$', Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE_WITH_DASH] => '' }.each do |token, prefix| describe token do its(:skip_text) { should be_false } context "when compatibly is disabled" do before :each do Puppet[:allow_variables_with_dashes] = false end Puppet::Pops::Parser::Lexer::TOKENS.each do |name, value| it "should be unacceptable after #{name}" do token.acceptable?(:after => name).should be_false end end # Yes, this should still *match*, just not be acceptable. ENamesWithDashes.each do |name| ["", "::"].each do |global_scope| var = prefix + global_scope + name it "should match #{var.inspect}" do subject.regex.match(var).to_a.should == [var] end end end end context "when compatibility is enabled" do before :each do Puppet[:allow_variables_with_dashes] = true end it "should be acceptable after DQPRE" do token.acceptable?(:after => :DQPRE).should be_true end ENamesWithDashes.each do |name| ["", "::"].each do |global_scope| var = prefix + global_scope + name it "should match #{var.inspect}" do subject.regex.match(var).to_a.should == [var] end end end end end end context "deprecation warnings" do before :each do Puppet[:allow_variables_with_dashes] = true end it "should match a top level variable" do Puppet.expects(:deprecation_warning).once EgrammarLexerSpec.tokens_scanned_from('$foo-bar').should == [ [:VARIABLE, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>8}] ] end it "does not warn about a variable without a dash" do Puppet.expects(:deprecation_warning).never EgrammarLexerSpec.tokens_scanned_from('$c').should == [ [:VARIABLE, {:value=>"c", :line=>1, :pos=>1, :offset=>0, :length=>2}] ] end it "does not warn about referencing a class name that contains a dash" do Puppet.expects(:deprecation_warning).never EgrammarLexerSpec.tokens_scanned_from('foo-bar').should == [ [:NAME, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>7}] ] end it "warns about reference to variable" do Puppet.expects(:deprecation_warning).once EgrammarLexerSpec.tokens_scanned_from('$::foo-bar::baz-quux').should == [ [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>1, :offset=>0, :length=>20}] ] end it "warns about reference to variable interpolated in a string" do Puppet.expects(:deprecation_warning).once EgrammarLexerSpec.tokens_scanned_from('"$::foo-bar::baz-quux"').should == [ [:DQPRE, {:value=>"", :line=>1, :pos=>1, :offset=>0, :length=>2}], # length since preamble includes start and terminator [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>3, :offset=>2, :length=>19}], [:DQPOST, {:value=>"", :line=>1, :pos=>22, :offset=>21, :length=>1}], ] end it "warns about reference to variable interpolated in a string as an expression" do Puppet.expects(:deprecation_warning).once EgrammarLexerSpec.tokens_scanned_from('"${::foo-bar::baz-quux}"').should == [ [:DQPRE, {:value=>"", :line=>1, :pos=>1, :offset=>0, :length=>3}], [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>4, :offset=>3, :length=>19}], [:DQPOST, {:value=>"", :line=>1, :pos=>23, :offset=>22, :length=>2}], ] end end end describe Puppet::Pops::Parser::Lexer,"when lexing strings" do { %q{'single quoted string')} => [[:STRING,'single quoted string']], %q{"double quoted string"} => [[:STRING,'double quoted string']], %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']], %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']], %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']], %q{'single quoted string with an escaped "\r\n"'} => [[:STRING,'single quoted string with an escaped "\r\n"']], %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']], %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']], %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]], %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]], %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]], %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']], %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']], %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']], %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]], %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]], %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']], %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]], %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]], %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]], %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']], %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]], %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']], %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]], %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]], %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]], %q["$$$$"] => [[:STRING,"$$$$"]], %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]], %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]], %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]], %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]], %q[""] => [[:STRING,""]], %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]], %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]], %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]] }.each { |src,expected_result| it "should handle #{src} correctly" do EgrammarLexerSpec.tokens_scanned_from(src).should be_like(*expected_result) end } end describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] } it "should match against alpha words prefixed with '$'" do @token.regex.should =~ '$this_var' end it "should return the VARIABLE token and the variable name stripped of the '$'" do @token.convert(stub("lexer"), "$myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE], "myval"] end end describe Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] do before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] } it "should match against any expression enclosed in //" do @token.regex.should =~ '/this is a regex/' end it 'should not match if there is \n in the regex' do @token.regex.should_not =~ "/this is \n a regex/" end describe "when scanning" do it "should not consider escaped slashes to be the end of a regex" do EgrammarLexerSpec.tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}]) end it "should not lex chained division as a regex" do EgrammarLexerSpec.tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX ) end it "should accept a regular expression after NODE" do EgrammarLexerSpec.tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")]) end it "should accept regular expressions in a CASE" do s = %q{case $variable { "something": {$othervar = 4096 / 2} /regex/: {notice("this notably sucks")} } } EgrammarLexerSpec.tokens_scanned_from(s).should be_like( :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE ) end end it "should return the REGEX token and a Regexp" do @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Pops::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)] end end describe Puppet::Pops::Parser::Lexer, "when lexing comments" do before { @lexer = Puppet::Pops::Parser::Lexer.new } it "should skip whitespace before lexing the next token after a non-token" do EgrammarLexerSpec.tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"]) end end # FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now. describe "Puppet::Pops::Parser::Lexer in the old tests" do before { @lexer = Puppet::Pops::Parser::Lexer.new } it "should do simple lexing" do { %q{\\} => [[:BACKSLASH,"\\"]], %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]], %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]] }.each { |source,expected| EgrammarLexerSpec.tokens_scanned_from(source).should be_like(*expected) } end it "should fail usefully" do expect { EgrammarLexerSpec.tokens_scanned_from('^') }.to raise_error(RuntimeError) end it "should fail if the string is not set" do expect { @lexer.fullscan }.to raise_error(Puppet::LexError) end it "should correctly identify keywords" do EgrammarLexerSpec.tokens_scanned_from("case").should be_like([:CASE, "case"]) end it "should correctly parse class references" do %w{Many Different Words A Word}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF,t])} end # #774 it "should correctly parse namespaced class refernces token" do %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF, t]) } end it "should correctly parse names" do %w{this is a bunch of names}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) } end it "should correctly parse names with numerals" do %w{1name name1 11names names11}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) } end it "should correctly parse empty strings" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = ""') }.to_not raise_error end it "should correctly parse virtual resources" do EgrammarLexerSpec.tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"]) end it "should correctly deal with namespaces" do @lexer.string = %{class myclass} @lexer.fullscan @lexer.namespace.should == "myclass" @lexer.namepop @lexer.namespace.should == "" @lexer.string = "class base { class sub { class more" @lexer.fullscan @lexer.namespace.should == "base::sub::more" @lexer.namepop @lexer.namespace.should == "base::sub" end it "should not put class instantiation on the namespace" do @lexer.string = "class base { class sub { class { mode" @lexer.fullscan @lexer.namespace.should == "base::sub" end it "should correctly handle fully qualified names" do @lexer.string = "class base { class sub::more {" @lexer.fullscan @lexer.namespace.should == "base::sub::more" @lexer.namepop @lexer.namespace.should == "base" end it "should correctly lex variables" do ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string| EgrammarLexerSpec.tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')]) end end it "should end variables at `-`" do EgrammarLexerSpec.tokens_scanned_from('$hyphenated-variable'). should be_like [:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable'] end it "should not include whitespace in a variable" do EgrammarLexerSpec.tokens_scanned_from("$foo bar").should_not be_like([:VARIABLE, "foo bar"]) end it "should not include excess colons in a variable" do EgrammarLexerSpec.tokens_scanned_from("$foo::::bar").should_not be_like([:VARIABLE, "foo::::bar"]) end end describe "Puppet::Pops::Parser::Lexer in the old tests when lexing example files" do my_fixtures('*.pp') do |file| it "should correctly lex #{file}" do lexer = Puppet::Pops::Parser::Lexer.new lexer.file = file expect { lexer.fullscan }.to_not raise_error end end end describe "when trying to lex an non-existent file" do include PuppetSpec::Files it "should return an empty list of tokens" do lexer = Puppet::Pops::Parser::Lexer.new lexer.file = nofile = tmpfile('lexer') File.exists?(nofile).should == false lexer.fullscan.should == [[false,false]] end end describe "when string quotes are not closed" do it "should report with message including an \" opening quote" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/after '"'/) end it "should report with message including an \' opening quote" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = \'') }.to raise_error(/after "'"/) end it "should report <eof> if immediately followed by eof" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/followed by '<eof>'/) end it "should report max 5 chars following quote" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/followed by '12345...'/) end it "should escape control chars" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = "12\n3456') }.to raise_error(/followed by '12\\n3...'/) end it "should resport position of opening quote" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:8/) expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:9/) end end describe "when lexing number, bad input should not go unpunished" do it "should slap bad octal as such" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0778') }.to raise_error(/Not a valid octal/) end it "should slap bad hex as such" do expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xFG') }.to raise_error(/Not a valid hex/) expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xfg') }.to raise_error(/Not a valid hex/) end # Note, bad decimals are probably impossible to enter, as they are not recognized as complete numbers, instead, # the error will be something else, depending on what follows some initial digit. # end describe "when lexing interpolation detailed positioning should be correct" do it "should correctly position a string without interpolation" do EgrammarLexerSpec.tokens_scanned_from('"not interpolated"').should be_like( [:STRING, {:value=>"not interpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}]) end it "should correctly position a string with false start in interpolation" do EgrammarLexerSpec.tokens_scanned_from('"not $$$ rpolated"').should be_like( [:STRING, {:value=>"not $$$ rpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}]) end it "should correctly position pre-mid-end interpolation " do EgrammarLexerSpec.tokens_scanned_from('"pre $x mid $y end"').should be_like( [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>6}], [:VARIABLE, {:value=>"x", :line=>1, :offset=>6, :pos=>7, :length=>1}], [:DQMID, {:value=>" mid ", :line=>1, :offset=>7, :pos=>8, :length=>6}], [:VARIABLE, {:value=>"y", :line=>1, :offset=>13, :pos=>14, :length=>1}], [:DQPOST, {:value=>" end", :line=>1, :offset=>14, :pos=>15, :length=>5}] ) end it "should correctly position pre-mid-end interpolation using ${} " do EgrammarLexerSpec.tokens_scanned_from('"pre ${x} mid ${y} end"').should be_like( [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}], [:DQMID, {:value=>" mid ", :line=>1, :offset=>8, :pos=>9, :length=>8}], [:VARIABLE, {:value=>"y", :line=>1, :offset=>16, :pos=>17, :length=>1}], [:DQPOST, {:value=>" end", :line=>1, :offset=>17, :pos=>18, :length=>6}] ) end it "should correctly position pre-end interpolation using ${} with f call" do EgrammarLexerSpec.tokens_scanned_from('"pre ${x()} end"').should be_like( [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], [:NAME, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}], [:LPAREN, {:value=>"(", :line=>1, :offset=>8, :pos=>9, :length=>1}], [:RPAREN, {:value=>")", :line=>1, :offset=>9, :pos=>10, :length=>1}], [:DQPOST, {:value=>" end", :line=>1, :offset=>10, :pos=>11, :length=>6}] ) end it "should correctly position pre-end interpolation using ${} with $x" do EgrammarLexerSpec.tokens_scanned_from('"pre ${$x} end"').should be_like( [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>2}], [:DQPOST, {:value=>" end", :line=>1, :offset=>9, :pos=>10, :length=>6}] ) end it "should correctly position pre-end interpolation across lines" do EgrammarLexerSpec.tokens_scanned_from(%Q["pre ${\n$x} end"]).should be_like( [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}], [:VARIABLE, {:value=>"x", :line=>2, :offset=>8, :pos=>1, :length=>2}], [:DQPOST, {:value=>" end", :line=>2, :offset=>10, :pos=>3, :length=>6}] ) end it "should correctly position interpolation across lines when strings have embedded newlines" do EgrammarLexerSpec.tokens_scanned_from(%Q["pre \n\n${$x}\n mid$y"]).should be_like( [:DQPRE, {:value=>"pre \n\n", :line=>1, :offset=>0, :pos=>1, :length=>9}], [:VARIABLE, {:value=>"x", :line=>3, :offset=>9, :pos=>3, :length=>2}], [:DQMID, {:value=>"\n mid", :line=>3, :offset=>11, :pos=>5, :length=>7}], [:VARIABLE, {:value=>"y", :line=>4, :offset=>18, :pos=>6, :length=>1}] ) end end