Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: recursion support #87

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ import Config

config :fika,
disable_web_server: true

config :logger, level: :error
80 changes: 79 additions & 1 deletion lib/fika/compiler/code_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,29 @@ defmodule Fika.Compiler.CodeServer do
GenServer.call(__MODULE__, {:write_binaries, dest})
end

@doc false
def get_dependency_graph do
GenServer.call(__MODULE__, :get_dependency_graph)
end

@spec set_function_dependency(source :: String.t() | nil, target :: String.t() | nil) ::
:ok | {:error, :cycle_encountered}
def set_function_dependency(source, target) do
GenServer.call(__MODULE__, {:set_function_dependency, source, target})
end

@spec check_cycle(source :: String.t() | nil) ::
:ok | {:error, :cycle_encountered}
def check_cycle(node) do
GenServer.call(__MODULE__, {:check_cycle, node})
end

@spec get_cycle(source :: String.t() | nil) ::
{:ok, list()} | {:error, :no_cycle_found}
def get_cycle(node) do
GenServer.call(__MODULE__, {:get_cycle, node})
end

def init(_) do
state = init_state()

Expand Down Expand Up @@ -123,6 +146,15 @@ defmodule Fika.Compiler.CodeServer do
{:reply, :ok, init_state()}
end

def handle_call(:get_dependency_graph, _from, %{function_dependencies: graph} = state) do
vertices = graph |> :digraph.vertices() |> Enum.sort()
edges = graph |> :digraph.edges() |> Enum.sort()

deps = %{vertices: vertices, edges: edges}

{:reply, deps, state}
end

def handle_call({:get_type, signature}, from, state) do
module = signature.module
function = signature.function
Expand Down Expand Up @@ -150,6 +182,51 @@ defmodule Fika.Compiler.CodeServer do
{:noreply, state}
end

def handle_call({:set_function_dependency, source, target}, _from, state)
when is_nil(source) or is_nil(target) do
{:reply, :ok, state}
end

def handle_call(
{:set_function_dependency, source, target},
_from,
%{function_dependencies: graph} = state
) do
response = __MODULE__.FunctionDependencies.set_function_dependency(graph, source, target)

{:reply, response, state}
end

def handle_call({:get_cycle, node}, _from, state)
when is_nil(node) do
{:reply, {:error, :no_cycle_found}, state}
end

def handle_call(
{:get_cycle, node},
_from,
%{function_dependencies: graph} = state
) do
response = __MODULE__.FunctionDependencies.get_cycle(graph, node)

{:reply, response, state}
end

def handle_call({:check_cycle, node}, _from, state)
when is_nil(node) do
{:reply, :ok, state}
end

def handle_call(
{:check_cycle, node},
_from,
%{function_dependencies: graph} = state
) do
response = __MODULE__.FunctionDependencies.check_cycle(graph, node)

{:reply, response, state}
end

def handle_call(:list_all_types, _from, state) do
{:reply, state.public_functions, state}
end
Expand Down Expand Up @@ -280,7 +357,8 @@ defmodule Fika.Compiler.CodeServer do
parent_pid: nil,
dev_token: Application.get_env(:fika, :dev_token),
remote_endpoint: Application.get_env(:fika, :remote_endpoint, "https://fikaapp.com/code"),
binaries: []
binaries: [],
function_dependencies: __MODULE__.FunctionDependencies.new_graph()
}
|> put_default_types(DefaultTypes.kernel())
|> put_default_types(DefaultTypes.io())
Expand Down
56 changes: 56 additions & 0 deletions lib/fika/compiler/code_server/function_dependencies.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule Fika.Compiler.CodeServer.FunctionDependencies do
alias Fika.Compiler.FunctionSignature

def new_graph, do: :digraph.new()

@spec set_function_dependency(
graph :: :digraph.graph(),
source_function :: FunctionSignature.t(),
sink_function :: FunctionSignature.t()
) :: :ok | {:error, :cycle_encountered}
def set_function_dependency(graph, source_function, sink_function) do
graph
|> add_vertex(source_function)
|> add_vertex(sink_function)
|> add_edge(source_function, sink_function)
|> check_cycle(source_function, sink_function)
end

def get_cycle(graph, function) do
if vertices = :digraph.get_cycle(graph, function) do
{:ok, vertices}
else
{:error, :no_cycle_found}
end
end

def check_cycle(graph, source_function, sink_function \\ nil) do
vertices = :digraph.get_cycle(graph, source_function)

cond do
!vertices ->
:ok

is_nil(sink_function) ->
{:error, :cycle_encountered}

sink_function in vertices ->
{:error, :cycle_encountered}

true ->
:ok
end
end

defp add_vertex(graph, v) do
:digraph.add_vertex(graph, v, v)
graph
end

defp add_edge(graph, source, target) do
edge = {source, target}

:digraph.add_edge(graph, edge, source, target, edge)
graph
end
end
2 changes: 1 addition & 1 deletion lib/fika/compiler/erl_translate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule Fika.Compiler.ErlTranslate do
translate_exp(exp)
end

def erl_module_name(module_name_str) do
def erl_module_name(module_name_str) when is_binary(module_name_str) do
module_name_str
|> String.replace("/", ".")
|> String.to_atom()
Expand Down
2 changes: 2 additions & 0 deletions lib/fika/compiler/function_signature.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule Fika.Compiler.FunctionSignature do
defstruct [:module, :function, :return, types: []]

@type t :: %__MODULE__{}

alias Fika.Compiler.TypeChecker.Types, as: T

defimpl String.Chars, for: __MODULE__ do
Expand Down
8 changes: 8 additions & 0 deletions lib/fika/compiler/parser/helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ defmodule Fika.Compiler.Parser.Helper do
%T.Effect{type: inner_type}
end

def do_to_ast({[{_, _, inner_type}], _line}, :loop_type) when is_struct(inner_type) do
%T.Loop{type: inner_type}
end

def do_to_ast({[inner_type], _line}, :loop_type) do
%T.Loop{type: inner_type}
end

def do_to_ast({inner_types, _line}, :tuple_type) do
%T.Tuple{elements: inner_types}
end
Expand Down
8 changes: 8 additions & 0 deletions lib/fika/compiler/parser/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ defmodule Fika.Compiler.Parser.Types do
identifier_str
|> label("type variable")

loop_type =
ignore(string("Loop("))
|> concat(parsec(:type))
|> ignore(string(")"))
|> label("loop type")
|> Helper.to_ast(:loop_type)

base_type =
choice([
string_type,
Expand All @@ -124,6 +131,7 @@ defmodule Fika.Compiler.Parser.Types do
function_type,
list_type,
effect_type,
loop_type,
record_type,
map_type,
tuple_type,
Expand Down
Loading