Current section
Files
Jump to
Current section
Files
lib/mix/tasks/phi.base.ex
defmodule Mix.Tasks.Phi.Base do
@moduledoc """
Compiles all Phi .phi standard library files, sharing the accumulated type
environment across compilations.
Usage: mix phi.base
"""
use Mix.Task
@shortdoc "Compile Base Library"
def run(_args) do
Mix.Task.run("compile", [])
files = Path.wildcard("priv/**/*.phi") |> Enum.sort()
total = length(files)
IO.puts("\n#{IO.ANSI.bright()}=== Phi priv Compilation Report ===#{IO.ANSI.reset()}")
IO.puts("Found #{total} .phi files\n")
IO.puts("Compiling...\n")
results = compile_in_order(files)
# Summary
passed = Enum.count(results, fn {_, status, _} -> status == :ok end)
failed = total - passed
IO.puts("\n#{IO.ANSI.bright()}=== Summary ===#{IO.ANSI.reset()}")
IO.puts("#{IO.ANSI.green()}PASS: #{passed}/#{total}#{IO.ANSI.reset()}")
if failed > 0 do
IO.puts("#{IO.ANSI.red()}FAIL: #{failed}/#{total}#{IO.ANSI.reset()}")
IO.puts("\n#{IO.ANSI.bright()}Failed files:#{IO.ANSI.reset()}")
results
|> Enum.filter(fn {_, status, _} -> status == :error end)
|> Enum.each(fn {file, _, reason} ->
short_reason = reason |> String.slice(0, 120) |> String.replace("\n", " ")
IO.puts(" #{IO.ANSI.red()}✗#{IO.ANSI.reset()} #{file}: #{short_reason}")
end)
end
IO.puts("")
if failed > 0 do
IO.puts("#{IO.ANSI.bright()}Error categories:#{IO.ANSI.reset()}")
results
|> Enum.filter(fn {_, status, _} -> status == :error end)
|> Enum.map(fn {_, _, reason} -> categorize_error(reason) end)
|> Enum.frequencies()
|> Enum.sort_by(fn {_, count} -> -count end)
|> Enum.each(fn {category, count} ->
IO.puts(" #{count}x #{category}")
end)
IO.puts("")
end
end
# ---------------------------------------------------------------------------
# Compilation with shared environment
# ---------------------------------------------------------------------------
defp compile_in_order(files) do
{results, _final_env} =
Enum.reduce(files, {[], Phi.Typechecker.Env.new()}, fn file, {acc, env} ->
{status, reason, new_env} = compile_file(file, env)
label =
if status == :ok,
do: "#{IO.ANSI.green()}✓#{IO.ANSI.reset()}",
else: "#{IO.ANSI.red()}✗#{IO.ANSI.reset()}"
mod_hint =
file
|> String.replace_prefix("priv/", "")
|> String.replace_suffix(".phi", "")
|> String.replace("/", ".")
IO.puts(" #{label} #{file} (#{mod_hint})")
{[{file, status, reason} | acc], new_env}
end)
Enum.reverse(results)
end
defp compile_file(file, env) do
result =
try do
source = File.read!(file)
case Phi.Compiler.compile_module(source, source_path: file, env: env) do
{:ok, mod, bin, _ast, new_env} ->
Phi.Compiler.load_module(mod, bin)
{:ok, "", new_env}
{:error, reason} ->
{:error, inspect(reason), env}
other ->
{:error, inspect(other), env}
end
rescue
e -> {:error, "#{inspect(e.__struct__)}: #{Exception.message(e)}", env}
catch
kind, value -> {:error, "#{kind}: #{inspect(value)}", env}
end
case result do
{:ok, _, new_env} -> {:ok, "", new_env}
{:error, reason, kept_env} -> {:error, reason, kept_env}
end
end
# ---------------------------------------------------------------------------
# Error categorisation
# ---------------------------------------------------------------------------
defp categorize_error(reason) do
cond do
String.contains?(reason, "FunctionClauseError") -> "FunctionClauseError"
String.contains?(reason, "MatchError") -> "MatchError"
String.contains?(reason, "erl_compile") -> "Erlang compile error"
String.contains?(reason, "erl_lint") -> "Erlang lint error"
String.contains?(reason, "parse") or String.contains?(reason, "Parser") -> "Parse error"
String.contains?(reason, "KeyError") -> "KeyError"
String.contains?(reason, "UndefinedFunctionError") -> "UndefinedFunctionError"
String.contains?(reason, "CaseClauseError") -> "CaseClauseError"
String.contains?(reason, "BadMapError") -> "BadMapError"
true -> "Other"
end
end
end