From af74ddb119e3c5167a10bf5468af3960c8db42c0 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 2 Mar 2019 21:48:30 +0100 Subject: Use 'fn' to define closure to make parsing/reading the language easier It caused issues when you have parentheses to surround math expression, for example: ((func() + 34) * 54) is easier to parse if closure has to begin with 'fn'. Also removed requirement for semicolons. Semicolons can't even be used optionally yet. --- doc/DESIGN.md | 192 ++++++++++++++++++++++++++-------------------------- include/tokenizer.h | 1 + src/parser.c | 48 +++++++------ src/tokenizer.c | 10 +++ tests/io.amal | 2 +- tests/main.amal | 6 +- 6 files changed, 138 insertions(+), 121 deletions(-) diff --git a/doc/DESIGN.md b/doc/DESIGN.md index b8693f1..e3d7bf1 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -4,62 +4,88 @@ All functions are closures. Assigning a closure to a variable is how you make re ## Hello world ``` -const main = () { - stderr.writeln("hello, world!"); +const main = fn { + stderr.writeln("hello, world!") } ``` ## Conditions ``` -const main = () { - var value = 23 + 50; +const main = fn { + var value = 23 + 50 if value < 23 - stderr.writeln("less!"); + stderr.writeln("less!") else - stderr.writeln("more!"); + stderr.writeln("more!") while value > 0 { - stderr.writeln("value: {}", value); - value -= 1; + stderr.writeln("value: {}", value) + value -= 1 } } ``` +## Closure +Parentheses after `fn` is only required when the closure has parameters or returns data +``` +const apply = fn(func: () bool) { + const result = func() +} + +const main = fn { + // Return type is automatically deduced. If function returns multiple different types at different points, + // then you get an error and are required to specify the return type + apply(fn { + return true + }) + + apply(fn() bool { + return true + }) + + // Or store in a variable and use it + const func = fn { + return true + } + apply(func) +} +``` + ## Data types ``` -const main = () !void { - var v1: i32 = 50; - var v2: u32 = 50; +const main = fn() !void { + var v1: i32 = 50 + var v2: u32 = 50 - v1 = v2; // error, v2 can't be implicitly cast to v1 because i32 can't represent the same values as u32 - v1 = @cast(i32, v2); // ok, explicitly cast u32 to i32 + v1 = v2 // error, v2 can't be implicitly cast to v1 because i32 can't represent the same values as u32 + v1 = @cast(i32, v2) // ok, explicitly cast u32 to i32 - var str1 = "hello"; - var str2 = "world"; - var str3 = try str1 + " " + str2; - var str4 = try str1 + 20; // error, can't add number to string. Preferable use str.fmt or explicitly cast to string - var str5 = try str1 + str(20); // ok, number explicitly cast to string + var str1 = "hello" + var str2 = "world" + var str3 = try str1 + " " + str2 + var str4 = try str1 + 20 // error, can't add number to string. Preferable use str.fmt or explicitly cast to string + var str5 = try str1 + str(20) // ok, number explicitly cast to string - const str6 = "hello"; - const str7 = "world"; - const str8 = str6 + " " + str7; // ok, all variable involved are const. They can be combined at compile-time + const str6 = "hello" + const str7 = "world" + const str8 = str6 + " " + str7 // ok, all variable involved are const. They can be combined at compile-time - stderr.writeln("{}, {} | {}", str1, str2, str3); // prints hello world | hello world + stderr.writeln("{}, {} | {}", str1, str2, str3) // prints hello world | hello world } ``` ## Dynamic allocation (array) ``` -const ArrayList = @import("std.array.ArrayList"); +const ArrayList = @import("std.array.ArrayList") -const main = () !void { - var list = ArrayList(i32); - try list.add(23); - try list.add(50); - var value = list.get(40); +const main = fn() !void { + var list = ArrayList(i32) + try list.add(23) + try list.add(50) + var value = list.get(40) for val in list { - stdout.writeln("value: {}", val); + stdout.writeln("value: {}", val) } } ``` @@ -72,11 +98,11 @@ struct User { level = 1 // default value is 1 and type is i32 } -const levelUp = (self: &User) { - self.level += 1; +const levelUp = fn(self: &User) { + self.level += 1 } -const main = () { +const main = fn { const user1 = User { name: "John", age: 24 @@ -88,10 +114,10 @@ const main = () { level: 100 } - levelUp(user2); + levelUp(user2) // syntax sugar for calling a function with the first argument as // the variable before the dot (same thing as levelUp(user2)) - user2.levelUp(); + user2.levelUp() } ``` @@ -104,7 +130,7 @@ struct User { level: i32 } -const createUser = (name: str, age: i32, level: i32 = 1) User { +const createUser = fn(name: str, age: i32, level: i32 = 1) User { return User { name: name, age: age, @@ -112,45 +138,21 @@ const createUser = (name: str, age: i32, level: i32 = 1) User { } } -const main = { - createUser(name: "John", level: 30, age: 30); - createUser(age: 40, name: "Titor"); -} -``` - -## Closure -``` -const apply = (func: () bool) { - const result = func(); -} - -const main = () { - // Return type is automatically deduces. If function returns multiple different types at different points, then you get an error and are required to specify the return type - apply((){ - return true; - }); - - apply(() bool { - return true; - }); - - // Or store in a variable and use it - const func = () { - return true; - } - apply(func); +const main = fn { + createUser(name: "John", level: 30, age: 30) + createUser(age: 40, name: "Titor") } ``` ## Generic programming ``` -const add = (comptime T: type, a: T, b: T) !T { - return try a + b; +const add = fn(comptime T: type, a: T, b: T) !T { + return try a + b } -const main = () { - var numberValue = add(20, 40); - var stringValue = add("hello", "world"); +const main = fn { + var numberValue = add(20, 40) + var stringValue = add("hello", "world") } ``` @@ -161,59 +163,59 @@ Rust doesn't handle this but Amalgam does it using #reallocatable(instance). Reallocatable should be ignored if the reference that taken from the reallocatable memory doesn't change location after realloc, which would be the case for pointers. ``` -const ArrayList = @import("std.array.ArrayList"); +const ArrayList = @import("std.array.ArrayList") struct User { name: str, level: i32 } -const addUserToList = (list: &ArrayList(User), user: User) { +const addUserToList = fn(list: &ArrayList(User), user: User) { // this is not actually needed for ArrayList because ArrayList uses #reallocatable internally for list.add and list.remove - @reallocatable list.add(move user); + @reallocatable list.add(move user) } -const main = () { - var users = ArrayList(User); +const main = fn { + var users = ArrayList(User) users.add(User { name: "John", level: 34 - }); + }) const user1 = User { name: "David", level: 55 } // error, addUserToList expects user1 to be moved or copied - // addUserToList(users, user1); + // addUserToList(users, user1) - // addUserToList(users, clone user1); // ok, user1 has been copied to function scope - addUserToList(users, move user1); // ok, user1 has been moved to function scope + // addUserToList(users, clone user1) // ok, user1 has been copied to function scope + addUserToList(users, move user1) // ok, user1 has been moved to function scope - next(move users); + next(move users) } -const getUserAtIndex = (list: &ArrayList(User), index: usize) User { - return list.get(index); +const getUserAtIndex = fn(list: &ArrayList(User), index: usize) User { + return list.get(index) } -const next = (users: ArrayList(User)) { - const user = getUserAtIndex(users, 0); +const next = fn(users: ArrayList(User)) { + const user = getUserAtIndex(users, 0) // Reallocatable example: addUserToList(users, User { name: "John", level: 34 - }); + }) // error, "user" can't be safely used because addUserToList on line XXX can reallocate "users" which "user" belongs to - stdout.writeln("user name: {}", user.name); + stdout.writeln("user name: {}", user.name) } ``` ## Table (inspired by lua) ``` -const main = () { +const main = fn { const values = { "name": "John", "age": 42, @@ -223,29 +225,29 @@ const main = () { ] } - printMap(values); // stdout.writeln("{}", values) can also be used directly as it supports tables + printMap(values) // stdout.writeln("{}", values) can also be used directly as it supports tables } -const printTable = (value: TableValue) { +const printTable = fn(value: TableValue) { switch @type(value) { array => { // value type is automatically cast to array here, same with other cases in the switch for index, val in value { - stdout.write("[{}] = ", index); - printTable(val); - stdout.writeln(","); + stdout.write("[{}] = ", index) + printTable(val) + stdout.writeln(",") } } map => { - stdout.writeln("{"); + stdout.writeln("{") for key, val in value { - stdout.write("'{}': ", key); - printTable(val); - stdout.writeln(","); + stdout.write("'{}': ", key) + printTable(val) + stdout.writeln(",") } - stdout.writeln("}"); + stdout.writeln("}") } - else => stdout.write(value); + else => stdout.write(value) } } ``` \ No newline at end of file diff --git a/include/tokenizer.h b/include/tokenizer.h index c76cd52..6f00704 100644 --- a/include/tokenizer.h +++ b/include/tokenizer.h @@ -16,6 +16,7 @@ typedef enum { TOK_CONST, TOK_VAR, TOK_STRING, + TOK_FN, TOK_EQUALS, TOK_OPEN_PAREN, TOK_CLOSING_PAREN, diff --git a/src/parser.c b/src/parser.c index 539ebd3..babb2d9 100644 --- a/src/parser.c +++ b/src/parser.c @@ -94,20 +94,24 @@ static CHECK_RESULT int parser_parse_lhs(Parser *self, LhsExpr **result) { } /* -CLOSURE = '(' PARAM* ')' '{' BODY_LOOP '}' +CLOSURE = 'fn' ('(' PARAM* ')')? '{' BODY_LOOP '}' */ static CHECK_RESULT int parser_parse_function_decl(Parser *self, FunctionDecl **func_decl) { - bool result; + bool match; *func_decl = NULL; - return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_PAREN, &result)); - if(!result) + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_FN, &match)); + if(!match) return PARSER_OK; - /* TODO: Parse parameters */ - return_if_error(tokenizer_accept(&self->tokenizer, TOK_CLOSING_PAREN)); - /* TODO: Parse return types */ - return_if_error(tokenizer_accept(&self->tokenizer, TOK_OPEN_BRACE)); + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_BRACE, &match)); + if(!match) { + return_if_error(tokenizer_accept(&self->tokenizer, TOK_OPEN_PAREN)); + /* TODO: Parse parameters */ + return_if_error(tokenizer_accept(&self->tokenizer, TOK_CLOSING_PAREN)); + /* TODO: Parse return types */ + return_if_error(tokenizer_accept(&self->tokenizer, TOK_OPEN_BRACE)); + } return_if_error(scoped_allocator_alloc(self->allocator, sizeof(FunctionDecl), (void**)func_decl)); return_if_error(funcdecl_init(*func_decl, self->allocator)); @@ -147,17 +151,17 @@ VARIABLE = IDENTIFIER FUNC_CALL_OR_VARIABLE = VARIABLE '(' FUNC_ARGS ')' */ static CHECK_RESULT int parser_parse_function_call_or_variable(Parser *self, Ast *expr) { - bool result; + bool match; BufferView identifier; FunctionCall *func_call; - return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IDENTIFIER, &result)); - if(!result) + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IDENTIFIER, &match)); + if(!match) return PARSER_OK; identifier = self->tokenizer.value.identifier; - return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_PAREN, &result)); - if(!result) { + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_PAREN, &match)); + if(!match) { Variable *variable; return_if_error(scoped_allocator_alloc(self->allocator, sizeof(Variable), (void**)&variable)); variable->name = identifier; @@ -177,11 +181,11 @@ static CHECK_RESULT int parser_parse_function_call_or_variable(Parser *self, Ast IMPORT = IMPORT_SYMBOL */ static CHECK_RESULT int parser_parse_import(Parser *self, Import **import) { - bool result; + bool match; *import = NULL; - return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IMPORT, &result)); - if(!result) + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IMPORT, &match)); + if(!match) return PARSER_OK; return_if_error(scoped_allocator_alloc(self->allocator, sizeof(Import), (void**)import)); @@ -190,11 +194,11 @@ static CHECK_RESULT int parser_parse_import(Parser *self, Import **import) { } static CHECK_RESULT int parser_parse_number(Parser *self, Ast *rhs_expr) { - bool result; + bool match; Number *number; - return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_NUMBER, &result)); - if(!result) + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_NUMBER, &match)); + if(!match) return PARSER_OK; return_if_error(scoped_allocator_alloc(self->allocator, sizeof(Number), (void**)&number)); @@ -208,10 +212,10 @@ static CHECK_RESULT int parser_parse_number(Parser *self, Ast *rhs_expr) { RHS = STRING | NUMBER | FUNC_CALL_OR_VARIABLE */ static CHECK_RESULT int parser_parse_rhs(Parser *self, Ast *rhs_expr) { - bool result; + bool match; - return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_STRING, &result)); - if(result) { + return_if_error(tokenizer_consume_if(&self->tokenizer, TOK_STRING, &match)); + if(match) { String *string; return_if_error(scoped_allocator_alloc(self->allocator, sizeof(String), (void**)&string)); return_if_error(string_init(string, self->tokenizer.value.string)); diff --git a/src/tokenizer.c b/src/tokenizer.c index 41d46fb..b996354 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -146,6 +146,13 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { self->value.identifier = create_buffer_view(self->code.data + identifier_start, self->index - identifier_start); switch(self->value.identifier.size) { + case 2: { + if(am_memeql(self->value.identifier.data, "fn", 2)) { + *token = TOK_FN; + return TOKENIZER_OK; + } + break; + } case 3: { if(am_memeql(self->value.identifier.data, "var", 3)) { *token = TOK_VAR; @@ -324,6 +331,9 @@ static BufferView tokenizer_expected_token_as_string(Token token) { case TOK_STRING: str = "string"; break; + case TOK_FN: + str = "fn"; + break; case TOK_EQUALS: str = "="; break; diff --git a/tests/io.amal b/tests/io.amal index c75fa2d..bb7935b 100644 --- a/tests/io.amal +++ b/tests/io.amal @@ -1,3 +1,3 @@ -const puts = () { +const puts = fn { } \ No newline at end of file diff --git a/tests/main.amal b/tests/main.amal index 68ddc47..955806f 100644 --- a/tests/main.amal +++ b/tests/main.amal @@ -1,13 +1,13 @@ const io = @import("tests/io.amal") -const main = () { - var hello = () { +const main = fn { + var hello = fn { } const value = "hello" print(value, "world", 356) } -const print = () { +const print = fn { } -- cgit v1.2.3