Skip to content

Commit

Permalink
Translate labelled arguments to keyword arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Aug 20, 2024
1 parent c99e5c2 commit 1a132a0
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 12 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
12 changes: 11 additions & 1 deletion src/generator.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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, "\""])
Expand Down Expand Up @@ -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(")")
Expand Down
2 changes: 1 addition & 1 deletion src/python.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
13 changes: 8 additions & 5 deletions src/transformer.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down Expand Up @@ -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),
Expand All @@ -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(
Expand Down
41 changes: 40 additions & 1 deletion test/expression_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ def main():
)
}

pub fn call_expression_test() {
pub fn simple_call_expression_test() {
"fn main() {
foo(\"bar\")
}"
Expand All @@ -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)
Expand All @@ -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)",
)
}

0 comments on commit 1a132a0

Please sign in to comment.