Skip to content

Commit

Permalink
Support list patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Aug 25, 2024
1 parent 197ce74 commit 040d41d
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 22 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,14 @@ are ) in the codebase, as of the last time that I updated this list.
- debatable whether to make them a `def` right on the class or have the def be defined somewhere and just attach it like other fields
- Fields that are "HoleType" are not supported and I don't even know what that means
- IN case statements, the following patterns are not supported:
- List patterns
- Concatenate patterns
- Bitstring patterns (bytes)
- Destructuring in assignments is not supported yet
- (EASY) tuple destructuring can map straight to python destructuring
- other structures will maybe need a match statement?
- Use statements are not supported yet

### Some other the things I know are missing
### Some other things I know are missing

- empty tuples are probably broken
- (EASY) I used parens instead of a `,` like a total python NOOB
Expand Down
19 changes: 19 additions & 0 deletions src/compiler/internal/generator/statements.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ fn generate_pattern(pattern: python.Pattern) -> StringBuilder {
|> string_builder.join(", ")
|> string_builder.prepend("(")
|> string_builder.append(")")
python.PatternList(elements, rest) -> generate_pattern_list(elements, rest)
python.PatternAlternate(patterns) ->
patterns
|> list.map(generate_pattern)
Expand Down Expand Up @@ -123,3 +124,21 @@ fn generate_pattern_constructor_field(
python.UnlabelledField(pattern) -> generate_pattern(pattern)
}
}

/// Lists are weird. Gleam syntax is like [a, b, c, ..rest]
/// But the pattern in python has to match a linked list.
/// The pattern is essentially GleamList(a, GleamList(b, GleamList(c, rest)))
/// potential optimization: make tail recursive by carrying the number of
/// closing parenns forward
fn generate_pattern_list(elements, rest) -> StringBuilder {
case elements, rest {
[], option.None -> string_builder.from_string("None")
[], option.Some(pattern) -> generate_pattern(pattern)
[head, ..others], rest ->
string_builder.from_string("GleamList(")
|> string_builder.append_builder(generate_pattern(head))
|> string_builder.append(", ")
|> string_builder.append_builder(generate_pattern_list(others, rest))
|> string_builder.append(")")
}
}
6 changes: 5 additions & 1 deletion src/compiler/internal/transformer/patterns.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ fn transform_pattern(pattern: glance.Pattern) -> python.Pattern {
glance.PatternDiscard(str) -> python.PatternVariable("_" <> str)
glance.PatternTuple(patterns) ->
python.PatternTuple(list.map(patterns, transform_pattern))
glance.PatternList(_, _) -> todo as "list patterns are not supported yet"
glance.PatternList(elems, rest) ->
python.PatternList(
list.map(elems, transform_pattern),
option.map(rest, transform_pattern),
)
glance.PatternAssignment(pattern, name) ->
python.PatternAssignment(transform_pattern(pattern), name)
glance.PatternConcatenate(_, _) ->
Expand Down
1 change: 1 addition & 0 deletions src/compiler/python.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub type Pattern {
PatternVariable(value: String)
PatternAssignment(pattern: Pattern, name: String)
PatternTuple(value: List(Pattern))
PatternList(elems: List(Pattern), rest: option.Option(Pattern))
PatternAlternate(patterns: List(Pattern))
PatternConstructor(
module: option.Option(String),
Expand Down
32 changes: 13 additions & 19 deletions src/python_prelude.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub const gleam_builtins = "
from __future__ import annotations
import dataclasses
import sys
import typing
Expand All @@ -12,36 +13,29 @@ GleamListElem = typing.TypeVar('GleamListElem')
class GleamList(typing.Generic[GleamListElem]):
__slots__ = [\"value\", \"tail\"]
__match_args__ = (\"value\", \"tail\")
def __init__(self, value: GleamListElem, tail: GleamList[GleamListElem] | None):
self.value = value
self.tail = tail
def __str__(self):
strs = []
head = self
while not head.is_empty:
strs.append(head.value)
while head is not None:
strs.append(str(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
return \"GleamList([\" + \", \".join(strs) + \"])\"
def to_gleam_list(elements: list[GleamListElem], tail: GleamList = EmptyGleamList()):
def to_gleam_list(elements: list[GleamListElem], tail: GleamList | None=None):
head = tail
for element in reversed(elements):
head = NonEmptyGleamList(element, head)
head = GleamList(element, head)
return head
def gleam_bitstring_segments_to_bytes(*segments):
Expand Down
149 changes: 149 additions & 0 deletions test/case_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,152 @@ def main():
return _fn_case_0(1)",
)
}

pub fn case_empty_list_test() {
"pub fn main() {
case [] {
[] -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case None:
return 1
return _fn_case_0(to_gleam_list([]))",
)
}

pub fn case_single_element_list_test() {
"pub fn main() {
case [1] {
[1] -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case GleamList(1, None):
return 1
return _fn_case_0(to_gleam_list([1]))",
)
}

pub fn case_multi_element_list_test() {
"pub fn main() {
case [1, 2, 3] {
[1, 2, 3] -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case GleamList(1, GleamList(2, GleamList(3, None))):
return 1
return _fn_case_0(to_gleam_list([1, 2, 3]))",
)
}

// The gleam formatter doesn't permit this scenario, but it is encountered
// during recursion
pub fn case_empty_rest_case_test() {
"pub fn main() {
case [1, 2, 3] {
rest -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case rest:
return 1
return _fn_case_0(to_gleam_list([1, 2, 3]))",
)
}

pub fn single_element_with_rest_case_test() {
"pub fn main() {
case [1, 2, 3] {
[1, ..rest] -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case GleamList(1, rest):
return 1
return _fn_case_0(to_gleam_list([1, 2, 3]))",
)
}

pub fn multi_element_with_rest_case_test() {
"pub fn main() {
case [1, 2, 3] {
[1, 2, ..rest] -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case GleamList(1, GleamList(2, rest)):
return 1
return _fn_case_0(to_gleam_list([1, 2, 3]))",
)
}

pub fn unnamed_rest_test() {
"pub fn main() {
case [1, 2, 3] {
[1, 2, ..] -> 1
}
}
"
|> compiler.compile
|> should.be_ok
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case GleamList(1, GleamList(2, _)):
return 1
return _fn_case_0(to_gleam_list([1, 2, 3]))",
)
}

0 comments on commit 040d41d

Please sign in to comment.