aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/DESIGN.md192
-rw-r--r--include/tokenizer.h1
-rw-r--r--src/parser.c48
-rw-r--r--src/tokenizer.c10
-rw-r--r--tests/io.amal2
-rw-r--r--tests/main.amal6
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 {
}