require 'test/unit'
require_relative '../lib/sexp_path'

class SexpPathCaptureTest < Test::Unit::TestCase  
  def setup
    @ast_sexp = # Imagine it looks like a ruby AST
      s(:class, :cake, 
        s(
          s(:defn, :foo, 
            s(:add, :a, :b)
          ),
          s(:defn, :bar, 
            s(:sub, :a, :b)
          )
        )
      )
  end
  
  def test_match_with_no_capture_defined_should_not_capture
    sexp = s(:a, :b, :c)
    assert_equal({}, Q?{ sexp }.satisfy?(sexp))
  end
  
  def test_match_with_capture_defined_should_capture
    sexp = s(:a, :b, :c)
    assert res = Q?{ sexp % 'cake' }.satisfy?(sexp)
    assert_equal(sexp, res['cake'])
  end
  
  def test_match_with_multiple_captures
    assert res = Q?{ s(:add, atom % 'A', atom % 'B') }.satisfy?( s(:add, :a, :b) )
    assert_equal(:a, res['A'])
    assert_equal(:b, res['B'])
  end
  
  def test_deep_matches
    assert res = Q?{ s(:class, atom % 'name', s( _ % 'def1', _ % 'def2')) }.satisfy?( @ast_sexp )
    assert_equal(:cake, res['name'])
    assert_equal(s(:defn, :foo, s(:add, :a, :b)), res['def1'])
    assert_equal(s(:defn, :bar, s(:sub, :a, :b)), res['def2'])
  end
  
  def test_simple_searching
    res = @ast_sexp / Q?{ s(:class, atom % 'name', _) }
    assert_equal 1, res.length
    assert_equal :cake, res.first['name']
  end
  
  def test_iterative_searching
    result = @ast_sexp / Q?{ s(:class, atom % 'class', _) } / Q?{ s(:defn, atom % 'method', _) }
    assert_equal 2, result.length, "Should have matched both defn nodes"
    
    result.each do |match_data|
      assert match_data['method'], "Should have captured to 'method'"
      assert match_data['class'], "Should have propogated 'class' capture"
    end
  end
  
  def test_capturing_any_matchers
    sexp = s(:add, :a, :b)
    assert res = Q?{ any(s(:add, :a, :b), s(:sub, :a, :b)) % 'match' }.satisfy?( sexp )
    assert_equal sexp, res['match']
    
    assert res = Q?{ any(s(atom % 'name', :a, :b), s(:sub, :a, :b)) % 'match' }.satisfy?( sexp )
    assert_equal sexp, res['match']
    assert_equal :add, res['name']
  end
  
  def test_capturing_all_matchers
    sexp = s(:add, :a, :b)
    assert res = Q?{ all(s(_, :a, :b), s(atom, :a, :b)) % 'match' }.satisfy?( sexp )
    assert_equal sexp, res['match']
    
    assert res = Q?{ all(s(_ % 'wild', :a, :b), s(atom % 'atom', :a, :b)) % 'match' }.satisfy?( sexp )
    assert_equal sexp, res['match']
    assert_equal :add, res['wild']
    assert_equal :add, res['atom']
  end
  
  def test_capturing_type_matches
    sexp = s(:add, :a, :b)
    assert res = Q?{ t(:add) % 'match' }.satisfy?( sexp )
    assert_equal sexp, res['match']
  end
  
  def test_capturing_child_matches
    sexp = s(:a, s(:b, s(:c)))
    assert res = Q?{ s(:a, child( s(atom % 'atom') ) % 'child' ) }.satisfy?( sexp )
    assert_equal s(:b, s(:c)), res['child']
    assert_equal :c, res['atom']
  end
  
  def test_catpuring_pattern_matches
    sexp = s(:add, :a, :b)
    assert res = Q?{ s(m(/a../) % 'regexp', :a, :b) }.satisfy?( sexp )
    assert_equal :add, res['regexp']
  end
  
  def test_catpuring_include_matches
    sexp = s(:add, :a, :b)
    assert res = Q?{ include(:a) % 'include' }.satisfy?( sexp )
    assert_equal sexp, res['include']
  end
  
  def test_catpuring_nested_include_matches
    sexp = s(:add, s(:a), :b)
    assert res = Q?{ include(s(atom % 'atom' )) % 'include' }.satisfy?( sexp )
    assert_equal sexp, res['include']
    assert_equal :a, res['atom']
  end
  
  def test_capturing_negations
    sexp = s(:b)
    assert res = Q?{ (-s(:a)) % 'not' }.satisfy?( sexp )
    assert_equal s(:b), res['not']
  end
  
  def test_capturing_negation_contents
    sexp = s(:a, :b)
    assert res = Q?{ -((include(:b) % 'b') & t(:c)) }.satisfy?( sexp )
    assert !res['b'], 'b should not be included'
  end
  
  def test_capturing_siblings
    sexp = s(s(:a), s(s(:b)), s(:c))
    assert res = Q?{ (s(atom) % 'a') >> (s(atom) % 'c') }.satisfy?( sexp )
    assert_equal s(:a), res['a']
    assert_equal s(:c), res['c']
  end
  
  def test_capturing_remaining
    sexp = s(s(:a), s(:b), s(:c))
    assert res = Q?{ s(s(:a), ___ % 'match') }.satisfy?( sexp )
    assert_equal [s(:b), s(:c)], res['match']
  end
  
  def test_capturing_remaining_atoms
    sexp = s(:a, :b, :c)
    assert res = Q?{ s(:a, ___ % 'match') }.satisfy?( sexp )
    assert_equal [:b, :c], res['match']
  end
  
end