From b71aac1dba18583671060e03ca5899e0f9529276 Mon Sep 17 00:00:00 2001 From: Dusty Phillips Date: Wed, 21 Aug 2024 12:46:43 -0300 Subject: [PATCH] Basic support for List syntax --- README.md | 17 ++++++++++----- src/generator.gleam | 33 ++++++++++++++++++++++++++++++ src/python.gleam | 2 ++ src/python_prelude.gleam | 37 +++++++++++++++++++++++++++++++++ src/transformer.gleam | 12 ++++++++--- test/expression_test.gleam | 42 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a551fd3..43ba5c2 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,19 @@ PRs are welcome. ### Some of the things I know are missing -- some complex expressions aren't implemented 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 +- no case expressions - no destructuring/pattern matching in let - no let assert - label aliases aren't supported yet (ie `fn foo(bar bas: Str)`) +- const definitions aren't supported yet (module.constants) +- type aliases aren't supported yet (module.type_aliases) +- block expressions aren't supported yet +- fnCapture and fn expressions are not supported yet + - but fnCapture is supported as part of a Call, so that probably needs to be unwrapped - 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 +- No Result custom types yet +- List custom type is missing `match_args`, other helpers - glance doesn't support comments - 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`) @@ -65,7 +68,11 @@ PRs are welcome. - only compiles one module at a time - copies the prelude module blindly into the directory that contains that one module - eliminate all todos in source code +- No standard library - generate **main** if a module has a main function +- 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 - 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? diff --git a/src/generator.gleam b/src/generator.gleam index f7ad27f..d5048ed 100644 --- a/src/generator.gleam +++ b/src/generator.gleam @@ -75,21 +75,49 @@ fn generate_call_fields(field: python.Field(python.Expression)) -> StringBuilder fn generate_expression(expression: python.Expression) { case expression { python.String(string) -> string_builder.from_strings(["\"", string, "\""]) + python.Number(number) -> string_builder.from_string(number) + python.Bool(value) -> string_builder.from_string(value) + python.Variable(value) -> string_builder.from_string(value) + python.Negate(expression) -> generate_expression(expression) |> string_builder.prepend("-") + python.Not(expression) -> generate_expression(expression) |> string_builder.prepend("not ") + python.Panic(expression) -> generate_expression(expression) |> string_builder.prepend("raise GleamPanic(") |> string_builder.append(")") + python.Todo(expression) -> generate_expression(expression) |> string_builder.prepend("raise NotImplementedError(") |> string_builder.append(")") + + python.List(elements) -> + string_builder.from_string("to_gleam_list([") + |> string_builder.append_builder(generator_helpers.generate_plural( + elements, + generate_expression, + ", ", + )) + |> string_builder.append("])") + + python.ListWithRest(elements, rest) -> + string_builder.from_string("to_gleam_list([") + |> string_builder.append_builder(generator_helpers.generate_plural( + elements, + generate_expression, + ", ", + )) + |> string_builder.append("], ") + |> string_builder.append_builder(generate_expression(rest)) + |> string_builder.append(")") + python.Tuple(expressions) -> string_builder.new() |> string_builder.append("(") @@ -98,15 +126,18 @@ fn generate_expression(expression: python.Expression) { |> generator_helpers.generate_plural(generate_expression, ", "), ) |> string_builder.append(")") + python.TupleIndex(expression, index) -> generate_expression(expression) |> string_builder.append("[") |> string_builder.append(index |> int.to_string) |> string_builder.append("]") + python.FieldAccess(expression, label) -> generate_expression(expression) |> string_builder.append(".") |> string_builder.append(label) + python.RecordUpdate(record, fields) -> string_builder.new() |> string_builder.append("dataclasses.replace(") @@ -118,6 +149,7 @@ fn generate_expression(expression: python.Expression) { ", ", )) |> string_builder.append(")") + python.Call(function, arguments) -> string_builder.new() |> string_builder.append_builder(generate_expression(function)) @@ -128,6 +160,7 @@ fn generate_expression(expression: python.Expression) { |> string_builder.join(", "), ) |> string_builder.append(")") + python.BinaryOperator(name, left, right) -> generate_binop(name, left, right) } diff --git a/src/python.gleam b/src/python.gleam index a2365d7..a573ab5 100644 --- a/src/python.gleam +++ b/src/python.gleam @@ -41,6 +41,8 @@ pub type Expression { Not(Expression) Panic(Expression) Todo(Expression) + List(elements: List(Expression)) + ListWithRest(elements: List(Expression), rest: Expression) TupleIndex(tuple: Expression, index: Int) FieldAccess(container: Expression, label: String) Call(function: Expression, arguments: List(Field(Expression))) diff --git a/src/python_prelude.gleam b/src/python_prelude.gleam index 03a25e2..fe8e9fd 100644 --- a/src/python_prelude.gleam +++ b/src/python_prelude.gleam @@ -4,6 +4,43 @@ import typing class GleamPanic(BaseException): pass + + +GleamListElem = typing.TypeVar(\"GleamListElem\") + + +class GleamList(typing.Generic[GleamListElem]): + def __str__(self): + strs = [] + head = self + + while not head.is_empty: + strs.append(head.value) + head = head.tail + + return \"GleamList([\" + \", \".join(strs) + \"])\" + + +class NonEmptyGleamList(GleamList[GleamListElem]): + __slots__ = [\"value\", \"tail\"] + is_empty = False + + def __init__(self, value: GleamListElem, tail: GleamList[GleamListElem]): + self.value = value + self.tail = tail + + +class EmptyGleamList(GleamList): + __slots__ = [] + is_empty = True + + + +def to_gleam_list(elements: list[GleamListElem], tail: GleamList = EmptyGleamList()): + head = tail + for element in reversed(elements): + head = NonEmptyGleamList(element, head) + return head " pub const prelude = "from gleam_builtins import *\n\n" diff --git a/src/transformer.gleam b/src/transformer.gleam index c2764b4..e6795c1 100644 --- a/src/transformer.gleam +++ b/src/transformer.gleam @@ -176,13 +176,19 @@ fn transform_expression(expression: glance.Expression) -> python.Expression { ) } + glance.List(head, option.Some(rest)) as expr -> + python.ListWithRest( + list.map(head, transform_expression), + transform_expression(rest), + ) + glance.List(head, option.None) as expr -> + python.List(list.map(head, transform_expression)) + glance.BitString(_) as expr | glance.Block(_) as expr | glance.Case(_, _) as expr - | glance.FieldAccess(_, _) as expr | glance.Fn(_, _, _) as expr - | glance.FnCapture(_, _, _, _) as expr - | glance.List(_, _) as expr -> { + | glance.FnCapture(_, _, _, _) as expr -> { pprint.debug(expr) todo as "Several expressions are not implemented yet" } diff --git a/test/expression_test.gleam b/test/expression_test.gleam index 65084cb..1cd4fea 100644 --- a/test/expression_test.gleam +++ b/test/expression_test.gleam @@ -57,6 +57,48 @@ def main(): ) } +pub fn empty_list_expression_test() { + "fn main() { + [] + }" + |> macabre.compile + |> should.be_ok + |> should.equal( + "from gleam_builtins import * + +def main(): + return to_gleam_list([])", + ) +} + +pub fn list_expression_with_contents_test() { + "fn main() { + [1, 2, 3] + }" + |> macabre.compile + |> should.be_ok + |> should.equal( + "from gleam_builtins import * + +def main(): + return to_gleam_list([1, 2, 3])", + ) +} + +pub fn list_expression_with_tail_test() { + "fn main() { + [1, 2, ..[3, 4]] + }" + |> macabre.compile + |> should.be_ok + |> should.equal( + "from gleam_builtins import * + +def main(): + return to_gleam_list([1, 2], to_gleam_list([3, 4]))", + ) +} + pub fn true_expression_test() { "fn main() { True