Skip to content

Commit

Permalink
Basic case statements working
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Aug 23, 2024
1 parent 06a5933 commit 93eb4cc
Show file tree
Hide file tree
Showing 7 changed files with 390 additions and 6 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ PRs are welcome.
### Some of the things I know are missing

- no destructuring/pattern matching in let
- some constructs can map straight to python destructuring
- others may need a custom case
- no let assert
- no case expressions
- empty tuples are probably broken
- 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)
Expand Down
52 changes: 50 additions & 2 deletions src/compiler/generator.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import gleam/int
import gleam/list
import gleam/option
import gleam/string_builder.{type StringBuilder}
import pprint
import python_prelude

fn generate_import(import_: python.Import) -> StringBuilder {
Expand Down Expand Up @@ -191,17 +192,64 @@ fn generate_statement(statement: python.Statement) -> StringBuilder {
|> string_builder.append(" = ")
|> string_builder.append_builder(generate_expression(value))
}
python.Match(cases) ->
string_builder.new()
|> string_builder.append("match _case_subject:\n")
|> string_builder.append_builder(
generate_cases(cases) |> internal.indent(4),
)
// TODO: Deal with cases
python.FunctionDef(function) -> generate_function(function)
}
}

fn generate_cases(cases: List(python.MatchCase)) -> StringBuilder {
case cases {
[] -> string_builder.from_string("pass")
cases -> internal.generate_plural(cases, generate_case, "\n")
}
}

fn generate_case(case_: python.MatchCase) -> StringBuilder {
string_builder.from_string("case ")
|> string_builder.append_builder(generate_pattern(case_.pattern))
|> string_builder.append(":\n")
|> string_builder.append_builder(
generate_block(case_.body) |> internal.indent(4),
)
}

fn generate_pattern(pattern: python.Pattern) -> StringBuilder {
case pattern {
python.PatternWildcard -> string_builder.from_string("_")
python.PatternInt(str)
| python.PatternFloat(str)
| python.PatternVariable(str) -> string_builder.from_string(str)
python.PatternString(str) -> string_builder.from_strings(["\"", str, "\""])
python.PatternAssignment(pattern, name) ->
generate_pattern(pattern)
|> string_builder.append(" as ")
|> string_builder.append(name)
python.PatternTuple(patterns) ->
patterns
|> list.map(generate_pattern)
|> string_builder.join(", ")
|> string_builder.prepend("(")
|> string_builder.append(")")
python.PatternAlternate(patterns) ->
patterns
|> list.map(generate_pattern)
|> string_builder.join(" | ")
}
}

fn generate_parameter(param: python.FunctionParameter) -> StringBuilder {
case param {
python.NameParam(name) -> string_builder.from_string(name)
}
}

