Skip to content

Instantly share code, notes, and snippets.

@macintux
Last active December 20, 2015 14:39
Show Gist options
  • Save macintux/6148225 to your computer and use it in GitHub Desktop.
Save macintux/6148225 to your computer and use it in GitHub Desktop.
%%% @author John Daily
%%% @copyright (C) 2013, John Daily
%%% @doc
%%%
%%% @end
%%% Created : 3 Aug 2013 by John Daily
-module(etude51).
-compile(export_all).
area() ->
ask_for_values(
validate_shape(get_input(string, "R)ectangle, T)riangle, or E)llipse"),
[{"r", rectangle},
{"R", rectangle},
{"t", triangle},
{"T", triangle},
{"e", ellipse},
{"E", ellipse}]
)
).
promptify(String) ->
String ++ " > ".
%% I miss Perl's chomp
chomp(String) ->
re:replace(String, "\\s+$", "", [{return, list}]).
get_line(Prompt) ->
chomp(io:get_line(Prompt)).
%% Technically, validate + optionally convert
validate(Input, [Fun|T]) ->
validate_next(Fun(Input), T).
validate_next({ok, Val}, []) ->
{ok, Val};
validate_next({ok, Val}, [Fun|T]) ->
validate_next(Fun(Val), T);
validate_next(error, _List) ->
error.
%% get_input will, unlike the design sketched out in the étude, make
%% certain that the type of the input matches the intent. If we
%% specify string, make sure it's a non-zero length string; if it's a
%% float, make sure the value parses to a float.
%%
%% Because the best way to validate the input is a float is to run the
%% conversion, the converted value will be returned.
get_input(string, Prompt) ->
repeat_if_error(validate(get_line(promptify(Prompt)),
[fun reject_blank/1]),
string, Prompt);
get_input(float, Prompt) ->
repeat_if_error(validate(get_line(promptify(Prompt)),
[fun reject_blank/1, fun reject_non_float/1]),
float, Prompt).
repeat_if_error({ok, Val}, _Type, _Prompt) ->
Val;
repeat_if_error(error, Type, Prompt) ->
get_input(Type, Prompt).
reject_blank(String) ->
case re:run(String, "\\S", [{capture, none}]) of
match ->
{ok, string:strip(String)};
nomatch ->
error
end.
conversion_check({error, _}, {error, _}) ->
error;
conversion_check({Value, []}, _Int) ->
{ok, Value};
conversion_check(_Float, {Value, []}) ->
{ok, Value * 1.0}; %% floatify that int
conversion_check(_, _) ->
error.
reject_non_float(String) ->
conversion_check(string:to_float(String),
string:to_integer(String)).
%% returns rectangle, triangle, ellipse, or {undefined, String}
validate_shape(String, Choices) ->
proplists:get_value(String, Choices, {undefined, String}).
ask_for_values({undefined, Input}) ->
io:format("Unknown shape ~s~n", [Input]);
ask_for_values(rectangle) ->
area(fun(X, Y) -> X * Y * 1.0 end,
{get_input(float, "Enter width"),
get_input(float, "Enter height")});
ask_for_values(triangle) ->
area(fun(X, Y) -> (X * Y) / 2.0 end,
{get_input(float, "Enter base"),
get_input(float, "Enter height")});
ask_for_values(ellipse) ->
area(fun(X, Y) -> (math:pi() * X * Y) / 2.0 end,
{get_input(float, "Enter major axis length"),
get_input(float, "Enter minor axis length")}).
%% Because get_input/2 is guaranteed to give us numbers, we just have
%% to sanity check the values
area(_Fun, {X, Y}) when X < 0 orelse Y < 0 ->
inputs_must_be_non_negative;
area(Fun, {X, Y}) ->
Fun(X, Y).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment