Packages

Imperative C `switch`, `while`, `do while`, `for` and `break` in Gleam

Current section

Files

Jump to
glimp src glimp.erl
Raw

src/glimp.erl

-module(glimp).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([switch/3, while/3, do_while/3, for/4]).
-export_type([case_block/2, loop_state/1, transformed_case_block/2, switch_state/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" The intent of this module called `Glimp`, short for `Gleam Imperative` is\n"
" to provide close alternatives to the traditional C features `while`,\n"
" `do while`, `for` and `switch` but in a functional language. This will\n"
" allow you to quickly port complex algorithms implemented in C or other\n"
" imperative languages to Gleam. Of course, it would be best to reimplement\n"
" these as functional algorithms. However, this is typically hard,\n"
" error-prone and very time consuming. With this module, an initial\n"
" `make-it-work` port becomes much easier. Once you get this initial port\n"
" working, you can focus your energy on implementing this in a more functional way.\n"
"\n"
" For examples of how to use this library see glimp_test.gleam in the test\n"
" folder of the project.\n"
).
-type case_block(EZM, EZN) :: {case_block, EZM, fun((EZM) -> {EZN, boolean()})}.
-type loop_state(EZO) :: {loop_state, EZO, boolean()}.
-type transformed_case_block(EZP, EZQ) :: {transformed_case_block,
EZP,
fun((EZP) -> switch_state(EZP, EZQ))}.
-type switch_state(EZR, EZS) :: {switch_state,
EZR,
gleam@option:option(EZS),
boolean()}.
-file("src/glimp.gleam", 97).
-spec switch_helper(
switch_state(FAA, FAB),
list(transformed_case_block(FAA, FAB)),
fun((FAA) -> switch_state(FAA, FAB))
) -> switch_state(FAA, FAB).
switch_helper(Switch_state, Transformed_cases, Transformed_default_function) ->
Current_state = erlang:element(2, Switch_state),
Break = erlang:element(4, Switch_state),
case Break of
true ->
Switch_state;
false ->
case Transformed_cases of
[] ->
Transformed_default_function(Current_state);
[First | Rest] ->
Comparison = erlang:element(2, First) =:= Current_state,
case Comparison of
true ->
New_state = (erlang:element(3, First))(
Current_state
),
switch_helper(
New_state,
Rest,
Transformed_default_function
);
false ->
switch_helper(
Switch_state,
Rest,
Transformed_default_function
)
end
end
end.
-file("src/glimp.gleam", 169).
-spec while_helper(
loop_state(FAN),
fun((FAN) -> boolean()),
fun((loop_state(FAN)) -> loop_state(FAN))
) -> loop_state(FAN).
while_helper(State, Pre_run_condition, Transformed_code_to_run) ->
case erlang:element(3, State) of
true ->
State;
false ->
case Pre_run_condition(erlang:element(2, State)) of
false ->
State;
true ->
New_state = Transformed_code_to_run(State),
while_helper(
New_state,
Pre_run_condition,
Transformed_code_to_run
)
end
end.
-file("src/glimp.gleam", 228).
-spec do_while_helper(
loop_state(FAU),
fun((loop_state(FAU)) -> loop_state(FAU)),
fun((FAU) -> boolean())
) -> loop_state(FAU).
do_while_helper(State, Transformed_code_to_run, Post_run_condition) ->
New_state = Transformed_code_to_run(State),
Extended_post_run_condition_check = Post_run_condition(
erlang:element(2, New_state)
)
andalso gleam@bool:negate(erlang:element(3, New_state)),
case Extended_post_run_condition_check of
false ->
New_state;
true ->
do_while_helper(
New_state,
Transformed_code_to_run,
Post_run_condition
)
end.
-file("src/glimp.gleam", 291).
-spec for_helper(
loop_state(FBC),
fun((loop_state(FBC)) -> boolean()),
fun((loop_state(FBC)) -> loop_state(FBC)),
fun((loop_state(FBC)) -> loop_state(FBC))
) -> loop_state(FBC).
for_helper(
State,
Transformed_pre_run_condition,
Transformed_code_to_run,
Transformed_increment_code
) ->
case Transformed_pre_run_condition(State) of
false ->
State;
true ->
Intermediate_state = Transformed_code_to_run(State),
New_state = Transformed_increment_code(Intermediate_state),
for_helper(
New_state,
Transformed_pre_run_condition,
Transformed_code_to_run,
Transformed_increment_code
)
end.
-file("src/glimp.gleam", 328).
-spec transform_case(case_block(FBK, FBL)) -> transformed_case_block(FBK, FBL).
transform_case(Case_block) ->
Value = erlang:element(2, Case_block),
Original_fn = erlang:element(3, Case_block),
New_fn = fun(Val) ->
Result = erlang:element(1, Original_fn(Val)),
Flag = erlang:element(2, Original_fn(Val)),
{switch_state, Value, {some, Result}, Flag}
end,
{transformed_case_block, Value, New_fn}.
-file("src/glimp.gleam", 347).
?DOC(
" Transforms the default function in the switch statement into a more complex\n"
" structure for the switch_helper function.\n"
).
-spec transform_default(fun((FBQ) -> FBR)) -> fun((FBQ) -> switch_state(FBQ, FBR)).
transform_default(Original_fn) ->
New_fn = fun(Val) ->
Result = Original_fn(Val),
{switch_state, Val, {some, Result}, true}
end,
New_fn.
-file("src/glimp.gleam", 73).
-spec switch(EZT, list(case_block(EZT, EZU)), fun((EZT) -> EZU)) -> {ok, EZU} |
{error, nil}.
switch(Expression, Cases, Default) ->
Switch_state = {switch_state, Expression, none, false},
Transformed_cases = gleam@list:map(Cases, fun transform_case/1),
Transformed_default_function = transform_default(Default),
Result = erlang:element(
3,
switch_helper(
Switch_state,
Transformed_cases,
Transformed_default_function
)
),
case Result of
{some, Val} ->
{ok, Val};
none ->
{error, nil}
end.
-file("src/glimp.gleam", 358).
-spec transform_code_to_run(fun((FBU) -> loop_state(FBU))) -> fun((loop_state(FBU)) -> loop_state(FBU)).
transform_code_to_run(Original_fn) ->
fun(State) -> case State of
{loop_state, Value, Flag} ->
Result = Original_fn(Value),
case Result of
{loop_state, New_value, New_flag} ->
{loop_state, New_value, Flag orelse New_flag}
end
end end.
-file("src/glimp.gleam", 149).
?DOC(
"2. IMPLEMENTING WHILE WITH BREAK\n"
" In C syntax, `while` is defined as:\n"
"\n"
" while (condition) {\n"
" // code block to be executed\n"
"}\n"
"\n"
" The `while` function in Gleam is stack-safe and checks the pre_run_condition\n"
" based on the communicated state at the beginning of an iteration of the while loop.\n"
" If the condition is true then the code in the code block handed to the `while`\n"
" function is run and generates a new LoopState in preparation for the next iteration.\n"
" The code in the code block can throw a Break just like in C.\n"
).
-spec while(FAL, fun((FAL) -> boolean()), fun((FAL) -> loop_state(FAL))) -> FAL.
while(State, Pre_run_condition, Code_to_run) ->
Transformed_state = {loop_state, State, false},
Transformed_code_to_run = transform_code_to_run(Code_to_run),
Result = while_helper(
Transformed_state,
Pre_run_condition,
Transformed_code_to_run
),
case Result of
{loop_state, A, _} ->
A
end.
-file("src/glimp.gleam", 209).
?DOC(
"3. IMPLEMENTING DO WHILE WITH BREAK\n"
" In C, the syntax for `do while` is:\n"
"\n"
" do {\n"
" // code block to be executed\n"
" }\n"
" while (condition);\n"
"\n"
" This corresponding `do while` function in Gleam is stack-safe and checks the\n"
" condition based on the communicated state at the end of an iteration of the\n"
" while loop. It therefore runs the code in the `do while` function AT LEAST ONCE.\n"
" If the condition is true then the new state generated by the code is handed to\n"
" the next iteration of the `do while` loop and used with the next run of the code.\n"
" The code in the code block can throw a Break just like in C. If the condition is\n"
" false or break is true then the new state is returned and the loop stops.\n"
" Note that ending the loop returns the new state. This may or may not be desired.\n"
" The alternative is returning the old state. Let me know if there is a desire for\n"
" that feature.\n"
).
-spec do_while(FAS, fun((FAS) -> loop_state(FAS)), fun((FAS) -> boolean())) -> FAS.
do_while(State, Code_to_run, Post_run_condition) ->
Transformed_state = {loop_state, State, false},
Transformed_code_to_run = transform_code_to_run(Code_to_run),
Result = do_while_helper(
Transformed_state,
Transformed_code_to_run,
Post_run_condition
),
case Result of
{loop_state, A, _} ->
A
end.
-file("src/glimp.gleam", 375).
-spec transform_pre_run_condition(fun((FBY) -> boolean())) -> fun((loop_state(FBY)) -> boolean()).
transform_pre_run_condition(Original_fn) ->
fun(State) -> case State of
{loop_state, Value, Flag} ->
Original_fn(Value) andalso gleam@bool:negate(Flag)
end end.
-file("src/glimp.gleam", 264).
?DOC(
"4. IMPLEMENTING FOR WITH BREAK\n"
" In C, a `for`loop is defined as:\n"
"\n"
" for (initialization; condition; increment) {\n"
" // code block to be executed\n"
" }\n"
" The initialization is executed (one time) before the execution of the code block.\n"
" The condition defines the condition for executing the code block.\n"
" And the increment code is executed (every time) after the code block has run.\n"
"\n"
" In Gleam, the `for` function checks the condition using the communicated state at the\n"
" beginning of an iteration.\n"
" If the condition is true then the code block handed to the `for` function is run.\n"
" At the end of a run, a new state is generated that is used for the next\n"
" iteration.\n"
" If the condition is false or break is true then the code returns the final state and stops.\n"
" Just as the while functions, this `for` function is stack-safe.\n"
).
-spec for(
FAZ,
fun((FAZ) -> boolean()),
fun((FAZ) -> loop_state(FAZ)),
fun((FAZ) -> loop_state(FAZ))
) -> FAZ.
for(State, Pre_run_condition, Code_to_run, Post_run_code) ->
Transformed_state = {loop_state, State, false},
Transformed_pre_run_condition = transform_pre_run_condition(
Pre_run_condition
),
Transformed_code_to_run = transform_code_to_run(Code_to_run),
Transformed_increment_code = transform_code_to_run(Post_run_code),
Result = for_helper(
Transformed_state,
Transformed_pre_run_condition,
Transformed_code_to_run,
Transformed_increment_code
),
case Result of
{loop_state, A, _} ->
A
end.