From 96299cbf09bdb75e4d6c6849c9c4c3ce000a6fc5 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Mon, 10 Nov 2025 12:27:21 +0100 Subject: feat(parser): supports literal decimal numbers --- example/input.txt | 2 +- lib/elixir_math_parser.ex | 5 +++++ lib/math/Calc.ex | 28 ---------------------------- lib/math/calc.ex | 28 ++++++++++++++++++++++++++++ lib/math/conversion.ex | 18 ++++++++++++++++++ src/elixir_math_parser.yrl | 12 ++++++++---- src/elixir_math_parser_lexer.xrl | 2 ++ 7 files changed, 62 insertions(+), 33 deletions(-) delete mode 100644 lib/math/Calc.ex create mode 100644 lib/math/calc.ex create mode 100644 lib/math/conversion.ex diff --git a/example/input.txt b/example/input.txt index 1f4789b..909e250 100644 --- a/example/input.txt +++ b/example/input.txt @@ -1,4 +1,4 @@ -a = 7;; b = 4 +a = 10.5;; b = .5 (a + b) * 10 / 2 x = 2_0 diff --git a/lib/elixir_math_parser.ex b/lib/elixir_math_parser.ex index 4a96daf..286829e 100644 --- a/lib/elixir_math_parser.ex +++ b/lib/elixir_math_parser.ex @@ -4,11 +4,16 @@ defmodule ElixirMathParser do """ alias ElixirMathParser.Math.Rational alias ElixirMathParser.Math.Calc + alias ElixirMathParser.Math.Conversion defp reduce_to_value({:int, _line, value}, _state) do {:ok, Rational.new(value)} end + defp reduce_to_value({:float, _line, value}, _state) do + {:ok, to_string(value) |> Conversion.literal_float_to_rational()} + end + defp reduce_to_value({:var, line, var}, state) do if !Map.has_key?(state, var) do {:error, line, "value not found for " <> to_string(var)} diff --git a/lib/math/Calc.ex b/lib/math/Calc.ex deleted file mode 100644 index 7dc5fcc..0000000 --- a/lib/math/Calc.ex +++ /dev/null @@ -1,28 +0,0 @@ -defmodule ElixirMathParser.Math.Calc do - alias ElixirMathParser.Math.Rational - def factorial(n), do: factorial_rec(n, 1) - defp factorial_rec(n, acc) when n > 0, do: factorial_rec(n - 1, acc * n) - defp factorial_rec(0, acc), do: acc - - def pow(value, exponent), do: pow_rec(value, exponent, Rational.new(1)) - - defp pow_rec(value, exponent, acc) when is_integer(exponent) do - case exponent do - 0 -> - acc - - _ when exponent < 0 -> - pow_rec(Rational.new(1, value), -exponent, acc) - - _ when Kernel.rem(exponent, 2) == 0 -> - pow_rec(Rational.mult(value, value), Kernel.div(exponent, 2), acc) - - _ -> - pow_rec( - Rational.mult(value, value), - Kernel.div(exponent - 1, 2), - Rational.mult(acc, value) - ) - end - end -end diff --git a/lib/math/calc.ex b/lib/math/calc.ex new file mode 100644 index 0000000..7dc5fcc --- /dev/null +++ b/lib/math/calc.ex @@ -0,0 +1,28 @@ +defmodule ElixirMathParser.Math.Calc do + alias ElixirMathParser.Math.Rational + def factorial(n), do: factorial_rec(n, 1) + defp factorial_rec(n, acc) when n > 0, do: factorial_rec(n - 1, acc * n) + defp factorial_rec(0, acc), do: acc + + def pow(value, exponent), do: pow_rec(value, exponent, Rational.new(1)) + + defp pow_rec(value, exponent, acc) when is_integer(exponent) do + case exponent do + 0 -> + acc + + _ when exponent < 0 -> + pow_rec(Rational.new(1, value), -exponent, acc) + + _ when Kernel.rem(exponent, 2) == 0 -> + pow_rec(Rational.mult(value, value), Kernel.div(exponent, 2), acc) + + _ -> + pow_rec( + Rational.mult(value, value), + Kernel.div(exponent - 1, 2), + Rational.mult(acc, value) + ) + end + end +end diff --git a/lib/math/conversion.ex b/lib/math/conversion.ex new file mode 100644 index 0000000..65ddf8f --- /dev/null +++ b/lib/math/conversion.ex @@ -0,0 +1,18 @@ +defmodule ElixirMathParser.Math.Conversion do + alias ElixirMathParser.Math.Rational + + def literal_float_to_rational(value) do + {int, dec} = Integer.parse(value) + + String.graphemes(dec) + |> Enum.reduce(Rational.new(int), fn v, acc -> + if v != "." do + num = Rational.numerator(acc) * 10 + den = Rational.denominator(acc) * 10 + Rational.new(num + String.to_integer(v), den) + else + acc + end + end) + end +end diff --git a/src/elixir_math_parser.yrl b/src/elixir_math_parser.yrl index 4e8e9e9..635ebd3 100644 --- a/src/elixir_math_parser.yrl +++ b/src/elixir_math_parser.yrl @@ -7,7 +7,7 @@ Nonterminals . Terminals - int + int float var break '+' '-' '*' '/' '!' '^' @@ -42,6 +42,7 @@ exprs -> expr : '$1'. exprs -> expr exprs : {mul_op, '$1', '$2'}. 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'}. @@ -54,6 +55,9 @@ expr -> '-' exprs : {sub_op, {int, 0, 0}, '$2'}. Erlang code. -unwrap({int, Line, Value}) -> - Match = fun(X) -> not(X == 95) end, - {int, Line, list_to_integer(lists:filter(Match, Value))}. +% 95 is the unicode of "_" +numberMatch(X) -> not(X == 95). + +unwrap({int, Line, Value}) -> {int, Line, list_to_integer(lists:filter(fun numberMatch/1, Value))}; +% 48 is the unicode of "0" +unwrap({float, Line, Value}) -> {float, Line, [48 | lists:filter(fun numberMatch/1, Value)]}. diff --git a/src/elixir_math_parser_lexer.xrl b/src/elixir_math_parser_lexer.xrl index c418c02..4073958 100644 --- a/src/elixir_math_parser_lexer.xrl +++ b/src/elixir_math_parser_lexer.xrl @@ -1,5 +1,6 @@ Definitions. INT = [0-9_]+ +FLOAT = [0-9_]*\.[0-9]+ NAME = [a-zA-Z_][a-zA-Z0-9_]* WHITESPACE = [\s\t\r] COMMENT = #[^\n]*\n? @@ -17,6 +18,7 @@ Rules. \^ : {token, {'^', TokenLine}}. {BREAK}+ : {token, {break, TokenLine}}. {NAME} : {token, {var, TokenLine, TokenChars}}. +{FLOAT} : {token, {float, TokenLine, TokenChars}}. {INT} : {token, {int, TokenLine, TokenChars}}. {WHITESPACE}+ : skip_token. {COMMENT}+ : skip_token. -- cgit v1.2.3