diff --git a/README.md b/README.md
index d572ff3..8bd489f 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,6 @@ 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
@@ -85,7 +84,7 @@ are ) in the codebase, as of the last time that I updated this list.
- 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
diff --git a/src/compiler/internal/generator/statements.gleam b/src/compiler/internal/generator/statements.gleam
index 740ba72..e8768b8 100644
--- a/src/compiler/internal/generator/statements.gleam
+++ b/src/compiler/internal/generator/statements.gleam
@@ -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)
@@ -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(")")
+ }
+}
diff --git a/src/compiler/internal/transformer/patterns.gleam b/src/compiler/internal/transformer/patterns.gleam
index f2789cc..9a79030 100644
--- a/src/compiler/internal/transformer/patterns.gleam
+++ b/src/compiler/internal/transformer/patterns.gleam
@@ -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(_, _) ->
diff --git a/src/compiler/python.gleam b/src/compiler/python.gleam
index 6772d6a..582ee6b 100644
--- a/src/compiler/python.gleam
+++ b/src/compiler/python.gleam
@@ -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),
diff --git a/src/python_prelude.gleam b/src/python_prelude.gleam
index 104c99a..a8022ee 100644
--- a/src/python_prelude.gleam
+++ b/src/python_prelude.gleam
@@ -1,4 +1,5 @@
pub const gleam_builtins = "
+from __future__ import annotations
import dataclasses
import sys
import typing
@@ -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):
diff --git a/test/case_test.gleam b/test/case_test.gleam
index 1958d3e..7a586bb 100644
--- a/test/case_test.gleam
+++ b/test/case_test.gleam
@@ -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]))",
+ )
+}