From 1674d9add90df0226a1d86b01bc13303c79b832b Mon Sep 17 00:00:00 2001 From: Dusty Phillips Date: Wed, 21 Aug 2024 11:19:37 -0300 Subject: [PATCH] Map implicit to explicit returns --- README.md | 3 +- src/generator.gleam | 3 ++ src/internal/transformer.gleam | 12 +++++ src/python.gleam | 1 + src/transformer.gleam | 15 +++++- test/expression_test.gleam | 87 ++++++++++++---------------------- test/function_test.gleam | 14 ++++++ test/transformer_test.gleam | 27 +++++++++++ 8 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 src/internal/transformer.gleam create mode 100644 test/transformer_test.gleam diff --git a/README.md b/README.md index 96c2988..a551fd3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ PRs are welcome. - calling functions or constructors with out of order positional args doesn't work in python - e.g. `Foo(mystr: String, point: #(Int, Int))` can be called with `Foo(#(1, 1), mystr: "Foo")` in gleam - javascript seems to solve this by automatically reordering the arguments to match the input type -- implicit returns from functions aren't hooked up +- no destructuring/pattern matching in let +- no let assert - label aliases aren't supported yet (ie `fn foo(bar bas: Str)`) - haven't really tested with nesting of expressions - need to print out nice errors when glance fails to parse diff --git a/src/generator.gleam b/src/generator.gleam index d313a8d..f7ad27f 100644 --- a/src/generator.gleam +++ b/src/generator.gleam @@ -138,6 +138,9 @@ fn generate_statement(statement: python.Statement) -> StringBuilder { python.Expression(expression) -> generate_expression(expression) |> generator_helpers.append_if_not_empty("\n") + python.Return(expression) -> + string_builder.from_string("return ") + |> string_builder.append_builder(generate_expression(expression)) python.SimpleAssignment(name, value) -> { string_builder.new() |> string_builder.append(name) diff --git a/src/internal/transformer.gleam b/src/internal/transformer.gleam new file mode 100644 index 0000000..6b6ede6 --- /dev/null +++ b/src/internal/transformer.gleam @@ -0,0 +1,12 @@ +import gleam/list + +pub fn transform_last(elements: List(a), transformer: fn(a) -> a) -> List(a) { + // This makes three iterations over elements. It may be a candidate for optimization + // since it happens on all the statements in every function body. I can find ways + // to do it with only two iterations, or with one iteration but transforming every + // element and discarding the intermediates, but I didn't have any luck with a solution + // that could do it in only one iteration. + let length = list.length(elements) + let #(head, tail) = list.split(elements, length - 1) + list.append(head, tail |> list.map(transformer)) +} diff --git a/src/python.gleam b/src/python.gleam index 0cc425a..a2365d7 100644 --- a/src/python.gleam +++ b/src/python.gleam @@ -50,6 +50,7 @@ pub type Expression { pub type Statement { Expression(Expression) + Return(Expression) SimpleAssignment(name: String, value: Expression) } diff --git a/src/transformer.gleam b/src/transformer.gleam index 9965048..c2764b4 100644 --- a/src/transformer.gleam +++ b/src/transformer.gleam @@ -1,6 +1,7 @@ import glance import gleam/list import gleam/option +import internal/transformer import pprint import python @@ -20,6 +21,17 @@ fn maybe_extract_external( } } +fn add_return_if_returnable_expression( + statement: python.Statement, +) -> python.Statement { + case statement { + python.Expression(python.Panic(_)) -> statement + python.Expression(python.Todo(_)) -> statement + python.Expression(expr) -> python.Return(expr) + statement -> statement + } +} + fn transform_function_parameter( function_parameter: glance.FunctionParameter, ) -> python.FunctionParameter { @@ -201,7 +213,8 @@ fn transform_function(function: glance.Function) -> python.Function { python.Function( name: function.name, parameters: list.map(function.parameters, transform_function_parameter), - body: list.map(function.body, transform_statement), + body: list.map(function.body, transform_statement) + |> transformer.transform_last(add_return_if_returnable_expression), ) } diff --git a/test/expression_test.gleam b/test/expression_test.gleam index b20481c..65084cb 100644 --- a/test/expression_test.gleam +++ b/test/expression_test.gleam @@ -11,8 +11,7 @@ pub fn string_expression_test() { "from gleam_builtins import * def main(): - \"bar\" - ", + return \"bar\"", ) } @@ -26,8 +25,7 @@ pub fn int_expression_test() { "from gleam_builtins import * def main(): - 42 - ", + return 42", ) } @@ -41,8 +39,7 @@ pub fn float_expression_test() { "from gleam_builtins import * def main(): - 12.5 - ", + return 12.5", ) } @@ -56,8 +53,7 @@ pub fn tuple_expression_test() { "from gleam_builtins import * def main(): - (42, 12.5, \"foo\") - ", + return (42, 12.5, \"foo\")", ) } @@ -71,8 +67,7 @@ pub fn true_expression_test() { "from gleam_builtins import * def main(): - True - ", + return True", ) } @@ -86,8 +81,7 @@ pub fn false_expression_test() { "from gleam_builtins import * def main(): - False - ", + return False", ) } @@ -101,8 +95,7 @@ pub fn variable_expression_test() { "from gleam_builtins import * def main(): - println(a) - ", + return println(a)", ) } @@ -206,8 +199,7 @@ pub fn tuple_index_test() { "from gleam_builtins import * def main(): - (42, 12.5, \"foo\")[1] - ", + return (42, 12.5, \"foo\")[1]", ) } @@ -221,8 +213,7 @@ pub fn field_access_test() { "from gleam_builtins import * def main(): - foo.b - ", + return foo.b", ) } @@ -236,8 +227,7 @@ pub fn binop_int_add_test() { "from gleam_builtins import * def main(): - 40 + 2 - ", + return 40 + 2", ) } @@ -251,8 +241,7 @@ pub fn binop_float_add_test() { "from gleam_builtins import * def main(): - 40.2 + 2.5 - ", + return 40.2 + 2.5", ) } @@ -266,8 +255,7 @@ pub fn binop_concat_add_test() { "from gleam_builtins import * def main(): - \"hello \" + \"world\" - ", + return \"hello \" + \"world\"", ) } @@ -281,8 +269,7 @@ pub fn binop_int_sub_test() { "from gleam_builtins import * def main(): - 40 - 2 - ", + return 40 - 2", ) } @@ -296,8 +283,7 @@ pub fn binop_float_sub_test() { "from gleam_builtins import * def main(): - 40.2 - 2.5 - ", + return 40.2 - 2.5", ) } @@ -311,8 +297,7 @@ pub fn binop_int_div_test() { "from gleam_builtins import * def main(): - 40 // 2 - ", + return 40 // 2", ) } @@ -326,8 +311,7 @@ pub fn binop_float_div_test() { "from gleam_builtins import * def main(): - 40.2 / 2.5 - ", + return 40.2 / 2.5", ) } @@ -341,8 +325,7 @@ pub fn binop_int_modulo_test() { "from gleam_builtins import * def main(): - 5 % 2 - ", + return 5 % 2", ) } @@ -356,8 +339,7 @@ pub fn equality_test() { "from gleam_builtins import * def main(): - 5 == 5 - ", + return 5 == 5", ) } @@ -371,8 +353,7 @@ pub fn inequality_test() { "from gleam_builtins import * def main(): - 5 != 2 - ", + return 5 != 2", ) } @@ -386,8 +367,7 @@ pub fn lt_int_test() { "from gleam_builtins import * def main(): - 5 < 2 - ", + return 5 < 2", ) } @@ -401,8 +381,7 @@ pub fn lt_float_test() { "from gleam_builtins import * def main(): - 5.0 < 2.0 - ", + return 5.0 < 2.0", ) } @@ -416,8 +395,7 @@ pub fn lt_eq_int_test() { "from gleam_builtins import * def main(): - 5 <= 2 - ", + return 5 <= 2", ) } @@ -431,8 +409,7 @@ pub fn lt_eq_float_test() { "from gleam_builtins import * def main(): - 5.0 <= 2.0 - ", + return 5.0 <= 2.0", ) } @@ -446,8 +423,7 @@ pub fn logical_or_test() { "from gleam_builtins import * def main(): - True or False - ", + return True or False", ) } @@ -461,8 +437,7 @@ pub fn logical_and_test() { "from gleam_builtins import * def main(): - True and False - ", + return True and False", ) } @@ -476,8 +451,7 @@ pub fn simple_pipe_test() { "from gleam_builtins import * def main(): - println(\"foo\") - ", + return println(\"foo\")", ) } @@ -491,8 +465,7 @@ pub fn capture_pipe_test() { "from gleam_builtins import * def main(): - println(\"a\", \"foo\", \"b\") - ", + return println(\"a\", \"foo\", \"b\")", ) } @@ -506,8 +479,7 @@ pub fn simple_call_expression_test() { "from gleam_builtins import * def main(): - foo(\"bar\") - ", + return foo(\"bar\")", ) } @@ -521,8 +493,7 @@ pub fn labelled_argument_call_expression_test() { "from gleam_builtins import * def main(): - foo(\"bar\", baz=\"baz\") - ", + return foo(\"bar\", baz=\"baz\")", ) } diff --git a/test/function_test.gleam b/test/function_test.gleam index b9aee79..1dc5a6c 100644 --- a/test/function_test.gleam +++ b/test/function_test.gleam @@ -101,3 +101,17 @@ def func2(): pass", ) } + +pub fn function_with_return_value_test() { + "fn greet() -> String { + \"hello world\" + }" + |> macabre.compile + |> should.be_ok + |> should.equal( + "from gleam_builtins import * + +def greet(): + return \"hello world\"", + ) +} diff --git a/test/transformer_test.gleam b/test/transformer_test.gleam new file mode 100644 index 0000000..2ffe48d --- /dev/null +++ b/test/transformer_test.gleam @@ -0,0 +1,27 @@ +import gleam/list +import gleeunit/should +import internal/transformer + +pub fn transform_last_empty_test() { + [] + |> transformer.transform_last(fn(a) { a }) + |> should.equal([]) +} + +pub fn transform_last_single_test() { + ["a"] + |> transformer.transform_last(fn(_) { "b" }) + |> should.equal(["b"]) +} + +pub fn transform_last_two_element_test() { + ["a", "b"] + |> transformer.transform_last(fn(_) { "c" }) + |> should.equal(["a", "c"]) +} + +pub fn transform_last_three_element_test() { + ["a", "b", "c"] + |> transformer.transform_last(fn(_) { "d" }) + |> should.equal(["a", "b", "d"]) +}