diff --git a/grammar.md b/grammar.md index f4d6cd9..6f2e39d 100644 --- a/grammar.md +++ b/grammar.md @@ -15,7 +15,7 @@ declaration | external -> TopLevelExternal ; -function = "def" ["pub"] IDENT "\\" [comma_sep] "=" expression ; +function = "def" ["pub"] IDENT "\\" [comma_sep] "=" expression ; constant = "const" ["pub"] IDENT "=" expression ; @@ -60,9 +60,9 @@ record_field = IDENT [":" expression] ; external = "external" STRING ; -lambda = "\\" [comma_sep] "->" expression ; +lambda = "\\" [comma_sep] "->" expression ; -backpass = "\\" [comma_sep] "<-" expression expression ; +backpass = "\\" [comma_sep] "<-" expression expression ; let = "let" Ident "=" expression "in" expression ; diff --git a/manifest.toml b/manifest.toml index f552642..bc8ed3b 100644 --- a/manifest.toml +++ b/manifest.toml @@ -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] diff --git a/src/spark.gleam b/src/spark.gleam index d6c9c0e..b917476 100644 --- a/src/spark.gleam +++ b/src/spark.gleam @@ -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 = { diff --git a/src/spark/ast.gleam b/src/spark/ast.gleam index 186e91c..21dc697 100644 --- a/src/spark/ast.gleam +++ b/src/spark/ast.gleam @@ -29,7 +29,7 @@ pub type Import { pub type Declaration { Function( name: String, - parameters: List(String), + parameters: List(Pattern), body: Expression, publicity: Publicity, ) @@ -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) diff --git a/src/spark/compile.gleam b/src/spark/compile.gleam index 4238526..ada5a46 100644 --- a/src/spark/compile.gleam +++ b/src/spark/compile.gleam @@ -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 @@ -97,16 +100,17 @@ 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, @@ -114,20 +118,25 @@ fn compile_function( ]) } -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( @@ -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( diff --git a/src/spark/parse.gleam b/src/spark/parse.gleam index 9edb209..785cb1e 100644 --- a/src/spark/parse.gleam +++ b/src/spark/parse.gleam @@ -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([]), ) @@ -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( diff --git a/src/templates/spark.prelude.mjs b/src/templates/spark.prelude.mjs index 5d7a07b..efb2c4a 100644 --- a/src/templates/spark.prelude.mjs +++ b/src/templates/spark.prelude.mjs @@ -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`); + } }