aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Hergès <william@herges.fr>2025-11-09 13:23:05 +0100
committerWilliam Hergès <william@herges.fr>2025-11-09 13:23:05 +0100
commit3b0cc79dee76244da55d2326cc4244a384d85e44 (patch)
tree7a2819f658fe987f942633ad82997a75850e16ef
parent8f9730d89e66ab1cbf08cbc0dea2a429d135e0da (diff)
feat(calc)): supports factorial
-rw-r--r--example/input.txt4
-rw-r--r--lib/elixir_math_parser.ex22
-rw-r--r--lib/main.ex1
-rw-r--r--src/elixir_math_parser.yrl4
-rw-r--r--src/elixir_math_parser_lexer.xrl1
5 files changed, 26 insertions, 6 deletions
diff --git a/example/input.txt b/example/input.txt
index a32d267..7efcb9c 100644
--- a/example/input.txt
+++ b/example/input.txt
@@ -2,6 +2,6 @@ a = 7;; b = 4
(a + b) * 10 / 2
x = 20
-y = 3
-z = 2y
+y = 2!
+z = y/2
10 + 4(z / y)(x / 4)
diff --git a/lib/elixir_math_parser.ex b/lib/elixir_math_parser.ex
index f7f916f..863ecd8 100644
--- a/lib/elixir_math_parser.ex
+++ b/lib/elixir_math_parser.ex
@@ -10,9 +10,9 @@ defmodule ElixirMathParser do
{:ok, value <~> 1}
end
- defp reduce_to_value({:var, _line, var}, state) do
+ defp reduce_to_value({:var, line, var}, state) do
if !Map.has_key?(state, var) do
- {:error, "value not found for " <> to_string(var)}
+ {:error, line, "value not found for " <> to_string(var)}
else
{:ok, state[var]}
end
@@ -46,9 +46,25 @@ defmodule ElixirMathParser do
end
end
- defp evaluate_tree([{:assign, {:var, _line, lhs}, rhs} | tail], state) do
+ defp reduce_to_value({:factor_op, lhs}, state) do
+ with {:ok, op} <- reduce_to_value(lhs, state),
+ true <- Ratio.denominator(op) == 1,
+ true <- Ratio.numerator(op) >= 0 do
+ {:ok, factor(Ratio.numerator(op), 1)}
+ else
+ {:error, line, reason} -> {:error, line, reason}
+ false -> {:error, "must have a positive integer for the factorial"}
+ end
+ end
+
+ defp factor(n, acc) when n > 0, do: factor(n - 1, acc * n)
+ defp factor(0, acc), do: acc
+
+ defp evaluate_tree([{:assign, {:var, line, lhs}, rhs} | tail], state) do
with {:ok, val} <- reduce_to_value(rhs, state) do
evaluate_tree(tail, Map.merge(state, %{lhs => val}))
+ else
+ {:error, reason} -> {:error, line, reason}
end
end
diff --git a/lib/main.ex b/lib/main.ex
index 99c25ca..5e7cf55 100644
--- a/lib/main.ex
+++ b/lib/main.ex
@@ -11,6 +11,7 @@ defmodule ElixirMathParser.Main do
case ElixirMathParser.process_tree(tree) do
{:ok, _} -> :ok
+ {:error, line, reason} -> reason <> " (line " <> to_string(line) <> ")"
{:error, reason} -> reason
end
end
diff --git a/src/elixir_math_parser.yrl b/src/elixir_math_parser.yrl
index 86b36c8..0808683 100644
--- a/src/elixir_math_parser.yrl
+++ b/src/elixir_math_parser.yrl
@@ -10,7 +10,7 @@ Terminals
int
var
break
- '+' '-' '*' '/'
+ '+' '-' '*' '/' '!'
'='
'(' ')'
.
@@ -24,6 +24,7 @@ Left 300 '+'.
Left 300 '-'.
Left 400 '*'.
Left 400 '/'.
+Right 500 '!'.
Left 600 '('.
root -> statements : '$1'.
@@ -46,6 +47,7 @@ expr -> exprs '-' exprs : {sub_op, '$1', '$3'}.
expr -> exprs '*' exprs : {mul_op, '$1', '$3'}.
expr -> exprs '/' exprs : {div_op, '$1', '$3'}.
expr -> '(' exprs ')' : '$2'.
+expr -> expr '!' : {factor_op, '$1'}.
Erlang code.
diff --git a/src/elixir_math_parser_lexer.xrl b/src/elixir_math_parser_lexer.xrl
index facf323..5bdcbef 100644
--- a/src/elixir_math_parser_lexer.xrl
+++ b/src/elixir_math_parser_lexer.xrl
@@ -13,6 +13,7 @@ Rules.
\= : {token, {'=', TokenLine}}.
\( : {token, {'(', TokenLine}}.
\) : {token, {')', TokenLine}}.
+! : {token, {'!', TokenLine}}.
{BREAK}+ : {token, {break, TokenLine}}.
{NAME} : {token, {var, TokenLine, TokenChars}}.
{INT} : {token, {int, TokenLine, TokenChars}}.