Skip to content

Commit

Permalink
Basic support for List syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Aug 21, 2024
1 parent 1674d9a commit b71aac1
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 8 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -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?
Expand Down
33 changes: 33 additions & 0 deletions src/generator.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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("(")
Expand All @@ -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(")
Expand All @@ -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))
Expand All @@ -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)
}
Expand Down
2 changes: 2 additions & 0 deletions src/python.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down
37 changes: 37 additions & 0 deletions src/python_prelude.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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"
12 changes: 9 additions & 3 deletions src/transformer.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
42 changes: 42 additions & 0 deletions test/expression_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b71aac1

Please sign in to comment.