require 'spec_helper'
module WLang
  class Compiler
    class DialectEnforcer
      public :find_dispatching_method
    end
    describe DialectEnforcer do

      let(:dialect){
        WLang::dialect{
          tag('$')  do |buf,fn| buf << "$"  end
          tag('!$') do |buf,fn| buf << "!$" end
          tag('*')  do |buf,fn1,fn2|        end
          tag('%', [WLang::Dummy]) do |buf, fn| end
        }.new
      }

      describe 'find_dispatching_method' do
        let(:e){ DialectEnforcer.new(:dialect => dialect) }
        it 'works on exact matching' do
          e.find_dispatching_method("$").should eq(['', :_tag_36])
        end
        it 'takes the most specific' do
          e.find_dispatching_method("$").should eq(['', :_tag_36])
          e.find_dispatching_method("!$").should eq(['', :_tag_33_36])
        end
        it 'recognizes extras' do
          e.find_dispatching_method("@$").should eq(['@', :_tag_36])
          e.find_dispatching_method("@!$").should eq(['@', :_tag_33_36])
        end
        it 'recognizes missings' do
          e.find_dispatching_method("#").should eq(['#', nil])
          e.find_dispatching_method("@#").should eq(['@#', nil])
        end
      end

      def optimize(source)
        DialectEnforcer.new(:dialect => dialect).call(source)
      end

      let(:unknown){[
        [:wlang, '!', [:fn, [:static, "hello"]]],
        [:strconcat, [:static, "!"], [:static, "{"], [:static, "hello"], [:static, "}"]]
      ]}
      let(:hello_fn){[
        [:fn, unknown.first],
        [:fn, unknown.last]
      ]}
      let(:hello_dollar){[
        [:wlang, '$', hello_fn.first],
        [:wlang, '$', hello_fn.last]
      ]}

      ### recursion rules

      it 'recurses on template' do
        source   = [:template, hello_dollar.first]
        expected = [:template, hello_dollar.last]
        optimize(source).should eq(expected)
      end

      it 'leaves [:static, ...] unchanged' do
        source = [:static, "Hello world"]
        optimize(source).should eq(source)
      end

      it 'leaves [:strconcat, ...] unchanged if no tag' do
        source = [:strconcat, [:static, "Hello "], [:static, "world"]]
        optimize(source).should eq(source)
      end

      it 'recurses on [:strconcat, ...] to optimize further' do
        source   = [:strconcat, unknown.first]
        expected = [:strconcat, unknown.last]
        optimize(source).should eq(expected)
      end

      ### exact symbols

      it 'optimizes on exact symbols' do
        optimize(hello_dollar.first).should eq(hello_dollar.last)
      end

      it 'detects extra symbols' do
        source   = [:wlang, '@$', hello_fn.first]
        expected = [:strconcat, [:static, "@"], hello_dollar.last]
        optimize(source).should eq(expected)
      end

      ### unknown tags

      it 'recognizes unknown tags' do
        optimize(unknown.first).should eq(unknown.last)
      end

      it 'recognizes unknown tags even if enclosed' do
        source   = [:wlang, '$', [:fn, unknown.first]]
        expected = [:wlang, '$', [:fn, unknown.last]]
        optimize(source).should eq(expected)
      end

      ### high-order wlang

      it 'applies dynamic dispatching on high-order wlang' do
        source   = [:wlang, '$', [:fn, hello_dollar.first]]
        expected = [:wlang, '$', [:fn, hello_dollar.last]]
        optimize(source).should eq(expected)
      end

      ### arity 2

      it 'supports tags of arity 2' do
        source   = [:wlang, '*', hello_fn.first, hello_fn.first]
        expected = [:wlang, '*', hello_fn.last, hello_fn.last]
        optimize(source).should eq(expected)
      end

      ### missing blocks

      it 'detects missing blocks on arity 1' do
        source   = [:wlang, '$']
        expected = [:wlang, '$', [:arg, nil]]
        optimize(source).should eq(expected)
      end

      it 'detects missing blocks on arity 2' do
        source   = [:wlang, '*', hello_fn.first]
        expected = [:wlang, '*', hello_fn.last, [:arg, nil]]
        optimize(source).should eq(expected)
      end

      ### trailing blocks

      it 'detects trailing blocks' do
        source   = [:wlang, '$', hello_fn.first, hello_fn.first]
        expected = \
          [:strconcat,
            [:wlang, '$', hello_fn.last],
            [:static, '{'],
            hello_fn.last.last,
            [:static, '}']
          ]
        optimize(source).should eq(expected)
      end

      it 'detects trailing blocks on arity 2' do
        source   = [:wlang, '*', hello_fn.first, hello_fn.first, hello_fn.first]
        expected = \
          [:strconcat,
            [:wlang, '*', hello_fn.last, hello_fn.last],
            [:static, '{'],
            hello_fn.last.last,
            [:static, '}']
          ]
        optimize(source).should eq(expected)
      end

      ### dialect switching

      it 'introduce dialect switching mechanism' do
        source   = [:wlang, '%', hello_fn.first]
        expected = [:wlang, '%', [:modulo, WLang::Dummy, hello_fn.last]]
        optimize(source).should eq(expected)
      end

    end # describe DialectEnforcer
  end # class Compiler
end # module WLang
