Repository: fika-lang/fika
Pull request #16 (2020-10-02 06:06:54)

feat: support atoms
This PR aims to support ~literals~ atoms as defined by  #11 and discussed on the Discord channel.
I noticed that the code wasn't formatted with the standard formatter, so some "format-on-save"s might have slipped by me.

Closes  #11


Patch:
diff --git a/README.md b/README.md
index d20e027..17840f9 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ end
 Fika is currently a proof of concept so its data types and operators are quite
 limited.
 
-Data types - integers, strings, booleans, lists and records.
+Data types - atoms, integers, strings, booleans, lists and records.
 Operators - assignment and arithmetic.
 
 ```elixir
diff --git a/lib/fika/erl_translate.ex b/lib/fika/erl_translate.ex
index eaf7e2e..0a8a773 100644
--- a/lib/fika/erl_translate.ex
+++ b/lib/fika/erl_translate.ex
@@ -59,6 +59,10 @@ defmodule Fika.ErlTranslate do
     {:atom, line, value}
   end
 
+  defp translate_exp({:atom, {line, _, _}, value}) do
+    {:atom, line, value}
+  end
+  
   defp translate_exp({{:=, {line, _, _}}, pattern, exp}) do
     {:match, line, translate_exp(pattern), translate_exp(exp)}
   end
diff --git a/lib/fika/parser.ex b/lib/fika/parser.ex
index 9dc8e39..04e71d1 100644
--- a/lib/fika/parser.ex
+++ b/lib/fika/parser.ex
@@ -74,6 +74,12 @@ defmodule Fika.Parser do
     |> label("boolean")
     |> Helper.to_ast(:boolean)
 
+  atom =
+    ignore(string(":"))
+    |> concat(identifier)
+    |> label("atom")
+    |> Helper.to_ast(:atom)
+
   string_exp =
     ignore(string("\""))
     |> repeat(choice([string("\\\""), utf8_char([not: ?"])]))
@@ -226,6 +232,7 @@ defmodule Fika.Parser do
       exp_list,
       record,
       function_ref,
+      atom,
       exp_if_else
     ])
 
@@ -319,7 +326,6 @@ defmodule Fika.Parser do
     )
     |> reduce({Enum, :join, [","]})
 
-
   record_type =
     string("{")
     |> concat(type_key_values)
@@ -340,11 +346,10 @@ defmodule Fika.Parser do
   type =
     choice([
       function_type,
-
       simple_type
       |> optional(type_parens),
-
-      record_type
+      record_type,
+      atom
     ])
 
   parse_type =
@@ -375,7 +380,6 @@ defmodule Fika.Parser do
       |> wrap(args)
       |> concat(allow_space)
       |> ignore(string(")")),
-
       empty() |> wrap()
     ])
 
diff --git a/lib/fika/parser_helper.ex b/lib/fika/parser_helper.ex
index 5144d40..51b6765 100644
--- a/lib/fika/parser_helper.ex
+++ b/lib/fika/parser_helper.ex
@@ -57,7 +57,14 @@ defmodule Fika.ParserHelper do
   end
 
   def do_to_ast({types, line}, :type) do
-    type = Enum.join(types)
+    type =
+      Enum.reduce(types, "", fn
+        {:atom, _l, value}, acc ->
+          acc <> ":#{value}"
+
+        type, acc ->
+          acc <> type
+      end)
 
     {:type, line, type}
   end
@@ -121,6 +128,10 @@ defmodule Fika.ParserHelper do
     end
   end
 
+  def do_to_ast({[{:identifier, line, value}], line}, :atom) do
+    {:atom, line, value}
+  end
+  
   defp value_from_identifier({:identifier, _line, value}) do
     value
   end
diff --git a/lib/fika/type_checker.ex b/lib/fika/type_checker.ex
index 4518385..dea1889 100644
--- a/lib/fika/type_checker.ex
+++ b/lib/fika/type_checker.ex
@@ -197,6 +197,12 @@ defmodule Fika.TypeChecker do
     end
   end
 
+  # Atom value
+  def infer_exp(env, {:atom, _line, atom}) do
+    Logger.debug("Atom value found. Type: #{atom}")
+    {:ok, ":#{atom}", env}
+  end
+
   # if-else expression
   def infer_exp(env, {{:if, _line}, condition, if_block, else_block}) do
     Logger.debug "Inferring an if-else expression"


Test patch:
diff --git a/test/fika/erl_translate_test.exs b/test/fika/erl_translate_test.exs
index b89063e..7b76179 100644
--- a/test/fika/erl_translate_test.exs
+++ b/test/fika/erl_translate_test.exs
@@ -95,12 +95,32 @@ defmodule Fika.ErlTranslateTest do
     end
   end
 
-  test "boolean" do
-    str = "true"
+  describe "boolean" do
+    Enum.each([true, false], fn bool ->
+      test "#{bool} as boolean" do
+        str = "#{unquote(bool)}"
+        ast = Parser.expression!(str)
+        result = ErlTranslate.translate_expression(ast)
+
+        assert result == {:atom, 1, unquote(bool)}
+      end
+
+      test "#{bool} as atom" do
+        str = ":#{unquote(bool)}"
+        ast = Parser.expression!(str)
+        result = ErlTranslate.translate_expression(ast)
+
+        assert result == {:atom, 1, unquote(bool)}
+      end
+    end)
+  end
+
+  test "atom" do
+    str = ":foo"
     ast = Parser.expression!(str)
     result = ErlTranslate.translate_expression(ast)
 
-    assert result == {:atom, 1, true}
+    assert result == {:atom, 1, :foo}
   end
 
   test "if-else expression" do
diff --git a/test/fika/parser_test.exs b/test/fika/parser_test.exs
index 78ac303..b3fd603 100644
--- a/test/fika/parser_test.exs
+++ b/test/fika/parser_test.exs
@@ -26,6 +26,16 @@ defmodule Fika.ParserTest do
       assert result ==  {:boolean, {1, 0, 5}, false}
     end
   end
+
+  describe "atom" do
+    test "parses multi-char atoms" do
+      atom = :foobar
+      str = ":#{atom}"
+
+      assert Parser.expression!(str) == {:atom, {1, 0, 7}, atom}
+    end
+  end
+
   describe "arithmetic" do
     test "arithmetic with add and mult" do
       str = """
@@ -160,7 +170,7 @@ defmodule Fika.ParserTest do
       ]
     end
 
-    test "with return type" do
+    test "with return type Int" do
       str = """
       fn foo : Int do
         123
@@ -170,9 +180,24 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {3, 22, 25}],
-          {:foo, [], {:type, {1, 0, 12}, "Int"}, [{:integer, {2, 16, 21}, 123}]}}
-      ]
+                {:function, [position: {3, 22, 25}],
+                {:foo, [], {:type, {1, 0, 12}, "Int"}, [{:integer, {2, 16, 21}, 123}]}}
+            ]
+    end
+
+    test "with return type :ok" do
+      str = """
+      fn foo : :ok do
+        123
+      end
+      """
+
+      {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
+
+      assert result == [
+                {:function, [position: {3, 22, 25}],
+                {:foo, [], {:type, {1, 0, 12}, ":ok"}, [{:integer, {2, 16, 21}, 123}]}}
+            ]
     end
 
     test "with type params" do
@@ -448,6 +473,20 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.type_str(str)
       assert result == [{:type, {1, 0, 23}, "{foo:Int,bar:String}"}]
     end
+
+     test "atom type" do
+      str = ":foo"
+
+      {:ok, result, _rest, _context, _line, _byte_offset} = Parser.type_str(str)
+      assert result == [{:type, {1, 0, 4}, ":foo"}]
+    end
+
+    test "list of atom" do
+      str = "List(:foo)"
+
+      {:ok, result, _rest, _context, _line, _byte_offset} = Parser.type_str(str)
+      assert result == [{:type, {1, 0, 10}, "List(:foo)"}]
+    end
   end
 
   describe "function reference" do
diff --git a/test/fika/type_checker_test.exs b/test/fika/type_checker_test.exs
index f47356d..5cce937 100644
--- a/test/fika/type_checker_test.exs
+++ b/test/fika/type_checker_test.exs
@@ -14,6 +14,22 @@ defmodule Fika.TypeCheckerTest do
     assert {:ok, "Int", _} = TypeChecker.infer_exp(Env.init(), ast)
   end
 
+  test "infer type of atom expressions" do
+    str = ":a"
+
+    {:atom, {1, 0, 2}, :a} = ast = Fika.Parser.expression!(str)
+
+    assert {:ok, ":a", _} = TypeChecker.infer_exp(Env.init(), ast)
+  end
+
+  test "infer type for list of atom expressions" do
+    str = "[:a, :a]"
+
+    {:ok, [ast], _, _, _, _} = Fika.Parser.expression(str)
+
+    assert {:ok, "List(:a)", _} = TypeChecker.infer_exp(Env.init(), ast)
+  end
+
   test "infer type of arithmetic expressions" do
     str = "1 + 2"