fn generate_function_body(statements: List(python.Statement)) -> StringBuilder {
fn generate_block(statements: List(python.Statement)) -> StringBuilder {
case statements {
[] -> string_builder.from_string("pass")
multiple_lines ->
Expand All @@ -222,7 +270,7 @@ fn generate_function(function: python.Function) -> StringBuilder {
))
|> string_builder.append("):\n")
|> string_builder.append_builder(
generate_function_body(function.body) |> internal.indent(4),
generate_block(function.body) |> internal.indent(4),
)
}

Expand Down
6 changes: 5 additions & 1 deletion src/compiler/internal/transformer.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ pub type TransformError {
}

pub type TransformerContext {
TransformerContext(next_function_id: Int, next_block_id: Int)
TransformerContext(
next_function_id: Int,
next_block_id: Int,
next_case_id: Int,
)
}

pub type ExpressionReturn {
Expand Down
55 changes: 55 additions & 0 deletions src/compiler/internal/transformer/patterns.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import compiler/python
import glance
import gleam/list
import pprint

// alternative patterns are sent to us a a list of list of patters.
// the outer list represents alternatives, so 1 | 2 -> becomes [[1], [2]]
// inner loop represents groupings (see `transform_grouped_pattrns`)
// so 1, 2 | 3, 5 becomes [1, 2], [3, 5]
pub fn transform_alternative_patterns(
patterns: List(List(glance.Pattern)),
) -> python.Pattern {
case patterns {
[] -> panic as "missing pattern"
[one_alternative] -> transform_grouped_patterns(one_alternative)
multiple_alternatives ->
python.PatternAlternate(list.map(
multiple_alternatives,
transform_grouped_patterns,
))
}
}

// gleam distinguishes between groups of patterns (e.g: case 1, 2 {x, y -> ...})
// and glance sends those to us as a list of patterns. The python pattern
// for a group of patterns will always be a single tuple pattern.
fn transform_grouped_patterns(patterns: List(glance.Pattern)) -> python.Pattern {
case patterns {
[] -> panic as "missing pattern"
[one_item] -> transform_pattern(one_item)
multiple_items -> transform_pattern(glance.PatternTuple(multiple_items))
}
}

fn transform_pattern(pattern: glance.Pattern) -> python.Pattern {
case pattern {
glance.PatternInt(str) -> python.PatternInt(str)
glance.PatternFloat(str) -> python.PatternFloat(str)
glance.PatternString(str) -> python.PatternString(str)
glance.PatternVariable(str) -> python.PatternVariable(str)
glance.PatternDiscard("") -> python.PatternWildcard
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.PatternAssignment(pattern, name) ->
python.PatternAssignment(transform_pattern(pattern), name)
glance.PatternConcatenate(_, _) ->
todo as "concatenate patterns are not supported yet"
glance.PatternBitString(..) ->
todo as "bitstring patterns are not supported yet"
glance.PatternConstructor(..) ->
todo as "record constructor patterns are not supported yet"
}
}
70 changes: 68 additions & 2 deletions src/compiler/internal/transformer/statements.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import compiler/internal/transformer as internal
import compiler/internal/transformer/patterns
import compiler/python
import glance
import gleam/int
Expand All @@ -23,6 +24,7 @@ pub fn transform_statement_block(
context: internal.TransformerContext(
next_function_id: 0,
next_block_id: 0,
next_case_id: 0,
),
statements: [],
),
Expand Down Expand Up @@ -145,7 +147,7 @@ fn transform_expression(

glance.Block(statements) -> transform_block(context, statements)

glance.Case(..) -> todo as "case expressions not supported yet"
glance.Case(subjects, clauses) -> transform_case(context, subjects, clauses)

glance.TupleIndex(tuple, index) -> {
transform_expression(context, tuple)
Expand Down Expand Up @@ -340,7 +342,7 @@ fn transform_fn(
fn transform_block(
context: internal.TransformerContext,
body: List(glance.Statement),
) {
) -> internal.ExpressionReturn {
let function_name = "_fn_block_" <> int.to_string(context.next_block_id)
let function =
python.Function(function_name, [], transform_statement_block(body))
Expand All @@ -354,6 +356,70 @@ fn transform_block(
)
}

fn transform_case(
context: internal.TransformerContext,
subjects: List(glance.Expression),
clauses: List(glance.Clause),
) -> internal.ExpressionReturn {
let subjects_result = case subjects {
[] -> panic("No subjects!")
[subject] -> transform_expression(context, subject)
multiple -> transform_tuple(context, multiple)
}
let clause_result =
list.fold(
clauses,
internal.TransformState(subjects_result.context, [], []),
fold_case_clause,
)

let function_name = "_fn_case_" <> int.to_string(context.next_case_id)
let function =
python.Function(function_name, [python.NameParam("_case_subject")], [
python.Match(clause_result.item |> list.reverse),
])

internal.ExpressionReturn(
context: internal.TransformerContext(
..subjects_result.context,
next_case_id: context.next_case_id + 1,
),
statements: list.append(subjects_result.statements, [
python.FunctionDef(function),
]),
expression: python.Call(python.Variable(function_name), [
python.UnlabelledField(subjects_result.expression),
]),
)
}

fn fold_case_clause(
state: internal.TransformState(internal.ReversedList(python.MatchCase)),
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)) -> {
todo as "block case clauses not supported yet"
}

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

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

glance.Clause(..) -> {
todo as "multiple clause not implemented yet"
}
}
}

fn transform_pipe(
context: internal.TransformerContext,
left: glance.Expression,
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/python.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub type Statement {
Expression(Expression)
Return(Expression)
FunctionDef(Function)
Match(cases: List(MatchCase))
SimpleAssignment(name: String, value: Expression)
}

Expand Down Expand Up @@ -85,6 +86,23 @@ pub type CustomType {
CustomType(name: String, parameters: List(String), variants: List(Variant))
}

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))
}

pub type Pattern {
PatternWildcard
PatternInt(value: String)
PatternFloat(value: String)
PatternString(value: String)
PatternVariable(value: String)
PatternAssignment(pattern: Pattern, name: String)
PatternTuple(value: List(Pattern))
PatternAlternate(patterns: List(Pattern))
}

pub type Function {
Function(
name: String,
Expand Down
Loading

0 comments on commit 93eb4cc

Please sign in to comment.