diff --git a/birdie_snapshots/labelled_parameter.accepted b/birdie_snapshots/labelled_parameter.accepted new file mode 100644 index 0000000..895aa5f --- /dev/null +++ b/birdie_snapshots/labelled_parameter.accepted @@ -0,0 +1,15 @@ +--- +version: 1.4.1 +title: labelled_parameter +file: ./test/trick_test.gleam +test_name: labelled_parameter_test +--- +fn add(a a, b b) { + a + b +} + +fn main() { + add(1, 2) +} + + diff --git a/src/trick.gleam b/src/trick.gleam index 3979fae..469c7af 100644 --- a/src/trick.gleam +++ b/src/trick.gleam @@ -27,6 +27,7 @@ pub type Error { InvalidTupleAccess(type_: Type) InvalidCall(type_: Type) IncorrectNumberOfArguments(expected: Int, got: Int) + UnlabelledParameterAfterLabelledParameter } type Compiled { @@ -674,22 +675,26 @@ fn list_at(list: List(a), index: Int, length: Int) -> Result(a, Int) { } } -pub opaque type FunctionBuilder { +pub type Unlabelled + +pub type Labelled + +pub opaque type FunctionBuilder(labelling) { FunctionBuilder(parameters: List(Parameter), body: Statement) } type Parameter { - Parameter(name: String, type_: Type) + Parameter(name: String, label: Option(String), type_: Type) } -pub fn anonymous(function: FunctionBuilder) -> Expression { +pub fn anonymous(function: FunctionBuilder(Unlabelled)) -> Expression { use body <- compile_statement(function.body) let parameter_list = [ doc.break("(", "("), function.parameters - |> list.map(fn(parameter) { doc.from_string(parameter.name) }) + |> list.map(parameter_to_doc) |> doc.join(doc.break(", ", ",")), ] |> doc.concat @@ -726,16 +731,41 @@ pub fn anonymous(function: FunctionBuilder) -> Expression { pub fn parameter( name: String, type_: Type, - continue: fn(Expression) -> FunctionBuilder, -) -> FunctionBuilder { + continue: fn(Expression) -> FunctionBuilder(a), +) -> FunctionBuilder(a) { + let expression = Compiled(doc.from_string(name), type_) + let function = continue(return(expression)) + + let parameter = Parameter(name, None, type_) + FunctionBuilder(..function, parameters: [parameter, ..function.parameters]) +} + +pub fn labelled_parameter( + label: String, + name: String, + type_: Type, + continue: fn(Expression) -> FunctionBuilder(a), +) -> FunctionBuilder(Labelled) { let expression = Compiled(doc.from_string(name), type_) let function = continue(return(expression)) - let parameter = Parameter(name, type_) + let parameter = Parameter(name, Some(label), type_) FunctionBuilder(..function, parameters: [parameter, ..function.parameters]) } -pub fn function_body(body: Statement) -> FunctionBuilder { +fn parameter_to_doc(parameter: Parameter) -> Document { + case parameter.label { + None -> doc.from_string(parameter.name) + Some(label) -> + doc.concat([ + doc.from_string(label), + doc.from_string(" "), + doc.from_string(parameter.name), + ]) + } +} + +pub fn function_body(body: Statement) -> FunctionBuilder(Unlabelled) { FunctionBuilder([], body) } @@ -887,17 +917,29 @@ fn definition(continue: fn() -> Expression) -> Definition { pub fn function( name: String, - function: FunctionBuilder, + function: FunctionBuilder(a), continue: fn(Expression) -> Definition, ) -> Definition { use <- definition use body <- compile_statement(function.body) + let parameters_are_valid = + list.try_fold(function.parameters, False, fn(found_labelled, parameter) { + case parameter.label { + None if found_labelled -> + Error(UnlabelledParameterAfterLabelledParameter) + None -> Ok(False) + Some(_) -> Ok(True) + } + }) + + use _ <- try(pure(parameters_are_valid)) + let parameter_list = [ doc.break("(", "("), function.parameters - |> list.map(fn(parameter) { doc.from_string(parameter.name) }) + |> list.map(parameter_to_doc) |> doc.join(doc.break(", ", ",")), ] |> doc.concat diff --git a/test/trick_test.gleam b/test/trick_test.gleam index 9018640..3dedce3 100644 --- a/test/trick_test.gleam +++ b/test/trick_test.gleam @@ -720,3 +720,44 @@ pub fn multiple_functions_type_check_correctly_test() { assert error == trick.IncorrectNumberOfArguments(expected: 2, got: 3) } + +pub fn labelled_parameter_test() { + { + use add <- trick.function("add", { + use a <- trick.labelled_parameter("a", "a", type_int) + use b <- trick.labelled_parameter("b", "b", type_int) + trick.add(a, b) + |> trick.expression + |> trick.function_body + }) + + trick.call(add, [trick.int(1), trick.int(2)]) + |> trick.expression + |> trick.function_body + |> trick.function("main", _, fn(_main) { trick.empty() }) + } + |> trick.to_string + |> unwrap + |> birdie.snap("labelled_parameter") +} + +pub fn unlabelled_after_labelled_test() { + let assert Error(error) = + trick.function( + "add", + { + use a <- trick.labelled_parameter("a", "a", type_int) + use b <- trick.labelled_parameter("b", "b", type_int) + use c <- trick.parameter("c", type_int) + a + |> trick.add(b) + |> trick.add(c) + |> trick.expression + |> trick.function_body + }, + fn(_add) { trick.empty() }, + ) + |> trick.to_string + + assert error == trick.UnlabelledParameterAfterLabelledParameter +}