Skip to content

Instantly share code, notes, and snippets.

@pmarreck
Created May 4, 2015 01:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmarreck/90aeac54022044877ef7 to your computer and use it in GitHub Desktop.
Save pmarreck/90aeac54022044877ef7 to your computer and use it in GitHub Desktop.
no matches in this elixir attempt to rpn calc
# rpn.exs
# A commandline RPN calculator in Elixir... just for practice.
Code.require_file "math_integer_power.exs", __DIR__
defmodule RPN do
def initialize do
initialize(System.argv)
end
def initialize(input) when is_binary(input) do
initialize(String.split(input, " ", trim: true))
end
def initialize(input) when is_list(input) do
input |> normalize |> compute([], [dict: %{}])
end
def normalize(input) when is_list(input) do
input
|> Enum.map(fn(elem) ->
case elem do
"+" -> :+
"-" -> :-
"*" -> :*
"/" -> :/
"**" -> :"**"
"pi" -> :pi
"sin" -> :sin
"cos" -> :cos
"tan" -> :tan
"sqrt" -> :sqrt
"drop" -> :drop
"dup" -> :dup
"emit" -> :emit
"." -> :.
":" -> :":"
";" -> :";"
num -> fn ->
num_or_symbol = validate_num(num)
if num_or_symbol == :NaN do
String.to_atom(num)
else
num_or_symbol
end
end.()
end #case
end #fn
) #Enum.map
end
defp validate_num(num) when is_float(num), do: num
defp validate_num(num) when is_integer(num), do: num
defp validate_num(num) when is_binary(num) do
cond do
num =~ ~r/^-?[0-9]+$/ -> String.to_integer(num)
num =~ ~r/^-?[0-9]+(?:\.[0-9]+)?$/ -> String.to_float(num)
true -> :NaN
end
end
def compute([], [last_val], _) do
last_val
end
def compute([ :+ | remaining_input], [y, x | stack], dict) do
compute(remaining_input, [x + y | stack], dict)
end
def compute([ :- | remaining_input], [y, x | stack], dict) do
compute(remaining_input, [x - y | stack], dict)
end
def compute([ :* | remaining_input], [y, x | stack], dict) do
compute(remaining_input, [x * y | stack], dict)
end
def compute([ :/ | remaining_input], [y, x | stack], dict) do
compute(remaining_input, [x / y | stack], dict)
end
def compute([ :"**" | remaining_input], [y, x | stack], dict) when is_integer(y) and is_integer(x) do
compute(remaining_input, [Math.Integer.ipow(x, y) | stack], dict)
end
def compute([ :"**" | remaining_input], [y, x | stack], dict) do
compute(remaining_input, [:math.pow(x, y) | stack], dict)
end
def compute([ :pi | remaining_input], stack, dict) do
compute(remaining_input, [:math.pi | stack], dict)
end
def compute([ :sin | remaining_input], [x | stack], dict) do
compute(remaining_input, [:math.sin(x) | stack], dict)
end
def compute([ :cos | remaining_input], [x | stack], dict) do
compute(remaining_input, [:math.cos(x) | stack], dict)
end
def compute([ :tan | remaining_input], [x | stack], dict) do
compute(remaining_input, [:math.tan(x) | stack], dict)
end
def compute([ :sqrt | remaining_input], [x | stack], dict) do
compute(remaining_input, [:math.sqrt(x) | stack], dict)
end
def compute([ :drop | remaining_input], [_ | stack], dict) do
compute(remaining_input, stack, dict)
end
def compute([ :dup | remaining_input], [x | stack], dict) do
compute(remaining_input, [x, x | stack], dict)
end
# new definitions!
def compute([ :":", name | remaining_input ], stack, dict) do
{definition, [:";" | remainder]} = Enum.split_while(remaining_input, fn(ins) -> ins != :";" end)
dict = Dict.put(dict, name, definition)
compute(remainder, stack, dict)
end
# side effects!
def compute([ :emit | remaining_input], [x | stack], dict) do
IO.write <<x>>
compute(remaining_input, stack, dict)
end
def compute([ :. | remaining_input], [x | stack], dict) do
IO.puts x
compute(remaining_input, stack, dict)
end
# definition lookup
def compute([ symbol | remaining_input], stack, dict) when is_atom(symbol) do
if Dict.has_key?(dict, symbol) do
compute(remaining_input, dict[symbol] ++ stack, dict)
else
raise "Undefined symbol: #{symbol}"
end
end
# fallthrough
# def compute([n | remaining_input], stack, dict) do
# compute(remaining_input, [n | stack], dict)
# end
# def compute(remaining_input, stack) do
# end
end
# run this inline suite with "elixir #{__ENV__.file} test"
if System.argv |> List.first == "test" do
ExUnit.start
defmodule RPNTest do
use ExUnit.Case, async: true
test "normalizing input list" do
assert RPN.normalize(["1", "5", "+"]) === [1, 5, :+]
end
test "adding 2 numbers" do
assert RPN.initialize(~w[1.0 2.0 +]) === 3.0
end
test "subtracting 2 numbers" do
assert RPN.initialize(~w[3 2 -]) === 1
end
test "multiplying 2 numbers" do
assert RPN.initialize(~w[3 2 *]) === 6
end
test "dividing 2 numbers" do
assert RPN.initialize(~w[3 2 /]) === 1.5
end
test "sequence of simple math operations" do
assert RPN.initialize(~w[ 1 2 3 4 5 * + + + 2 /]) === 13.0
end
test "integer power" do
assert RPN.initialize(~w[ 10 3 ** ]) === 1000
end
test "float power" do
assert RPN.initialize(~w[ 10.0 3.0 ** ]) === 1000.0
end
test "pi" do
assert RPN.initialize(~w[ pi ]) === 3.141592653589793
end
test "sin" do
assert RPN.initialize(~w[ 3 sin ]) === 0.1411200080598672
end
test "cos" do
assert RPN.initialize(~w[ 3 cos ]) === -0.9899924966004454
end
test "tan" do
assert RPN.initialize(~w[ 3 tan ]) === -0.1425465430742778
end
test "sqrt" do
assert RPN.initialize(~w[ 3 sqrt ]) === 1.7320508075688772
end
test "drop" do
assert RPN.initialize(~w[ 3 3 drop ]) === 3
end
test "dup" do
assert RPN.initialize(~w[ 3 dup * ]) === 9
end
test "defining new words" do
assert RPN.initialize(~w[ : square dup * ; 5 square ]) === 25
end
end
else
IO.puts RPN.initialize
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment