require 'spec_helper'
module Sexpr
  module Grammar
    describe Matching, "compile_rule_defn" do
      include Matching

      subject{ compile_rule_defn(arg, :grammar) }

      def rules
        {:a_rule => "hello"}
      end

      context 'with an Element' do
        let(:arg){ Matcher::Terminal.new(//) }
        it 'returns arg itself' do
          subject.should eq(arg)
        end
      end

      context "with true" do
        let(:arg){ true }
        it 'gives it true' do
          subject.should be_a(Matcher::Terminal)
          subject.value.should eq(true)
        end
      end

      context "with false" do
        let(:arg){ false }
        it 'gives it false' do
          subject.should be_a(Matcher::Terminal)
          subject.value.should eq(false)
        end
      end

      context "with nil" do
        let(:arg){ nil }
        it 'gives it nil' do
          subject.should be_a(Matcher::Terminal)
          subject.value.should eq(nil)
        end
      end

      context "with a Module/Class" do
        let(:arg){ Symbol }
        it 'gives it the class' do
          subject.should be_a(Matcher::Terminal)
          subject.value.should eq(Symbol)
        end
      end

      context "with a single Module/Class name" do
        let(:arg){ '::Symbol' }
        it 'gives it the class' do
          subject.should be_a(Matcher::Terminal)
          subject.value.should eq(Symbol)
        end
      end

      context "with a qualified Module/Class name" do
        let(:arg){ '::Sexpr::Node' }
        it 'gives it the class' do
          subject.should be_a(Matcher::Terminal)
          subject.value.should eq(Sexpr::Node)
        end
      end

      context 'with an alternative array' do
        let(:arg){ [true, false, nil] }
        it 'factors an Alternative' do
          subject.should be_a(Matcher::Alternative)
        end
        it 'compiles its elements' do
          subject.terms.size.should eq(3)
          subject.terms.all?{|x| x.is_a?(Matcher::Terminal)}.should be_truthy
        end
      end

      context 'with a sequence array' do
        let(:arg){ [[true, false, nil]] }
        it 'factors a Sequence' do
          subject.should be_a(Matcher::Sequence)
        end
        it 'compiles its elements' do
          subject.terms.size.should eq(3)
          subject.terms.all?{|x| x.is_a?(Matcher::Terminal)}.should be_truthy
        end
      end

      context 'with subalternatives' do
        let(:arg){ [ ["a_rule", [false, true, nil] ]] }
        it 'compiles the last as an Alternative' do
          subject.terms.last.should be_a(Matcher::Alternative)
        end
      end

      context 'with a reference to a non-terminal' do
        let(:arg){ "a_rule" }
        it 'factors a Reference' do
          subject.should be_a(Matcher::Reference)
        end
        it 'refers to the appropriate rule name' do
          subject.rule_name.should eq(:a_rule)
        end
        it 'refers to the appropriate grammar' do
          subject.grammar.should eq(:grammar)
        end
      end

      context 'with a stared non-terminal' do
        let(:arg){ "a_rule+" }
        it 'factors a Many' do
          subject.should be_a(Matcher::Many)
        end
        it 'refers to the appropriate rule' do
          subject.term.should be_a(Matcher::Reference)
          subject.term.rule_name.should eq(:a_rule)
        end
        it 'refers to the appropriate multiplicities' do
          subject.min.should eq(1)
          subject.max.should be_nil
        end
      end

    end
  end
end