defmodule LispTest do
  use ExUnit.Case

  describe "tokenize tokenizes" do
    test "left-paren" do
      assert {:lp, ""} = Lisp.tokenize("(")
      assert {:lp, " "} = Lisp.tokenize("( ")
      assert {:lp, "foo"} = Lisp.tokenize("(foo")
    end

    test "right-paren" do
      assert {:rp, ""} = Lisp.tokenize(")")
      assert {:rp, " "} = Lisp.tokenize(") ")
    end

    test "spaces are ignored" do
      assert {nil, ""} = Lisp.tokenize(" ")
      assert {nil, " "} = Lisp.tokenize("  ")
    end

    test "identifiers" do
      assert {"foo", ""} = Lisp.tokenize("foo")
      assert {"foo", " "} = Lisp.tokenize("foo ")
      assert {"foo", " bar"} = Lisp.tokenize("foo bar")
      assert {"foo", " 1"} = Lisp.tokenize("foo 1")
      assert {"foo2", ""} = Lisp.tokenize("foo2")
    end

    test "numbers" do
      assert {1, ""} = Lisp.tokenize("1")
      assert {1, " "} = Lisp.tokenize("1 ")
      assert {47, " foo"} = Lisp.tokenize("47 foo")
    end

    test "operators" do
      assert {"+", ""} = Lisp.tokenize("+")
      assert {"+", " "} = Lisp.tokenize("+ ")
      assert {"+", "foo"} = Lisp.tokenize("+foo")
      assert {"+", "47"} = Lisp.tokenize("+47")
    end
  end

  describe "string_to_tokens/1" do
    test "puts together the tokens" do
      assert [:lp, "foo", :rp] == Lisp.string_to_tokens("(foo)")
      assert [:lp, "foo", :rp] == Lisp.string_to_tokens("( foo )")
      assert [:lp, "foo", 47, :rp] == Lisp.string_to_tokens("(foo 47)")
      assert [:lp, "foo47", :rp] == Lisp.string_to_tokens("(foo47)")
    end

    test "syntax errors are not detected" do
      assert [:lp, :lp] == Lisp.string_to_tokens("((")
      assert [:rp, :lp] == Lisp.string_to_tokens(")(")
    end
  end

  describe "when sent through the parser" do
    test "empty list is empty list" do
      assert [] == Lisp.parse("()")
    end

    test "an basic token is a basic token" do
      assert 47 == Lisp.parse("47")
      assert "foo" == Lisp.parse("foo")
      assert "+" == Lisp.parse("+")
    end

    test "lists work" do
      assert [] == Lisp.parse("()")
      assert ["foo", "bar"] == Lisp.parse("(foo bar)")
    end

    test "nested lists work" do
      assert [["foo", "bar"], 3] == Lisp.parse("((foo bar) 3)")
    end

    test "wierd stuff causes syntax error" do
      assert_raise RuntimeError, fn -> Lisp.parse("(()") end
      assert_raise RuntimeError, fn -> Lisp.parse("())") end
      assert_raise RuntimeError, fn -> Lisp.parse("foo bar") end
    end

    test "the example parses" do
      assert ["first", ["list", 1, ["+", 2, 3], 9]] ==
        Lisp.parse("(first (list 1 (+ 2 3) 9))")
    end
  end
end