From ba63ed35921ae14b485d612181e8afa23d7a53fe Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Wed, 18 Feb 2026 09:25:22 -0500 Subject: [PATCH] feat: benchmark against Luerl and C Lua This PR adds some benchmarks for getting an initial performance baseline. We have no implemented garbage collection or any performance optimizations yet, so there should be a lot of low-hanging fruit for improvement --- benchmarks/closures.exs | 88 +++++++++++++++++++ benchmarks/fibonacci.exs | 66 ++++++++++++++ benchmarks/oop.exs | 99 +++++++++++++++++++++ benchmarks/scripts/main.lua | 2 + benchmarks/string_ops.exs | 100 +++++++++++++++++++++ benchmarks/table_ops.exs | 169 ++++++++++++++++++++++++++++++++++++ mix.exs | 5 +- mix.lock | 5 ++ 8 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 benchmarks/closures.exs create mode 100644 benchmarks/fibonacci.exs create mode 100644 benchmarks/oop.exs create mode 100644 benchmarks/scripts/main.lua create mode 100644 benchmarks/string_ops.exs create mode 100644 benchmarks/table_ops.exs diff --git a/benchmarks/closures.exs b/benchmarks/closures.exs new file mode 100644 index 0000000..b78a209 --- /dev/null +++ b/benchmarks/closures.exs @@ -0,0 +1,88 @@ +# Run with: mix run benchmarks/closures.exs +# +# Benchmarks closures and upvalue capture. +# Creates 100 counter closures via a factory function, each starting at a different value, +# then calls each counter 10 times, accumulating the sum. +# +# Compares: +# - This Lua implementation (eval with string, eval with pre-compiled chunk) +# - Luerl (Erlang-based Lua 5.3 implementation) +# - C Lua 5.4 via luaport (port-based; results include IPC overhead) +# +# NOTE: luaport requires C Lua development headers. On macOS with Homebrew: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport +# Then run: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/closures.exs + +Application.ensure_all_started(:luerl) + +closure_def = """ +function make_counter(start) + local count = start + return function() + count = count + 1 + return count + end +end + +function run_closures(n) + local counters = {} + for i = 1, n do + counters[i] = make_counter(i) + end + local sum = 0 + for j = 1, n do + local counter = counters[j] + for k = 1, 10 do + sum = sum + counter() + end + end + return sum +end +""" + +call_closures = "return run_closures(100)" + +# --- This Lua implementation --- +lua = Lua.new() +{_, lua} = Lua.eval!(lua, closure_def) +{closure_chunk, _} = Lua.load_chunk!(lua, call_closures) + +# --- Luerl --- +luerl_state = :luerl.init() +{:ok, _, luerl_state} = :luerl.do(closure_def, luerl_state) + +# --- C Lua via luaport (optional) --- +{c_lua_benchmarks, c_lua_cleanup} = + case Application.ensure_all_started(:luaport) do + {:ok, _} -> + scripts_dir = Path.join(__DIR__, "scripts") + {:ok, port_pid, _} = :luaport.spawn(:closure_bench, to_charlist(scripts_dir)) + :luaport.load(port_pid, closure_def) + + benchmarks = %{ + "C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_closures, [100]) end + } + + {benchmarks, fn -> :luaport.despawn(:closure_bench) end} + + {:error, reason} -> + IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks") + {%{}, fn -> :ok end} + end + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_closures) end, + "lua (chunk)" => fn -> Lua.eval!(lua, closure_chunk) end, + "luerl" => fn -> :luerl.do(call_closures, luerl_state) end + }, + c_lua_benchmarks + ), + time: 10, + warmup: 2, + memory_time: 1 +) + +c_lua_cleanup.() diff --git a/benchmarks/fibonacci.exs b/benchmarks/fibonacci.exs new file mode 100644 index 0000000..2de4a3c --- /dev/null +++ b/benchmarks/fibonacci.exs @@ -0,0 +1,66 @@ +# Run with: mix run benchmarks/fibonacci.exs +# +# Benchmarks recursive Fibonacci (fib(30)) across: +# - This Lua implementation (eval with string, eval with pre-compiled chunk) +# - Luerl (Erlang-based Lua 5.3 implementation) +# - C Lua 5.4 via luaport (port-based; results include IPC overhead) +# +# NOTE: luaport requires C Lua development headers. On macOS with Homebrew: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport +# Then run: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/fibonacci.exs + +Application.ensure_all_started(:luerl) + +fib_def = """ +function fib(n) + if n < 2 then return n end + return fib(n-1) + fib(n-2) +end +""" + +call_fib = "return fib(30)" + +# --- This Lua implementation --- +lua = Lua.new() +{_, lua} = Lua.eval!(lua, fib_def) +{fib_chunk, _} = Lua.load_chunk!(lua, call_fib) + +# --- Luerl --- +luerl_state = :luerl.init() +{:ok, _, luerl_state} = :luerl.do(fib_def, luerl_state) + +# --- C Lua via luaport (optional) --- +{c_lua_benchmarks, c_lua_cleanup} = + case Application.ensure_all_started(:luaport) do + {:ok, _} -> + scripts_dir = Path.join(__DIR__, "scripts") + {:ok, port_pid, _} = :luaport.spawn(:fib_bench, to_charlist(scripts_dir)) + :luaport.load(port_pid, fib_def) + + benchmarks = %{ + "C Lua (luaport)" => fn -> :luaport.call(port_pid, :fib, [30]) end + } + + {benchmarks, fn -> :luaport.despawn(:fib_bench) end} + + {:error, reason} -> + IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks") + {%{}, fn -> :ok end} + end + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_fib) end, + "lua (chunk)" => fn -> Lua.eval!(lua, fib_chunk) end, + "luerl" => fn -> :luerl.do(call_fib, luerl_state) end + }, + c_lua_benchmarks + ), + time: 10, + warmup: 2, + memory_time: 1 +) + +c_lua_cleanup.() diff --git a/benchmarks/oop.exs b/benchmarks/oop.exs new file mode 100644 index 0000000..0158c11 --- /dev/null +++ b/benchmarks/oop.exs @@ -0,0 +1,99 @@ +# Run with: mix run benchmarks/oop.exs +# +# Benchmarks object-oriented patterns using Lua tables and metatables. +# Uses assignment-style method definitions (e.g. Animal.speak = function(self) ... end) +# which are compatible with this Lua implementation's current feature set. +# Creates 50 Animal instances per iteration and calls a method on each. +# +# Patterns tested: +# - Table creation and field assignment +# - setmetatable / __index prototype chain lookup +# - Closure creation per object (factory pattern variant) +# +# Compares: +# - This Lua implementation (eval with string, eval with pre-compiled chunk) +# - Luerl (Erlang-based Lua 5.3 implementation) +# - C Lua 5.4 via luaport (port-based; results include IPC overhead) +# +# NOTE: luaport requires C Lua development headers. On macOS with Homebrew: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport +# Then run: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/oop.exs + +Application.ensure_all_started(:luerl) + +oop_def = """ +Animal = {} +Animal.__index = Animal + +Animal.new = function(name, sound) + local obj = {} + obj.name = name + obj.sound = sound + setmetatable(obj, Animal) + return obj +end + +Animal.speak = function(self) + return self.name .. " says " .. self.sound +end + +Animal.getName = function(self) + return self.name +end + +function run_oop(n) + local result = "" + for i = 1, n do + local a = Animal.new("Animal" .. tostring(i), "sound" .. tostring(i)) + result = Animal.speak(a) + end + return result +end +""" + +call_oop = "return run_oop(50)" + +# --- This Lua implementation --- +lua = Lua.new() +{_, lua} = Lua.eval!(lua, oop_def) +{oop_chunk, _} = Lua.load_chunk!(lua, call_oop) + +# --- Luerl --- +luerl_state = :luerl.init() +{:ok, _, luerl_state} = :luerl.do(oop_def, luerl_state) + +# --- C Lua via luaport (optional) --- +{c_lua_benchmarks, c_lua_cleanup} = + case Application.ensure_all_started(:luaport) do + {:ok, _} -> + scripts_dir = Path.join(__DIR__, "scripts") + {:ok, port_pid, _} = :luaport.spawn(:oop_bench, to_charlist(scripts_dir)) + :luaport.load(port_pid, oop_def) + + benchmarks = %{ + "C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_oop, [50]) end + } + + {benchmarks, fn -> :luaport.despawn(:oop_bench) end} + + {:error, reason} -> + IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks") + {%{}, fn -> :ok end} + end + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_oop) end, + "lua (chunk)" => fn -> Lua.eval!(lua, oop_chunk) end, + "luerl" => fn -> :luerl.do(call_oop, luerl_state) end + }, + c_lua_benchmarks + ), + time: 10, + warmup: 2, + memory_time: 1 +) + +c_lua_cleanup.() diff --git a/benchmarks/scripts/main.lua b/benchmarks/scripts/main.lua new file mode 100644 index 0000000..c9c3d1c --- /dev/null +++ b/benchmarks/scripts/main.lua @@ -0,0 +1,2 @@ +-- Main script loaded by luaport on startup. +-- Benchmark-specific functions are loaded at runtime via :luaport.load/2. diff --git a/benchmarks/string_ops.exs b/benchmarks/string_ops.exs new file mode 100644 index 0000000..716afb4 --- /dev/null +++ b/benchmarks/string_ops.exs @@ -0,0 +1,100 @@ +# Run with: mix run benchmarks/string_ops.exs +# +# Benchmarks string operations: +# - table.concat: builds 100 string parts in a table then joins them +# - string.format: formats 100 strings with integer and float values +# +# Compares: +# - This Lua implementation (eval with string, eval with pre-compiled chunk) +# - Luerl (Erlang-based Lua 5.3 implementation) +# - C Lua 5.4 via luaport (port-based; results include IPC overhead) +# +# NOTE: luaport requires C Lua development headers. On macOS with Homebrew: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport +# Then run: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/string_ops.exs + +Application.ensure_all_started(:luerl) + +string_def = """ +function run_concat(n) + local parts = {} + for i = 1, n do + parts[i] = tostring(i) + end + return table.concat(parts, ", ") +end + +function run_format(n) + local parts = {} + for i = 1, n do + parts[i] = string.format("item_%d=%f", i, i * 1.5) + end + return table.concat(parts, "\\n") +end +""" + +call_concat = "return run_concat(100)" +call_format = "return run_format(100)" + +# --- This Lua implementation --- +lua = Lua.new() +{_, lua} = Lua.eval!(lua, string_def) +{concat_chunk, _} = Lua.load_chunk!(lua, call_concat) +{format_chunk, _} = Lua.load_chunk!(lua, call_format) + +# --- Luerl --- +luerl_state = :luerl.init() +{:ok, _, luerl_state} = :luerl.do(string_def, luerl_state) + +# --- C Lua via luaport (optional) --- +{c_lua_concat, c_lua_format, c_lua_cleanup} = + case Application.ensure_all_started(:luaport) do + {:ok, _} -> + scripts_dir = Path.join(__DIR__, "scripts") + {:ok, port_pid, _} = :luaport.spawn(:string_bench, to_charlist(scripts_dir)) + :luaport.load(port_pid, string_def) + + concat = %{"C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_concat, [100]) end} + format = %{"C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_format, [100]) end} + + {concat, format, fn -> :luaport.despawn(:string_bench) end} + + {:error, reason} -> + IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks") + {%{}, %{}, fn -> :ok end} + end + +IO.puts("\n=== String Concatenation via table.concat (n=100) ===\n") + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_concat) end, + "lua (chunk)" => fn -> Lua.eval!(lua, concat_chunk) end, + "luerl" => fn -> :luerl.do(call_concat, luerl_state) end + }, + c_lua_concat + ), + time: 10, + warmup: 2, + memory_time: 1 +) + +IO.puts("\n=== String Formatting via string.format (n=100) ===\n") + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_format) end, + "lua (chunk)" => fn -> Lua.eval!(lua, format_chunk) end, + "luerl" => fn -> :luerl.do(call_format, luerl_state) end + }, + c_lua_format + ), + time: 10, + warmup: 2, + memory_time: 1 +) + +c_lua_cleanup.() diff --git a/benchmarks/table_ops.exs b/benchmarks/table_ops.exs new file mode 100644 index 0000000..671d8e8 --- /dev/null +++ b/benchmarks/table_ops.exs @@ -0,0 +1,169 @@ +# Run with: mix run benchmarks/table_ops.exs +# +# Benchmarks table (array) operations with n=500: +# - build: create array of squared values +# - sort: sort a reverse-ordered array (worst case for naive sort) +# - iterate: sum all values via ipairs +# - map_reduce: build → square each element → sum (two passes) +# +# Compares: +# - This Lua implementation (eval with string, eval with pre-compiled chunk) +# - Luerl (Erlang-based Lua 5.3 implementation) +# - C Lua 5.4 via luaport (port-based; results include IPC overhead) +# +# NOTE: luaport requires C Lua development headers. On macOS with Homebrew: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport +# Then run: +# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/table_ops.exs + +Application.ensure_all_started(:luerl) + +table_def = """ +function run_table_build(n) + local t = {} + for i = 1, n do + t[i] = i * i + end + return #t +end + +function run_table_sort(n) + local t = {} + for i = 1, n do + t[i] = n - i + 1 + end + table.sort(t) + return t[1] +end + +function run_table_sum(n) + local t = {} + for i = 1, n do + t[i] = i + end + local sum = 0 + for j = 1, n do + sum = sum + t[j] + end + return sum +end + +function run_table_map_reduce(n) + local t = {} + for i = 1, n do + t[i] = i + end + local mapped = {} + for j = 1, n do + mapped[j] = t[j] * t[j] + end + local sum = 0 + for k = 1, n do + sum = sum + mapped[k] + end + return sum +end +""" + +n = 500 + +call_build = "return run_table_build(#{n})" +call_sort = "return run_table_sort(#{n})" +call_sum = "return run_table_sum(#{n})" +call_map_reduce = "return run_table_map_reduce(#{n})" + +# --- This Lua implementation --- +lua = Lua.new() +{_, lua} = Lua.eval!(lua, table_def) +{build_chunk, _} = Lua.load_chunk!(lua, call_build) +{sort_chunk, _} = Lua.load_chunk!(lua, call_sort) +{sum_chunk, _} = Lua.load_chunk!(lua, call_sum) +{map_reduce_chunk, _} = Lua.load_chunk!(lua, call_map_reduce) + +# --- Luerl --- +luerl_state = :luerl.init() +{:ok, _, luerl_state} = :luerl.do(table_def, luerl_state) + +# --- C Lua via luaport (optional) --- +{c_lua_build, c_lua_sort, c_lua_sum, c_lua_map_reduce, c_lua_cleanup} = + case Application.ensure_all_started(:luaport) do + {:ok, _} -> + scripts_dir = Path.join(__DIR__, "scripts") + {:ok, port_pid, _} = :luaport.spawn(:table_bench, to_charlist(scripts_dir)) + :luaport.load(port_pid, table_def) + + mk = fn func -> %{"C Lua (luaport)" => fn -> :luaport.call(port_pid, func, [n]) end} end + + { + mk.(:run_table_build), + mk.(:run_table_sort), + mk.(:run_table_sum), + mk.(:run_table_map_reduce), + fn -> :luaport.despawn(:table_bench) end + } + + {:error, reason} -> + IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks") + empty = %{} + {empty, empty, empty, empty, fn -> :ok end} + end + +benchee_opts = [time: 10, warmup: 2, memory_time: 1] + +IO.puts("\n=== Table Build (n=#{n}) ===\n") + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_build) end, + "lua (chunk)" => fn -> Lua.eval!(lua, build_chunk) end, + "luerl" => fn -> :luerl.do(call_build, luerl_state) end + }, + c_lua_build + ), + benchee_opts +) + +IO.puts("\n=== Table Sort (n=#{n}) ===\n") + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_sort) end, + "lua (chunk)" => fn -> Lua.eval!(lua, sort_chunk) end, + "luerl" => fn -> :luerl.do(call_sort, luerl_state) end + }, + c_lua_sort + ), + benchee_opts +) + +IO.puts("\n=== Table Iterate/Sum (n=#{n}) ===\n") + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_sum) end, + "lua (chunk)" => fn -> Lua.eval!(lua, sum_chunk) end, + "luerl" => fn -> :luerl.do(call_sum, luerl_state) end + }, + c_lua_sum + ), + benchee_opts +) + +IO.puts("\n=== Table Map + Reduce (n=#{n}) ===\n") + +Benchee.run( + Map.merge( + %{ + "lua (eval)" => fn -> Lua.eval!(lua, call_map_reduce) end, + "lua (chunk)" => fn -> Lua.eval!(lua, map_reduce_chunk) end, + "luerl" => fn -> :luerl.do(call_map_reduce, luerl_state) end + }, + c_lua_map_reduce + ), + benchee_opts +) + +c_lua_cleanup.() diff --git a/mix.exs b/mix.exs index ec2105b..662de12 100644 --- a/mix.exs +++ b/mix.exs @@ -64,7 +64,10 @@ defmodule Lua.MixProject do {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, {:stream_data, "~> 1.1", only: [:test]}, - {:styler, "~> 1.10", only: [:dev, :test], runtime: false} + {:styler, "~> 1.10", only: [:dev, :test], runtime: false}, + {:benchee, "~> 1.3", only: :dev}, + {:luerl, "~> 1.5", only: :dev}, + {:luaport, "~> 1.6", only: :dev} ] end end diff --git a/mix.lock b/mix.lock index ae430a1..af5f7cb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,6 @@ %{ + "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, @@ -8,6 +10,8 @@ "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "igniter": {:hex, :igniter, "0.7.2", "81c132c0df95963c7a228f74a32d3348773743ed9651f24183bfce0fe6ff16d1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f4cab73ec31f4fb452de1a17037f8a08826105265aa2d76486fcb848189bef9b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "luaport": {:hex, :luaport, "1.6.3", "df26eb476e4e8a372dc8c812f59ef3280b34743dc04ec339b646491a488b2560", [:rebar3], [], "hexpm", "3df1f51b82b62ed3b128d0d7c6caee4c5143c3bed498737f12302f7bf98b75a1"}, + "luerl": {:hex, :luerl, "1.5.1", "f6700420950fc6889137e7a0c11c4a8467dea04a8c23f707a40d83566d14e786", [:rebar3], [], "hexpm", "abf88d849baa0d5dca93b245a8688d4de2ee3d588159bb2faf51e15946509390"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, @@ -21,6 +25,7 @@ "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, "sourceror": {:hex, :sourceror, "1.10.1", "325753ed460fe9fa34ebb4deda76d57b2e1507dcd78a5eb9e1c41bfb78b7cdfe", [:mix], [], "hexpm", "288f3079d93865cd1e3e20df5b884ef2cb440e0e03e8ae393624ee8a770ba588"}, "spitfire": {:hex, :spitfire, "0.3.2", "476b7b5151fd053a864dae7b5eaeed01811e8b2ff3f24f3c048af1c9dfee5e3d", [:mix], [], "hexpm", "014f7b8c6dd45d1e3b08103c7e61515a590efc872441cf3e933a20efa4b5c46c"}, + "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, "styler": {:hex, :styler, "1.10.1", "9229050c978bfaaab1d94e8673843576d0127d48fe64824a30babde3d6342475", [:mix], [], "hexpm", "d86cbcc70e8ab424393af313d1d885931ba9dc7c383d7dd30f4ab255a8d39f73"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},