Skip to content

Commit

Permalink
✨ Pattern matching in function/lambda parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
MystPi committed Apr 26, 2024
1 parent eb428e9 commit 4a61a9c
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 60 deletions.
6 changes: 3 additions & 3 deletions grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ declaration
| external -> TopLevelExternal
;
function = "def" ["pub"] IDENT "\\" [comma_sep<IDENT>] "=" expression ;
function = "def" ["pub"] IDENT "\\" [comma_sep<pattern>] "=" expression ;
constant = "const" ["pub"] IDENT "=" expression ;
Expand Down Expand Up @@ -60,9 +60,9 @@ record_field = IDENT [":" expression] ;
external = "external" STRING ;
lambda = "\\" [comma_sep<IDENT>] "->" expression ;
lambda = "\\" [comma_sep<pattern>] "->" expression ;
backpass = "\\" [comma_sep<IDENT>] "<-" expression expression ;
backpass = "\\" [comma_sep<pattern>] "<-" expression expression ;
let = "let" Ident "=" expression "in" expression ;
Expand Down
18 changes: 4 additions & 14 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,17 @@
# You typically do not need to edit this file

packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ 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" },
{ name = "chomp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../chomp-nibble" },
{ name = "dedent", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "dedent", source = "hex", outer_checksum = "591C78F019CFE8B4F138BDCA5DB57EB429D95F90CD12B51262A3FC6455EC1AEF" },
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "glam", version = "2.0.0", build_tools = ["gleam"], requirements = ["birdie", "gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "1C10BE5EA72659E409DC2325BA5E94E0CC92C6C50B2A1DBADE6D07E8C9484D51" },
{ name = "glance", version = "0.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "4866132929EAA47649FBBDBD7C4FDBEB7E30938AA34561E4486640D8ABDE3C88" },
{ name = "glam", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "66EC3BCD632E51EED029678F8DF419659C1E57B1A93D874C5131FE220DFAD2B2" },
{ 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" },
{ 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" },
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "glexer", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "4484942A465482A0A100936E1E5F12314DB4B5AC0D87575A7B9E9062090B96BE" },
{ 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" },
{ name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" },
{ name = "pprint", version = "1.0.1", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "86E63F89D5EECAE9D46640EBE59100302EF94C3A551D94787EEDD03A3A74EB8C" },
{ name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
{ name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" },
{ name = "pprint", version = "1.0.2", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "3EB2A7A8F72322F73EEF342374D0354AE39E7F9C64678B960BE8B2DC1B564AE1" },
{ name = "thoas", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "540C8CB7D9257F2AD0A14145DC23560F91ACDCA995F0CCBA779EB33AF5D859D1" },
]

[requirements]
Expand Down
14 changes: 7 additions & 7 deletions src/spark.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,24 @@ pub fn main() {
@IO {
perform: \\ -> external \"
console.log(text);
return $.nil();
return $.nil;
\"
}
def then\\action, f =
def then\\@IO { perform }, f =
@IO {
perform: \\ -> f(action.perform()).perform()
perform: \\ -> f(perform()).perform()
}
def perform\\action =
case action
| @IO { perform } = perform()
def test =
\\_ <- println(\"Hello!\") |> then
println(\"Goodbye, now.\")
# Atoms don't need a name! Unnamed atoms are basically tuples.
const me = @(\"Joe\", 30)
def pub first\\@(a, _) = a
def pub second\\@(_, b) = b
"

let result = {
Expand Down
4 changes: 2 additions & 2 deletions src/spark/ast.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub type Import {
pub type Declaration {
Function(
name: String,
parameters: List(String),
parameters: List(Pattern),
body: Expression,
publicity: Publicity,
)
Expand Down Expand Up @@ -60,7 +60,7 @@ pub type Expression {
List(values: List(Expression))
Record(fields: List(#(String, Expression)), update: Option(Expression))
RecordAccess(record: Expression, field: String)
Lambda(parameters: List(String), body: Expression)
Lambda(parameters: List(Pattern), body: Expression)
Call(function: Expression, arguments: List(Expression))
Let(name: String, value: Expression, body: Expression)
Binop(op: Binop, left: Expression, right: Expression)
Expand Down
70 changes: 42 additions & 28 deletions src/spark/compile.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,13 @@ fn compile_declaration(declaration: ast.Declaration) -> Document {

fn compile_function(
name: String,
parameters: List(String),
parameters: List(ast.Pattern),
body: ast.Expression,
publicity: ast.Publicity,
) -> Document {
let #(checks, bindings) =
pattern.traverse_list(parameters, doc.from_string("arguments"))

let publicity = case publicity {
ast.Public -> doc.from_string("export ")
ast.Private -> doc.empty
Expand All @@ -97,37 +100,43 @@ fn compile_function(
let body =
[
doc.line,
gen_arguments_check(list.length(parameters)),
doc.lines(2),
gen_arguments_check(name, list.length(parameters), checks),
doc.line,
gen_bindings(bindings),
doc.line,
gen_return(compile_expression(body)),
]
|> doc.nest_docs(by: 2)

doc.concat([
publicity,
doc.from_string("function " <> util.legalize(name)),
gen_parameters_list(parameters),
doc.from_string("function " <> util.legalize(name) <> "()"),
doc.from_string(" {"),
body,
doc.line,
doc.from_string("}"),
])
}

fn gen_parameters_list(parameters: List(String)) -> Document {
parameters
|> list.map(fn(param) {
param
|> util.legalize
|> doc.from_string
})
|> list("(", ")")
}

fn gen_arguments_check(num_params: Int) -> Document {
doc.from_string(
"$.checkArgs(arguments, " <> int.to_string(num_params) <> ");",
)
fn gen_arguments_check(
name: String,
num_params: Int,
checks: List(pattern.Check),
) -> Document {
[
doc.from_string("'" <> name <> "'"),
doc.from_string("arguments"),
doc.from_string(int.to_string(num_params)),
..case checks {
[] -> []
_ -> [
[doc.from_string("() =>"), doc.space, pattern.compile_checks(checks)]
|> doc.nest_docs(by: 2)
|> doc.group,
]
}
]
|> list("$.checkArgs(", ");")
}

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

fn compile_lambda(parameters: List(String), body: ast.Expression) -> Document {
fn compile_lambda(
parameters: List(ast.Pattern),
body: ast.Expression,
) -> Document {
let #(checks, bindings) =
pattern.traverse_list(parameters, doc.from_string("arguments"))

let body =
[
doc.space,
gen_arguments_check(list.length(parameters)),
doc.space,
doc.line,
gen_arguments_check("ANON", list.length(parameters), checks),
doc.line,
gen_bindings(bindings),
gen_return(compile_expression(body)),
]
|> doc.nest_docs(by: 2)

doc.concat([
doc.from_string("function"),
gen_parameters_list(parameters),
doc.from_string(" {"),
doc.from_string("function() {"),
body,
doc.space,
doc.line,
doc.from_string("}"),
])
|> doc.group
|> doc.force_break
}

fn compile_call(
Expand Down
8 changes: 4 additions & 4 deletions src/spark/parse.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn function() -> Parser(ast.Declaration) {
ctx.InFunction(name),
{
use _ <- do(chomp.token(token.Backslash))
sequence1(ident(), separator(token.Comma))
sequence1(pattern(), separator(token.Comma))
}
|> chomp.or([]),
)
Expand Down Expand Up @@ -287,19 +287,19 @@ fn lambda_like() -> Parser(ast.Expression) {
// of parameters, we have this intermediate parser so we don't have to use
// backtracking.
use _ <- do(chomp.token(token.Backslash))
use parameters <- do(chomp.sequence(ident(), separator(token.Comma)))
use parameters <- do(chomp.sequence(pattern(), separator(token.Comma)))

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

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

fn do_backpass(parameters: List(String)) -> Parser(ast.Expression) {
fn do_backpass(parameters: List(ast.Pattern)) -> Parser(ast.Expression) {
use _ <- do(chomp.token(token.ArrowLeft))
use pass_to <- do_in(ctx.InBackpass, expression())
use body <- do(
Expand Down
8 changes: 6 additions & 2 deletions src/templates/spark.prelude.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ export function eq(a, b) {
return false;
}

export function checkArgs(args, expected) {
export function checkArgs(name, args, expected, matchesPattern) {
if (args.length !== expected) {
throw new Error(
'Expected ' + expected + ' argument(s), got ' + arguments.length
`\`${name}\` expects ${expected} argument(s), got ${args.length}`
);
}

if (matchesPattern !== undefined && !matchesPattern()) {
throw new Error(`Argument(s) given to \`${name}\` didn't match pattern`);
}
}

0 comments on commit 4a61a9c

Please sign in to comment.