From 1a132a0b6649630ea0013c7be39b9be4799e9ea7 Mon Sep 17 00:00:00 2001 From: Dusty Phillips Date: Tue, 20 Aug 2024 17:21:57 -0300 Subject: [PATCH] Translate labelled arguments to keyword arguments --- README.md | 13 ++++++++---- src/generator.gleam | 12 ++++++++++- src/python.gleam | 2 +- src/transformer.gleam | 13 +++++++----- test/expression_test.gleam | 41 +++++++++++++++++++++++++++++++++++++- 5 files changed, 69 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f5e70f2..91daafb 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,20 @@ Sweet, me too. PRs are welcome. -### TODO +### Some of the things I know are missing - some complex expressions aren't implemented yet -- labelled arguments when _constructing_ a record aren't supported yet +- 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 +- 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 - No List or Result custom types yet - glance doesn't support comments -- glance doesn't fully typecheck (e.g. `2.0 - 1.5` compiles successfully, but should be `2.0 -. 1.5`) +- Not doing anything to avoid collision between gleam identifiers with python keywords +- glance doesn't typecheck (e.g. `2.0 - 1.5` compiles successfully, but should be `2.0 -. 1.5`) - not currently generating python type hints (e.g. function arguments and return types), but gleam gives us that info so may as well use it - no concept of a "project", gleam.toml, downloading dependencies - only compiles one module at a time @@ -63,4 +68,4 @@ PRs are welcome. - custom types with unlabelled fields are not working - Given that labelled and unlabelled fields can be mixed on one class, I have a feeling we have to ditch dataclasses. Probably a custom class with slots, a dict of names to indices, and a custom **match_args** that can handle tuple-like _or_ record-like syntax? - I notice that the javascript doesn't generate the wrapping class for custom variants. Can we get away with not having them? -- flesh out this list +- maybe call ruff or black on the files after they are output, if they are installed. diff --git a/src/generator.gleam b/src/generator.gleam index b6870b2..c6c5576 100644 --- a/src/generator.gleam +++ b/src/generator.gleam @@ -62,6 +62,16 @@ fn generate_record_update_fields( } } +fn generate_call_fields(field: python.Field(python.Expression)) -> StringBuilder { + case field { + python.UnlabelledField(expression) -> generate_expression(expression) + python.LabelledField(label, expression) -> + generate_expression(expression) + |> string_builder.prepend("=") + |> string_builder.prepend(label) + } +} + fn generate_expression(expression: python.Expression) { case expression { python.String(string) -> string_builder.from_strings(["\"", string, "\""]) @@ -113,7 +123,7 @@ fn generate_expression(expression: python.Expression) { |> string_builder.append("(") |> string_builder.append_builder( arguments - |> list.map(generate_expression) + |> list.map(generate_call_fields) |> string_builder.join(", "), ) |> string_builder.append(")") diff --git a/src/python.gleam b/src/python.gleam index a8fe840..1f77bff 100644 --- a/src/python.gleam +++ b/src/python.gleam @@ -43,7 +43,7 @@ pub type Expression { Todo(Expression) TupleIndex(tuple: Expression, index: Int) FieldAccess(container: Expression, label: String) - Call(function: Expression, arguments: List(Expression)) + Call(function: Expression, arguments: List(Field(Expression))) RecordUpdate(record: Expression, fields: List(Field(Expression))) BinaryOperator(name: BinaryOperator, left: Expression, right: Expression) } diff --git a/src/transformer.gleam b/src/transformer.gleam index cd91f8f..7293cfa 100644 --- a/src/transformer.gleam +++ b/src/transformer.gleam @@ -41,12 +41,12 @@ fn transform_function_parameter( fn transform_call_argument( argument: glance.Field(glance.Expression), -) -> python.Expression { +) -> python.Field(python.Expression) { case argument { - glance.Field(label: option.Some(_), item: _) -> - todo as "Labelled arguments are not yet supported" + glance.Field(option.Some(label), expression) -> + python.LabelledField(label, transform_expression(expression)) glance.Field(label: option.None, item: expression) -> - transform_expression(expression) + python.UnlabelledField(transform_expression(expression)) } } @@ -105,6 +105,7 @@ fn transform_expression(expression: glance.Expression) -> python.Expression { python.Not(transform_expression(expression)) glance.Call(function, arguments) -> { + pprint.debug(arguments) python.Call( function: transform_expression(function), arguments: list.map(arguments, transform_call_argument), @@ -124,7 +125,9 @@ fn transform_expression(expression: glance.Expression) -> python.Expression { glance.BinaryOperator(glance.Pipe, left, glance.Variable(function)) -> { // simple pipe left |> foo - python.Call(python.Variable(function), [transform_expression(left)]) + python.Call(python.Variable(function), [ + python.UnlabelledField(transform_expression(left)), + ]) } glance.BinaryOperator( diff --git a/test/expression_test.gleam b/test/expression_test.gleam index 984af99..b20481c 100644 --- a/test/expression_test.gleam +++ b/test/expression_test.gleam @@ -496,7 +496,7 @@ def main(): ) } -pub fn call_expression_test() { +pub fn simple_call_expression_test() { "fn main() { foo(\"bar\") }" @@ -511,6 +511,21 @@ def main(): ) } +pub fn labelled_argument_call_expression_test() { + "fn main() { + foo(\"bar\", baz: \"baz\") + }" + |> macabre.compile + |> should.be_ok + |> should.equal( + "from gleam_builtins import * + +def main(): + foo(\"bar\", baz=\"baz\") + ", + ) +} + pub fn record_update_test() { "pub type Foo { Bar(a: Int, b: String) @@ -536,3 +551,27 @@ def main(): bar = dataclasses.replace(foo, b=\"you\")", ) } + +pub fn construct_record_with_label_test() { + "pub type Foo { + Bar(a: Int, b: String) + } + + pub fn main() { + let foo = Bar(b: \"who\", a: 1) + }" + |> macabre.compile + |> should.be_ok + |> should.equal( + "from gleam_builtins import * + +@dataclasses.dataclass(frozen=True) +class Bar: + a: int + b: str + + +def main(): + foo = Bar(b=\"who\", a=1)", + ) +}