Skip to content

Commit 1a132a0

Browse files
Translate labelled arguments to keyword arguments
1 parent c99e5c2 commit 1a132a0

File tree

5 files changed

+69
-12
lines changed

5 files changed

+69
-12
lines changed

README.md

+9-4
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,20 @@ Sweet, me too.
4545

4646
PRs are welcome.
4747

48-
### TODO
48+
### Some of the things I know are missing
4949

5050
- some complex expressions aren't implemented yet
51-
- labelled arguments when _constructing_ a record aren't supported yet
51+
- calling functions or constructors with out of order positional args doesn't work in python
52+
- e.g. `Foo(mystr: String, point: #(Int, Int))` can be called with `Foo(#(1, 1), mystr: "Foo")` in gleam
53+
- javascript seems to solve this by automatically reordering the arguments to match the input type
54+
- implicit returns from functions aren't hooked up
55+
- label aliases aren't supported yet (ie `fn foo(bar bas: Str)`)
5256
- haven't really tested with nesting of expressions
5357
- need to print out nice errors when glance fails to parse
5458
- No List or Result custom types yet
5559
- glance doesn't support comments
56-
- glance doesn't fully typecheck (e.g. `2.0 - 1.5` compiles successfully, but should be `2.0 -. 1.5`)
60+
- Not doing anything to avoid collision between gleam identifiers with python keywords
61+
- glance doesn't typecheck (e.g. `2.0 - 1.5` compiles successfully, but should be `2.0 -. 1.5`)
5762
- 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
5863
- no concept of a "project", gleam.toml, downloading dependencies
5964
- only compiles one module at a time
@@ -63,4 +68,4 @@ PRs are welcome.
6368
- custom types with unlabelled fields are not working
6469
- 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?
6570
- I notice that the javascript doesn't generate the wrapping class for custom variants. Can we get away with not having them?
66-
- flesh out this list
71+
- maybe call ruff or black on the files after they are output, if they are installed.

src/generator.gleam

+11-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ fn generate_record_update_fields(
6262
}
6363
}
6464

65+
fn generate_call_fields(field: python.Field(python.Expression)) -> StringBuilder {
66+
case field {
67+
python.UnlabelledField(expression) -> generate_expression(expression)
68+
python.LabelledField(label, expression) ->
69+
generate_expression(expression)
70+
|> string_builder.prepend("=")
71+
|> string_builder.prepend(label)
72+
}
73+
}
74+
6575
fn generate_expression(expression: python.Expression) {
6676
case expression {
6777
python.String(string) -> string_builder.from_strings(["\"", string, "\""])
@@ -113,7 +123,7 @@ fn generate_expression(expression: python.Expression) {
113123
|> string_builder.append("(")
114124
|> string_builder.append_builder(
115125
arguments
116-
|> list.map(generate_expression)
126+
|> list.map(generate_call_fields)
117127
|> string_builder.join(", "),
118128
)
119129
|> string_builder.append(")")

src/python.gleam

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub type Expression {
4343
Todo(Expression)
4444
TupleIndex(tuple: Expression, index: Int)
4545
FieldAccess(container: Expression, label: String)
46-
Call(function: Expression, arguments: List(Expression))
46+
Call(function: Expression, arguments: List(Field(Expression)))
4747
RecordUpdate(record: Expression, fields: List(Field(Expression)))
4848
BinaryOperator(name: BinaryOperator, left: Expression, right: Expression)
4949
}

src/transformer.gleam

+8-5
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ fn transform_function_parameter(
4141

4242
fn transform_call_argument(
4343
argument: glance.Field(glance.Expression),
44-
) -> python.Expression {
44+
) -> python.Field(python.Expression) {
4545
case argument {
46-
glance.Field(label: option.Some(_), item: _) ->
47-
todo as "Labelled arguments are not yet supported"
46+
glance.Field(option.Some(label), expression) ->
47+
python.LabelledField(label, transform_expression(expression))
4848
glance.Field(label: option.None, item: expression) ->
49-
transform_expression(expression)
49+
python.UnlabelledField(transform_expression(expression))
5050
}
5151
}
5252

@@ -105,6 +105,7 @@ fn transform_expression(expression: glance.Expression) -> python.Expression {
105105
python.Not(transform_expression(expression))
106106

107107
glance.Call(function, arguments) -> {
108+
pprint.debug(arguments)
108109
python.Call(
109110
function: transform_expression(function),
110111
arguments: list.map(arguments, transform_call_argument),
@@ -124,7 +125,9 @@ fn transform_expression(expression: glance.Expression) -> python.Expression {
124125

125126
glance.BinaryOperator(glance.Pipe, left, glance.Variable(function)) -> {
126127
// simple pipe left |> foo
127-
python.Call(python.Variable(function), [transform_expression(left)])
128+
python.Call(python.Variable(function), [
129+
python.UnlabelledField(transform_expression(left)),
130+
])
128131
}
129132

130133
glance.BinaryOperator(

test/expression_test.gleam

+40-1
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ def main():
496496
)
497497
}
498498

499-
pub fn call_expression_test() {
499+
pub fn simple_call_expression_test() {
500500
"fn main() {
501501
foo(\"bar\")
502502
}"
@@ -511,6 +511,21 @@ def main():
511511
)
512512
}
513513

514+
pub fn labelled_argument_call_expression_test() {
515+
"fn main() {
516+
foo(\"bar\", baz: \"baz\")
517+
}"
518+
|> macabre.compile
519+
|> should.be_ok
520+
|> should.equal(
521+
"from gleam_builtins import *
522+
523+
def main():
524+
foo(\"bar\", baz=\"baz\")
525+
",
526+
)
527+
}
528+
514529
pub fn record_update_test() {
515530
"pub type Foo {
516531
Bar(a: Int, b: String)
@@ -536,3 +551,27 @@ def main():
536551
bar = dataclasses.replace(foo, b=\"you\")",
537552
)
538553
}
554+
555+
pub fn construct_record_with_label_test() {
556+
"pub type Foo {
557+
Bar(a: Int, b: String)
558+
}
559+
560+
pub fn main() {
561+
let foo = Bar(b: \"who\", a: 1)
562+
}"
563+
|> macabre.compile
564+
|> should.be_ok
565+
|> should.equal(
566+
"from gleam_builtins import *
567+
568+
@dataclasses.dataclass(frozen=True)
569+
class Bar:
570+
a: int
571+
b: str
572+
573+
574+
def main():
575+
foo = Bar(b=\"who\", a=1)",
576+
)
577+
}

0 commit comments

Comments
 (0)