From 10f149f359c3bb9c8fbd57e2c19ac520a8da4c69 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Tue, 1 Dec 2015 08:05:46 -0800 Subject: [PATCH] Add "def" syntax sugar for named function definitions. This is technically redundant but it makes functional/procedural code look a *lot* more natural. --- example/animals.wren | 36 +++++++------ src/vm/wren_compiler.c | 33 ++++++++++++ test/benchmark/delta_blue.wren | 6 +-- test/core/function/call_extra_arguments.wren | 8 +-- .../core/function/call_missing_arguments.wren | 2 +- test/core/function/equality.wren | 2 +- test/core/list/reduce.wren | 15 ++---- test/language/def/empty_body.wren | 2 + test/language/def/newline_body.wren | 4 ++ .../def/newline_in_expression_block.wren | 4 ++ .../language/def/no_newline_before_close.wren | 2 + test/language/def/parameters.wren | 50 +++++++++++++++++++ test/language/def/syntax.wren | 29 +++++++++++ .../for/only_evaluate_sequence_once.wren | 2 +- test/language/for/return_closure.wren | 2 +- test/language/for/return_inside.wren | 2 +- test/language/implicit_call/local.wren | 12 ++--- .../implicit_call/module_variable.wren | 13 +++-- test/language/module_variable/undefined.wren | 2 +- .../use_in_function_before_definition.wren | 2 +- test/language/precedence.wren | 2 +- test/language/return/in_function.wren | 2 +- .../return/return_null_if_newline.wren | 2 +- test/language/while/return_closure.wren | 2 +- test/language/while/return_inside.wren | 2 +- test/limit/many_constants.wren | 2 +- test/limit/too_many_constants.wren | 2 +- 27 files changed, 178 insertions(+), 64 deletions(-) create mode 100644 test/language/def/empty_body.wren create mode 100644 test/language/def/newline_body.wren create mode 100644 test/language/def/newline_in_expression_block.wren create mode 100644 test/language/def/no_newline_before_close.wren create mode 100644 test/language/def/parameters.wren create mode 100644 test/language/def/syntax.wren diff --git a/example/animals.wren b/example/animals.wren index 9dea2065..3147cdf8 100644 --- a/example/animals.wren +++ b/example/animals.wren @@ -9,29 +9,27 @@ import "io" for Stdin // animals. Internal nodes are yes/no questions that choose which branch to // explore. -class Node { - // Reads a "yes" or "no" (or something approximating) those and returns true - // if yes was entered. - promptYesNo(prompt) { - while (true) { - var line = promptString(prompt) +// Reads a "yes" or "no" (or something approximating) those and returns true +// if yes was entered. +def promptYesNo(prompt) { + while (true) { + var line = promptString(prompt) - if (line.startsWith("y") || line.startsWith("Y")) return true - if (line.startsWith("n") || line.startsWith("N")) return false + if (line.startsWith("y") || line.startsWith("Y")) return true + if (line.startsWith("n") || line.startsWith("N")) return false - // Quit. - if (line.startsWith("q") || line.startsWith("Q")) Fiber.yield() - } - } - - // Writes a prompt and reads a string of input. - promptString(prompt) { - System.write("%(prompt) ") - return Stdin.readLine() + // Quit. + if (line.startsWith("q") || line.startsWith("Q")) Fiber.yield() } } -class Animal is Node { +// Writes a prompt and reads a string of input. +def promptString(prompt) { + System.write("%(prompt) ") + return Stdin.readLine() +} + +class Animal { construct new(name) { _name = name } @@ -56,7 +54,7 @@ class Animal is Node { } } -class Question is Node { +class Question { construct new(question, ifYes, ifNo) { _question = question _ifYes = ifYes diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 0d069967..bd489a7d 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -81,6 +81,7 @@ typedef enum TOKEN_BREAK, TOKEN_CLASS, TOKEN_CONSTRUCT, + TOKEN_DEF, TOKEN_ELSE, TOKEN_FALSE, TOKEN_FOR, @@ -490,6 +491,7 @@ static Keyword keywords[] = {"break", 5, TOKEN_BREAK}, {"class", 5, TOKEN_CLASS}, {"construct", 9, TOKEN_CONSTRUCT}, + {"def", 3, TOKEN_DEF}, {"else", 4, TOKEN_ELSE}, {"false", 5, TOKEN_FALSE}, {"for", 3, TOKEN_FOR}, @@ -2575,6 +2577,7 @@ GrammarRule rules[] = /* TOKEN_BREAK */ UNUSED, /* TOKEN_CLASS */ UNUSED, /* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL }, + /* TOKEN_DEF */ UNUSED, /* TOKEN_ELSE */ UNUSED, /* TOKEN_FALSE */ PREFIX(boolean), /* TOKEN_FOR */ UNUSED, @@ -3277,6 +3280,32 @@ static void import(Compiler* compiler) } while (match(compiler, TOKEN_COMMA)); } +// Compiles a "def" function definition statement. +static void functionDefinition(Compiler* compiler) +{ + // Create a variable to store the function in. + int slot = declareNamedVariable(compiler); + Token name = compiler->parser->previous; + + Compiler fnCompiler; + initCompiler(&fnCompiler, compiler->parser, compiler, true); + + // Make a dummy signature to track the arity. + Signature signature = { "", 0, SIG_METHOD, 0 }; + + parameterList(&fnCompiler, &signature); + + fnCompiler.numParams = signature.arity; + + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin function body."); + finishBody(&fnCompiler, false); + + // Use the function's declared name. + endCompiler(&fnCompiler, name.start, name.length); + + defineVariable(compiler, slot); +} + // Compiles a "var" variable definition statement. static void variableDefinition(Compiler* compiler) { @@ -3310,6 +3339,10 @@ void definition(Compiler* compiler) { classDefinition(compiler, false); } + else if (match(compiler, TOKEN_DEF)) + { + functionDefinition(compiler); + } else if (match(compiler, TOKEN_FOREIGN)) { consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); diff --git a/test/benchmark/delta_blue.wren b/test/benchmark/delta_blue.wren index b64083d0..17af321d 100644 --- a/test/benchmark/delta_blue.wren +++ b/test/benchmark/delta_blue.wren @@ -602,7 +602,7 @@ var total = 0 // constraint so it cannot be accomodated. The cost in this case is, // of course, very low. Typical situations lie somewhere between these // two extremes. -var chainTest = Fn.new {|n| +def chainTest(n) { ThePlanner = Planner.new() var prev = null var first = null @@ -627,7 +627,7 @@ var chainTest = Fn.new {|n| } } -var change = Fn.new {|v, newValue| +def change(v, newValue) { var edit = EditConstraint.new(v, PREFERRED) var plan = ThePlanner.extractPlanFromConstraints([edit]) for (i in 0...10) { @@ -642,7 +642,7 @@ var change = Fn.new {|v, newValue| // other by a simple linear transformation (scale and offset). The // time is measured to change a variable on either side of the // mapping and to change the scale and offset factors. -var projectionTest = Fn.new {|n| +def projectionTest(n) { ThePlanner = Planner.new() var scale = Variable.new("scale", 10) var offset = Variable.new("offset", 1000) diff --git a/test/core/function/call_extra_arguments.wren b/test/core/function/call_extra_arguments.wren index 0b68e893..cb074e32 100644 --- a/test/core/function/call_extra_arguments.wren +++ b/test/core/function/call_extra_arguments.wren @@ -1,7 +1,7 @@ -var f0 = Fn.new { System.print("zero") } -var f1 = Fn.new {|a| System.print("one %(a)") } -var f2 = Fn.new {|a, b| System.print("two %(a) %(b)") } -var f3 = Fn.new {|a, b, c| System.print("three %(a) %(b) %(c)") } +def f0() { System.print("zero") } +def f1(a) { System.print("one %(a)") } +def f2(a, b) { System.print("two %(a) %(b)") } +def f3(a, b, c) { System.print("three %(a) %(b) %(c)") } f0("a") // expect: zero f0("a", "b") // expect: zero diff --git a/test/core/function/call_missing_arguments.wren b/test/core/function/call_missing_arguments.wren index e02f56f3..98286533 100644 --- a/test/core/function/call_missing_arguments.wren +++ b/test/core/function/call_missing_arguments.wren @@ -1,2 +1,2 @@ -var f2 = Fn.new {|a, b| System.print(a + b) } +def f2(a, b) { System.print(a + b) } f2("a") // expect runtime error: Function expects more arguments. diff --git a/test/core/function/equality.wren b/test/core/function/equality.wren index 2164915b..c7d80d7f 100644 --- a/test/core/function/equality.wren +++ b/test/core/function/equality.wren @@ -11,6 +11,6 @@ System.print(Fn.new { 123 } != false) // expect: true System.print(Fn.new { 123 } != "fn 123") // expect: true // Equal by identity. -var f = Fn.new { 123 } +def f() { 123 } System.print(f == f) // expect: true System.print(f != f) // expect: false diff --git a/test/core/list/reduce.wren b/test/core/list/reduce.wren index f2a92a8f..e4d93784 100644 --- a/test/core/list/reduce.wren +++ b/test/core/list/reduce.wren @@ -1,14 +1,7 @@ var a = [1, 4, 2, 1, 5] -var b = ["W", "o", "r", "l", "d"] -var max = Fn.new {|a, b| a > b ? a : b } -var sum = Fn.new {|a, b| a + b } -System.print(a.reduce(max)) // expect: 5 -System.print(a.reduce(10, max)) // expect: 10 +System.print(a.reduce {|a, b| a > b ? a : b }) // expect: 5 +System.print(a.reduce(10) {|a, b| a > b ? a : b }) // expect: 10 -System.print(a.reduce(sum)) // expect: 13 -System.print(a.reduce(-1, sum)) // expect: 12 - -// sum also concatenates strings -System.print(b.reduce("Hello ", sum)) // expect: Hello World -System.print(b.reduce(sum)) // expect: World +System.print(a.reduce {|a, b| a + b }) // expect: 13 +System.print(a.reduce(-1) {|a, b| a + b }) // expect: 12 diff --git a/test/language/def/empty_body.wren b/test/language/def/empty_body.wren new file mode 100644 index 00000000..e837f8d6 --- /dev/null +++ b/test/language/def/empty_body.wren @@ -0,0 +1,2 @@ +def f() {} +System.print(f()) // expect: null diff --git a/test/language/def/newline_body.wren b/test/language/def/newline_body.wren new file mode 100644 index 00000000..2cf9c77c --- /dev/null +++ b/test/language/def/newline_body.wren @@ -0,0 +1,4 @@ +def f() { + // Hi. +} +System.print(f()) // expect: null diff --git a/test/language/def/newline_in_expression_block.wren b/test/language/def/newline_in_expression_block.wren new file mode 100644 index 00000000..4613ec65 --- /dev/null +++ b/test/language/def/newline_in_expression_block.wren @@ -0,0 +1,4 @@ +def f() { System.print("ok") // expect error +} + +f() diff --git a/test/language/def/no_newline_before_close.wren b/test/language/def/no_newline_before_close.wren new file mode 100644 index 00000000..932d1450 --- /dev/null +++ b/test/language/def/no_newline_before_close.wren @@ -0,0 +1,2 @@ +def f() { + System.print("ok") } // expect error \ No newline at end of file diff --git a/test/language/def/parameters.wren b/test/language/def/parameters.wren new file mode 100644 index 00000000..e6b9e1a8 --- /dev/null +++ b/test/language/def/parameters.wren @@ -0,0 +1,50 @@ +def f0() { 0 } +System.print(f0()) // expect: 0 + +def f1(a) { a } +System.print(f1(1)) // expect: 1 + +def f2(a, b) { a + b } +System.print(f2(1, 2)) // expect: 3 + +def f3(a, b, c) { a + b + c } +System.print(f3(1, 2, 3)) // expect: 6 + +def f4(a, b, c, d) { a + b + c + d } +System.print(f4(1, 2, 3, 4)) // expect: 10 + +def f5(a, b, c, d, e) { a + b + c + d + e } +System.print(f5(1, 2, 3, 4, 5)) // expect: 15 + +def f6(a, b, c, d, e, f) { a + b + c + d + e + f } +System.print(f6(1, 2, 3, 4, 5, 6)) // expect: 21 + +def f7(a, b, c, d, e, f, g) { a + b + c + d + e + f + g } +System.print(f7(1, 2, 3, 4, 5, 6, 7)) // expect: 28 + +def f8(a, b, c, d, e, f, g, h) { a + b + c + d + e + f + g + h } +System.print(f8(1, 2, 3, 4, 5, 6, 7, 8)) // expect: 36 + +def f9(a, b, c, d, e, f, g, h, i) { a + b + c + d + e + f + g + h + i } +System.print(f9(1, 2, 3, 4, 5, 6, 7, 8, 9)) // expect: 45 + +def f10(a, b, c, d, e, f, g, h, i, j) { a + b + c + d + e + f + g + h + i + j } +System.print(f10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) // expect: 55 + +def f11(a, b, c, d, e, f, g, h, i, j, k) { a + b + c + d + e + f + g + h + i + j + k } +System.print(f11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) // expect: 66 + +def f12(a, b, c, d, e, f, g, h, i, j, k, l) { a + b + c + d + e + f + g + h + i + j + k + l } +System.print(f12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)) // expect: 78 + +def f13(a, b, c, d, e, f, g, h, i, j, k, l, m) { a + b + c + d + e + f + g + h + i + j + k + l + m } +System.print(f13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)) // expect: 91 + +def f14(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { a + b + c + d + e + f + g + h + i + j + k + l + m + n } +System.print(f14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)) // expect: 105 + +def f15(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o } +System.print(f15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)) // expect: 120 + +def f16(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p } +System.print(f16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)) // expect: 136 diff --git a/test/language/def/syntax.wren b/test/language/def/syntax.wren new file mode 100644 index 00000000..5c26907d --- /dev/null +++ b/test/language/def/syntax.wren @@ -0,0 +1,29 @@ +// Single expression body. +def f() { System.print("ok") } // expect: ok +f() + +// Curly body. +def g() { + System.print("ok") // expect: ok +} +g() + +// Multiple statements. +def h() { + System.print("1") // expect: 1 + System.print("2") // expect: 2 +} +h() + +// Extra newlines. +def i() { + + + System.print("1") // expect: 1 + + + System.print("2") // expect: 2 + + +} +i() diff --git a/test/language/for/only_evaluate_sequence_once.wren b/test/language/for/only_evaluate_sequence_once.wren index 3e056fba..79328a9f 100644 --- a/test/language/for/only_evaluate_sequence_once.wren +++ b/test/language/for/only_evaluate_sequence_once.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { System.print("evaluate sequence") return [1, 2, 3] } diff --git a/test/language/for/return_closure.wren b/test/language/for/return_closure.wren index d083abb2..fb1b7350 100644 --- a/test/language/for/return_closure.wren +++ b/test/language/for/return_closure.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { for (i in [1, 2, 3]) { return Fn.new { System.print(i) } } diff --git a/test/language/for/return_inside.wren b/test/language/for/return_inside.wren index f1eff999..f32c77fd 100644 --- a/test/language/for/return_inside.wren +++ b/test/language/for/return_inside.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { for (i in [1, 2, 3]) { return i } diff --git a/test/language/implicit_call/local.wren b/test/language/implicit_call/local.wren index f3275d84..6c3f6e3c 100644 --- a/test/language/implicit_call/local.wren +++ b/test/language/implicit_call/local.wren @@ -1,11 +1,11 @@ Fn.new { - var fn = Fn.new {|arg| System.print(arg) } + def f1(arg) { System.print(arg) } - fn("string") // expect: string + f1("string") // expect: string - fn = Fn.new {|block| System.print(block()) } - fn { "block" } // expect: block + def f2(block) { System.print(block()) } + f2 { "block" } // expect: block - fn = Fn.new {|a, b, c| System.print("%(a) %(b) %(c())") } - fn(1, 2) { 3 } // expect: 1 2 3 + def f3(a, b, c) { System.print("%(a) %(b) %(c())") } + f3(1, 2) { 3 } // expect: 1 2 3 }() diff --git a/test/language/implicit_call/module_variable.wren b/test/language/implicit_call/module_variable.wren index 5cb5505e..57e4472e 100644 --- a/test/language/implicit_call/module_variable.wren +++ b/test/language/implicit_call/module_variable.wren @@ -1,9 +1,8 @@ -var fn = Fn.new {|arg| System.print(arg) } +def f1(arg) { System.print(arg) } +f1("string") // expect: string -fn("string") // expect: string +def f2(block) { System.print(block()) } +f2 { "block" } // expect: block -fn = Fn.new {|block| System.print(block()) } -fn { "block" } // expect: block - -fn = Fn.new {|a, b, c| System.print("%(a) %(b) %(c())") } -fn(1, 2) { 3 } // expect: 1 2 3 +def f3(a, b, c) { System.print("%(a) %(b) %(c())") } +f3(1, 2) { 3 } // expect: 1 2 3 diff --git a/test/language/module_variable/undefined.wren b/test/language/module_variable/undefined.wren index b622d0d8..79821d33 100644 --- a/test/language/module_variable/undefined.wren +++ b/test/language/module_variable/undefined.wren @@ -1,4 +1,4 @@ -var fn = Fn.new { +def fn() { System.print(a) System.print(b) } diff --git a/test/language/module_variable/use_in_function_before_definition.wren b/test/language/module_variable/use_in_function_before_definition.wren index 6ff8801d..983c6af1 100644 --- a/test/language/module_variable/use_in_function_before_definition.wren +++ b/test/language/module_variable/use_in_function_before_definition.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { System.print(variable) } diff --git a/test/language/precedence.wren b/test/language/precedence.wren index d5d3ee20..0101b317 100644 --- a/test/language/precedence.wren +++ b/test/language/precedence.wren @@ -1,5 +1,5 @@ // () has higher precedence than unary. -var fn = Fn.new { 2 } +def fn() { 2 } System.print(-fn()) // expect: -2 // * has higher precedence than +. diff --git a/test/language/return/in_function.wren b/test/language/return/in_function.wren index ab5073f3..ebaf5af7 100644 --- a/test/language/return/in_function.wren +++ b/test/language/return/in_function.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { return "ok" System.print("bad") } diff --git a/test/language/return/return_null_if_newline.wren b/test/language/return/return_null_if_newline.wren index 53e7036e..b1315790 100644 --- a/test/language/return/return_null_if_newline.wren +++ b/test/language/return/return_null_if_newline.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { return System.print("bad") } diff --git a/test/language/while/return_closure.wren b/test/language/while/return_closure.wren index e3ecfbf8..fded976f 100644 --- a/test/language/while/return_closure.wren +++ b/test/language/while/return_closure.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { while (true) { var i = "i" return Fn.new { System.print(i) } diff --git a/test/language/while/return_inside.wren b/test/language/while/return_inside.wren index 0a29375a..090ee386 100644 --- a/test/language/while/return_inside.wren +++ b/test/language/while/return_inside.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { while (true) { var i = "i" return i diff --git a/test/limit/many_constants.wren b/test/limit/many_constants.wren index 77edec79..3a5590e0 100644 --- a/test/limit/many_constants.wren +++ b/test/limit/many_constants.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { 1 2 3 diff --git a/test/limit/too_many_constants.wren b/test/limit/too_many_constants.wren index 48ea226a..46a8b66d 100644 --- a/test/limit/too_many_constants.wren +++ b/test/limit/too_many_constants.wren @@ -1,4 +1,4 @@ -var f = Fn.new { +def f() { 1 2 3