Skip to content

Commit 4a61a9c

Browse files
committed
✨ Pattern matching in function/lambda parameters
1 parent eb428e9 commit 4a61a9c

File tree

7 files changed

+68
-60
lines changed

7 files changed

+68
-60
lines changed

grammar.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ declaration
1515
| external -> TopLevelExternal
1616
;
1717
18-
function = "def" ["pub"] IDENT "\\" [comma_sep<IDENT>] "=" expression ;
18+
function = "def" ["pub"] IDENT "\\" [comma_sep<pattern>] "=" expression ;
1919
2020
constant = "const" ["pub"] IDENT "=" expression ;
2121
@@ -60,9 +60,9 @@ record_field = IDENT [":" expression] ;
6060
6161
external = "external" STRING ;
6262
63-
lambda = "\\" [comma_sep<IDENT>] "->" expression ;
63+
lambda = "\\" [comma_sep<pattern>] "->" expression ;
6464
65-
backpass = "\\" [comma_sep<IDENT>] "<-" expression expression ;
65+
backpass = "\\" [comma_sep<pattern>] "<-" expression expression ;
6666
6767
let = "let" Ident "=" expression "in" expression ;
6868

manifest.toml

+4-14
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,17 @@
22
# You typically do not need to edit this file
33

44
packages = [
5-
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
6-
{ name = "birdie", version = "1.1.2", build_tools = ["gleam"], requirements = ["argv", "filepath", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "justin", "rank", "simplifile", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "F9666AEB5F6EDFAE6ADF9DFBF10EF96A4EDBDDB84B854C29B9A3F615A6436311" },
75
{ name = "chomp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../chomp-nibble" },
86
{ name = "dedent", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "dedent", source = "hex", outer_checksum = "591C78F019CFE8B4F138BDCA5DB57EB429D95F90CD12B51262A3FC6455EC1AEF" },
9-
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
10-
{ name = "glam", version = "2.0.0", build_tools = ["gleam"], requirements = ["birdie", "gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "1C10BE5EA72659E409DC2325BA5E94E0CC92C6C50B2A1DBADE6D07E8C9484D51" },
11-
{ name = "glance", version = "0.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "4866132929EAA47649FBBDBD7C4FDBEB7E30938AA34561E4486640D8ABDE3C88" },
7+
{ name = "glam", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "66EC3BCD632E51EED029678F8DF419659C1E57B1A93D874C5131FE220DFAD2B2" },
128
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" },
139
{ name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" },
14-
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
15-
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
10+
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
1611
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
1712
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
18-
{ name = "glexer", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "4484942A465482A0A100936E1E5F12314DB4B5AC0D87575A7B9E9062090B96BE" },
1913
{ name = "hug", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib"], otp_app = "hug", source = "hex", outer_checksum = "D7D8C4F5A3093FB0B6A0228288D94E95966AADC5500133F9E983BA4634E92CC8" },
20-
{ name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" },
21-
{ name = "pprint", version = "1.0.1", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "86E63F89D5EECAE9D46640EBE59100302EF94C3A551D94787EEDD03A3A74EB8C" },
22-
{ name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" },
23-
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
24-
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
25-
{ name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" },
14+
{ name = "pprint", version = "1.0.2", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "3EB2A7A8F72322F73EEF342374D0354AE39E7F9C64678B960BE8B2DC1B564AE1" },
15+
{ name = "thoas", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "540C8CB7D9257F2AD0A14145DC23560F91ACDCA995F0CCBA779EB33AF5D859D1" },
2616
]
2717

2818
[requirements]

src/spark.gleam

+7-7
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,24 @@ pub fn main() {
4848
@IO {
4949
perform: \\ -> external \"
5050
console.log(text);
51-
return $.nil();
51+
return $.nil;
5252
\"
5353
}
5454
55-
def then\\action, f =
55+
def then\\@IO { perform }, f =
5656
@IO {
57-
perform: \\ -> f(action.perform()).perform()
57+
perform: \\ -> f(perform()).perform()
5858
}
5959
60-
def perform\\action =
61-
case action
62-
| @IO { perform } = perform()
63-
6460
def test =
6561
\\_ <- println(\"Hello!\") |> then
6662
println(\"Goodbye, now.\")
6763
64+
# Atoms don't need a name! Unnamed atoms are basically tuples.
6865
const me = @(\"Joe\", 30)
66+
67+
def pub first\\@(a, _) = a
68+
def pub second\\@(_, b) = b
6969
"
7070

7171
let result = {

src/spark/ast.gleam

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub type Import {
2929
pub type Declaration {
3030
Function(
3131
name: String,
32-
parameters: List(String),
32+
parameters: List(Pattern),
3333
body: Expression,
3434
publicity: Publicity,
3535
)
@@ -60,7 +60,7 @@ pub type Expression {
6060
List(values: List(Expression))
6161
Record(fields: List(#(String, Expression)), update: Option(Expression))
6262
RecordAccess(record: Expression, field: String)
63-
Lambda(parameters: List(String), body: Expression)
63+
Lambda(parameters: List(Pattern), body: Expression)
6464
Call(function: Expression, arguments: List(Expression))
6565
Let(name: String, value: Expression, body: Expression)
6666
Binop(op: Binop, left: Expression, right: Expression)

src/spark/compile.gleam

+42-28
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,13 @@ fn compile_declaration(declaration: ast.Declaration) -> Document {
8585

8686
fn compile_function(
8787
name: String,
88-
parameters: List(String),
88+
parameters: List(ast.Pattern),
8989
body: ast.Expression,
9090
publicity: ast.Publicity,
9191
) -> Document {
92+
let #(checks, bindings) =
93+
pattern.traverse_list(parameters, doc.from_string("arguments"))
94+
9295
let publicity = case publicity {
9396
ast.Public -> doc.from_string("export ")
9497
ast.Private -> doc.empty
@@ -97,37 +100,43 @@ fn compile_function(
97100
let body =
98101
[
99102
doc.line,
100-
gen_arguments_check(list.length(parameters)),
101-
doc.lines(2),
103+
gen_arguments_check(name, list.length(parameters), checks),
104+
doc.line,
105+
gen_bindings(bindings),
106+
doc.line,
102107
gen_return(compile_expression(body)),
103108
]
104109
|> doc.nest_docs(by: 2)
105110

106111
doc.concat([
107112
publicity,
108-
doc.from_string("function " <> util.legalize(name)),
109-
gen_parameters_list(parameters),
113+
doc.from_string("function " <> util.legalize(name) <> "()"),
110114
doc.from_string(" {"),
111115
body,
112116
doc.line,
113117
doc.from_string("}"),
114118
])
115119
}
116120

117-
fn gen_parameters_list(parameters: List(String)) -> Document {
118-
parameters
119-
|> list.map(fn(param) {
120-
param
121-
|> util.legalize
122-
|> doc.from_string
123-
})
124-
|> list("(", ")")
125-
}
126-
127-
fn gen_arguments_check(num_params: Int) -> Document {
128-
doc.from_string(
129-
"$.checkArgs(arguments, " <> int.to_string(num_params) <> ");",
130-
)
121+
fn gen_arguments_check(
122+
name: String,
123+
num_params: Int,
124+
checks: List(pattern.Check),
125+
) -> Document {
126+
[
127+
doc.from_string("'" <> name <> "'"),
128+
doc.from_string("arguments"),
129+
doc.from_string(int.to_string(num_params)),
130+
..case checks {
131+
[] -> []
132+
_ -> [
133+
[doc.from_string("() =>"), doc.space, pattern.compile_checks(checks)]
134+
|> doc.nest_docs(by: 2)
135+
|> doc.group,
136+
]
137+
}
138+
]
139+
|> list("$.checkArgs(", ");")
131140
}
132141

133142
fn compile_constant(
@@ -236,25 +245,30 @@ fn compile_record_access(record: ast.Expression, field: String) -> Document {
236245
|> list("$.Record.access(", ")")
237246
}
238247

239-
fn compile_lambda(parameters: List(String), body: ast.Expression) -> Document {
248+
fn compile_lambda(
249+
parameters: List(ast.Pattern),
250+
body: ast.Expression,
251+
) -> Document {
252+
let #(checks, bindings) =
253+
pattern.traverse_list(parameters, doc.from_string("arguments"))
254+
240255
let body =
241256
[
242-
doc.space,
243-
gen_arguments_check(list.length(parameters)),
244-
doc.space,
257+
doc.line,
258+
gen_arguments_check("ANON", list.length(parameters), checks),
259+
doc.line,
260+
gen_bindings(bindings),
245261
gen_return(compile_expression(body)),
246262
]
247263
|> doc.nest_docs(by: 2)
248264

249265
doc.concat([
250-
doc.from_string("function"),
251-
gen_parameters_list(parameters),
252-
doc.from_string(" {"),
266+
doc.from_string("function() {"),
253267
body,
254-
doc.space,
268+
doc.line,
255269
doc.from_string("}"),
256270
])
257-
|> doc.group
271+
|> doc.force_break
258272
}
259273

260274
fn compile_call(

src/spark/parse.gleam

+4-4
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ fn function() -> Parser(ast.Declaration) {
7070
ctx.InFunction(name),
7171
{
7272
use _ <- do(chomp.token(token.Backslash))
73-
sequence1(ident(), separator(token.Comma))
73+
sequence1(pattern(), separator(token.Comma))
7474
}
7575
|> chomp.or([]),
7676
)
@@ -287,19 +287,19 @@ fn lambda_like() -> Parser(ast.Expression) {
287287
// of parameters, we have this intermediate parser so we don't have to use
288288
// backtracking.
289289
use _ <- do(chomp.token(token.Backslash))
290-
use parameters <- do(chomp.sequence(ident(), separator(token.Comma)))
290+
use parameters <- do(chomp.sequence(pattern(), separator(token.Comma)))
291291

292292
chomp.one_of([do_lambda(parameters), do_backpass(parameters)])
293293
|> chomp.or_error("I expected a lambda or backpass (-> or <-)")
294294
}
295295

296-
fn do_lambda(parameters: List(String)) -> Parser(ast.Expression) {
296+
fn do_lambda(parameters: List(ast.Pattern)) -> Parser(ast.Expression) {
297297
use _ <- do(chomp.token(token.ArrowRight))
298298
use body <- do_in(ctx.InLambda, expression())
299299
return(ast.Lambda(parameters, body))
300300
}
301301

302-
fn do_backpass(parameters: List(String)) -> Parser(ast.Expression) {
302+
fn do_backpass(parameters: List(ast.Pattern)) -> Parser(ast.Expression) {
303303
use _ <- do(chomp.token(token.ArrowLeft))
304304
use pass_to <- do_in(ctx.InBackpass, expression())
305305
use body <- do(

src/templates/spark.prelude.mjs

+6-2
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,14 @@ export function eq(a, b) {
6565
return false;
6666
}
6767

68-
export function checkArgs(args, expected) {
68+
export function checkArgs(name, args, expected, matchesPattern) {
6969
if (args.length !== expected) {
7070
throw new Error(
71-
'Expected ' + expected + ' argument(s), got ' + arguments.length
71+
`\`${name}\` expects ${expected} argument(s), got ${args.length}`
7272
);
7373
}
74+
75+
if (matchesPattern !== undefined && !matchesPattern()) {
76+
throw new Error(`Argument(s) given to \`${name}\` didn't match pattern`);
77+
}
7478
}

0 commit comments

Comments
 (0)