diff options
| author | Anhgelus Morhtuuzh <william@herges.fr> | 2025-11-10 17:31:41 +0100 |
|---|---|---|
| committer | Anhgelus Morhtuuzh <william@herges.fr> | 2025-11-10 17:31:41 +0100 |
| commit | 4840f480c8f255a6cf3b4eed291a00cea76b0cac (patch) | |
| tree | d9baf898c1e961bb8ee5d181c38cce93b062129b | |
| parent | 037094a928653ed27f1f9d5497f637af1c5380e0 (diff) | |
feat(calc): supports function definition and evaluation
| -rw-r--r-- | example/input.txt | 12 | ||||
| -rw-r--r-- | lib/elixir_math_parser.ex | 40 | ||||
| -rw-r--r-- | lib/math/function.ex | 28 | ||||
| -rw-r--r-- | lib/math/rational.ex | 5 | ||||
| -rw-r--r-- | src/elixir_math_parser.yrl | 21 | ||||
| -rw-r--r-- | src/elixir_math_parser_lexer.xrl | 6 |
6 files changed, 99 insertions, 13 deletions
diff --git a/example/input.txt b/example/input.txt index 9c49d56..bd668e0 100644 --- a/example/input.txt +++ b/example/input.txt @@ -1,9 +1,9 @@ a = 10.05;; b = .95 (a + b) * 10 / 2 -x = 2_0 -y = 2! -z = y/2 -10 + 4(z / y)(x / 4) - -x^(-y) +f: x = x + 1 +x = 2 +# is 24 + 1 = 25 +f(24) +# must be 10 +x(5) diff --git a/lib/elixir_math_parser.ex b/lib/elixir_math_parser.ex index 286829e..b7f080c 100644 --- a/lib/elixir_math_parser.ex +++ b/lib/elixir_math_parser.ex @@ -5,6 +5,7 @@ defmodule ElixirMathParser do alias ElixirMathParser.Math.Rational alias ElixirMathParser.Math.Calc alias ElixirMathParser.Math.Conversion + alias ElixirMathParser.Math.Function defp reduce_to_value({:int, _line, value}, _state) do {:ok, Rational.new(value)} @@ -76,11 +77,35 @@ defmodule ElixirMathParser do end end + defp reduce_to_value({:eval_func, {:var, line, var}, params}, state) do + if !Map.has_key?(state, var) do + {:error, line, "function " <> to_string(var) <> " not found"} + else + v = state[var] + + # check if the mult is implicit + if Rational.is_rational(v) do + [head | _] = params + reduce_to_value({:mul_op, {:var, line, var}, head}, state) + else + params = Enum.map(params, fn v -> with {:ok, v} <- reduce_to_value(v, state), do: v end) + + with {:ok, v} <- state[var] |> Function.eval(params) do + {:ok, v} + else + {:error, reason} -> {:error, line, reason} + {:error, line, reason} -> {:error, line, reason} + end + end + end + end + 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} + {:error, line, reason} -> {:error, line, reason} end end @@ -92,6 +117,21 @@ defmodule ElixirMathParser do end end + defp evaluate_tree([{:assign_func, {:var, _line, name}, vars, expr} | tail], state) do + fun = + Function.new( + fn params, given -> + state = + Enum.reduce(given, state, fn {v, id}, acc -> Map.merge(acc, %{params[id] => v}) end) + + reduce_to_value(expr, state) + end, + Enum.map(vars, fn {:var, _line, name} -> name end) + ) + + evaluate_tree(tail, Map.merge(state, %{name => fun})) + end + defp evaluate_tree([], state) do {:ok, state} end diff --git a/lib/math/function.ex b/lib/math/function.ex new file mode 100644 index 0000000..2acf2c4 --- /dev/null +++ b/lib/math/function.ex @@ -0,0 +1,28 @@ +defmodule ElixirMathParser.Math.Function do + alias ElixirMathParser.Math.Function + + defstruct relation: nil, parameters: %{} + + def is_function(val) when is_map(val) and is_map_key(val, :__struct__) and is_struct(val), + do: :erlang.map_get(:__struct__, val) == __MODULE__ + + def new(relation, parameters) do + params = + Enum.with_index(parameters) + |> Enum.reduce(%{}, fn {v, id}, acc -> Map.merge(acc, %{id => v}) end) + + %Function{relation: relation, parameters: params} + end + + def eval(fun, parameters) do + if Enum.count(parameters) != Enum.count(fun.parameters) do + {:error, "wrong count of parameters"} + else + params = + Enum.with_index(parameters) + |> Enum.reduce(%{}, fn {v, id}, acc -> Map.merge(acc, %{v => id}) end) + + fun.relation.(fun.parameters, params) + end + end +end diff --git a/lib/math/rational.ex b/lib/math/rational.ex index c3d94c9..e788883 100644 --- a/lib/math/rational.ex +++ b/lib/math/rational.ex @@ -83,9 +83,8 @@ defmodule ElixirMathParser.Math.Rational do iex> Rational.is_rational("My quick brown fox") false """ - defguard is_rational(val) - when is_map(val) and is_map_key(val, :__struct__) and is_struct(val) and - :erlang.map_get(:__struct__, val) == __MODULE__ + def is_rational(val) when is_map(val) and is_map_key(val, :__struct__) and is_struct(val), + do: :erlang.map_get(:__struct__, val) == __MODULE__ @doc """ Creates a new Rational number. diff --git a/src/elixir_math_parser.yrl b/src/elixir_math_parser.yrl index 635ebd3..a48b57b 100644 --- a/src/elixir_math_parser.yrl +++ b/src/elixir_math_parser.yrl @@ -4,15 +4,21 @@ Nonterminals statements expr exprs + % function specific + vars params . Terminals int float var break + % eval '+' '-' '*' '/' '!' '^' - '=' '(' ')' + % assign + '=' + % function specific + ':' ',' . Rootsymbol @@ -37,13 +43,22 @@ statements -> break : []. statement -> var '=' exprs : {assign, '$1', '$3'}. statement -> exprs : {eval, '$1'}. +statement -> var ':' vars '=' exprs : {assign_func, '$1', '$3', '$5'}. + +vars -> var : ['$1']. +vars -> var ',' vars : ['$1' | '$3']. -exprs -> expr : '$1'. +exprs -> expr : '$1'. exprs -> expr exprs : {mul_op, '$1', '$2'}. +params -> expr : ['$1']. +params -> expr ',' : ['$1']. +params -> expr ',' params : ['$1' | '$3']. + expr -> int : unwrap('$1'). expr -> float : unwrap('$1'). expr -> var : '$1'. + expr -> exprs '+' exprs : {add_op, '$1', '$3'}. expr -> exprs '-' exprs : {sub_op, '$1', '$3'}. expr -> exprs '*' exprs : {mul_op, '$1', '$3'}. @@ -53,6 +68,8 @@ expr -> exprs '^' exprs : {exp_op, '$1', '$3'}. expr -> '(' exprs ')' : '$2'. expr -> '-' exprs : {sub_op, {int, 0, 0}, '$2'}. +expr -> var '(' params ')' : {eval_func, '$1', '$3'}. + Erlang code. % 95 is the unicode of "_" diff --git a/src/elixir_math_parser_lexer.xrl b/src/elixir_math_parser_lexer.xrl index 4073958..fdb661f 100644 --- a/src/elixir_math_parser_lexer.xrl +++ b/src/elixir_math_parser_lexer.xrl @@ -4,7 +4,7 @@ FLOAT = [0-9_]*\.[0-9]+ NAME = [a-zA-Z_][a-zA-Z0-9_]* WHITESPACE = [\s\t\r] COMMENT = #[^\n]*\n? -BREAK = [\n;;] +BREAK = [\n;;]+ Rules. \+ : {token, {'+', TokenLine}}. @@ -16,7 +16,9 @@ Rules. \) : {token, {')', TokenLine}}. ! : {token, {'!', TokenLine}}. \^ : {token, {'^', TokenLine}}. -{BREAK}+ : {token, {break, TokenLine}}. +\: : {token, {':', TokenLine}}. +, : {token, {',', TokenLine}}. +{BREAK} : {token, {break, TokenLine}}. {NAME} : {token, {var, TokenLine, TokenChars}}. {FLOAT} : {token, {float, TokenLine, TokenChars}}. {INT} : {token, {int, TokenLine, TokenChars}}. |
