Skip to content

Commit

Permalink
Support case guards
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Sep 1, 2024
1 parent aa22170 commit d463800
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ Some tasks below are marked easy if you want to get started.

### High Pri

- case guards are not supported yet
- Functions as type fields are not supported yet
- e.g pub type Foo {Foo(x: () -> String)}
- 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
- IN case statements, the following patterns are not supported:
- Concatenate patterns
Expand All @@ -133,7 +133,7 @@ Some tasks below are marked easy if you want to get started.
- glance doesn't have (much of) a typechecker
- Might be able to extract one from [gig](https://github.com/schurhammer/gig)
- Code is very not commented
- Should be putting public types, functions, and constants in `__all__`
- (EASY) Should be putting public types, functions, and constants in `__all__`

### Low Pri

Expand Down
12 changes: 12 additions & 0 deletions src/compiler/internal/generator/statements.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ fn generate_cases(cases: List(python.MatchCase)) -> StringBuilder {
fn generate_case(case_: python.MatchCase) -> StringBuilder {
string_builder.from_string("case ")
|> string_builder.append_builder(generate_pattern(case_.pattern))
|> string_builder.append_builder(generate_case_guard(case_.guard))
|> string_builder.append(":\n")
|> string_builder.append_builder(
generate_block(case_.body) |> internal.indent(4),
Expand Down Expand Up @@ -157,3 +158,14 @@ fn generate_pattern_list(elements, rest) -> StringBuilder {
|> string_builder.append(")")
}
}

fn generate_case_guard(guard: option.Option(python.Expression)) -> StringBuilder {
case guard {
option.None -> string_builder.new()
option.Some(expression) ->
string_builder.from_string(" if ")
|> string_builder.append_builder(expressions.generate_expression(
expression,
))
}
}
9 changes: 9 additions & 0 deletions src/compiler/internal/transformer.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import compiler/python
import gleam/list
import gleam/option

pub type ReversedList(a) =
List(a)
Expand Down Expand Up @@ -32,6 +33,14 @@ pub type ExpressionReturn {
)
}

pub type OptionalExpressionReturn {
OptionalExpressionReturn(
context: TransformerContext,
statements: List(python.Statement),
expression: option.Option(python.Expression),
)
}

pub type TransformState(a) {
TransformState(
context: TransformerContext,
Expand Down
36 changes: 28 additions & 8 deletions src/compiler/internal/transformer/statements.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import glance
import gleam/int
import gleam/list
import gleam/option
import pprint

// a block is a scope, so context can be reset at this level.
//
Expand Down Expand Up @@ -452,35 +453,54 @@ fn fold_case_clause(
clause: glance.Clause,
) -> internal.TransformState(internal.ReversedList(python.MatchCase)) {
case clause {
glance.Clause(guard: option.Some(_), ..) ->
todo as "Case guards not implemented yet"

glance.Clause(pattern_list, option.None, glance.Block(statements)) -> {
glance.Clause(pattern_list, guard, glance.Block(statements)) -> {
let python_pattern = patterns.transform_alternative_patterns(pattern_list)
let guard_return = transform_optional_expression(state.context, guard)
let statements_result =
transform_statement_block_with_context(state.context, statements)
transform_statement_block_with_context(guard_return.context, statements)
internal.TransformState(
statements_result.context,
state.statements,
state.item
|> list.prepend(python.MatchCase(
python_pattern,
guard_return.expression,
statements_result.statements,
)),
)
}

glance.Clause(pattern_list, option.None, body) -> {
glance.Clause(pattern_list, guard, body) -> {
let python_pattern = patterns.transform_alternative_patterns(pattern_list)
let body_result = transform_expression(state.context, body)
let guard_return = transform_optional_expression(state.context, guard)
let body_result = transform_expression(guard_return.context, body)

internal.merge_state_prepend(state, body_result, fn(expr) {
python.MatchCase(python_pattern, [python.Return(expr)])
python.MatchCase(python_pattern, guard_return.expression, [
python.Return(expr),
])
})
}
}
}

fn transform_optional_expression(
context: internal.TransformerContext,
expression: option.Option(glance.Expression),
) -> internal.OptionalExpressionReturn {
case expression {
option.None -> internal.OptionalExpressionReturn(context, [], option.None)
option.Some(expression) -> {
let expression_return = transform_expression(context, expression)
internal.OptionalExpressionReturn(
expression_return.context,
expression_return.statements,
option.Some(expression_return.expression),
)
}
}
}

fn transform_pipe(
context: internal.TransformerContext,
left: glance.Expression,
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/python.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ pub type CustomType {
pub type MatchCase {
// Inner List is collecting tuples together, outer list is patterns that get or'd together
// e.g. 1,2 | 3, 4 becomes [[1,2], [3, 4]]
MatchCase(pattern: Pattern, body: List(Statement))
MatchCase(
pattern: Pattern,
guard: option.Option(Expression),
body: List(Statement),
)
}

pub type Pattern {
Expand Down
30 changes: 30 additions & 0 deletions test/compiler/case_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,33 @@ def main():
return _fn_case_0(to_gleam_list([1, 2, 3]))",
)
}

pub fn case_guard_test() {
"pub fn main() -> Nil {
case num {
0 -> \"Just zero\"
x if x < 0 -> \"So negative\"
x if x % 2 == 0 -> \"Positively even\"
_ -> \"Somewhat odd\"
}
}"
|> glance.module
|> should.be_ok
|> compiler.compile_module
|> should.equal(
"from gleam_builtins import *
def main():
def _fn_case_0(_case_subject):
match _case_subject:
case 0:
return \"Just zero\"
case x if x < 0:
return \"So negative\"
case x if x % 2 == 0:
return \"Positively even\"
case _:
return \"Somewhat odd\"
return _fn_case_0(num)",
)
}

0 comments on commit d463800

Please sign in to comment.