Sfoglia il codice sorgente

Various enhancements.

Improved handling of ivar and computed properties.
Improved Fibers and Closures.
Improved Math module.
Improved Symbol Table.
Various fixes and enhancements.
Marco Bambini 7 anni fa
parent
commit
c4f143d7ef

+ 11 - 4
src/compiler/debug_macros.h

@@ -25,7 +25,8 @@
 #define GRAVITY_LEXEM_DEBUG			0
 #define GRAVITY_LEXER_DEGUB			0
 #define GRAVITY_PARSER_DEBUG		0
-#define GRAVITY_SEMANTIC_DEBUG		0
+#define GRAVITY_SEMA1_DEBUG         0
+#define GRAVITY_SEMA2_DEBUG		    0
 #define GRAVITY_AST_DEBUG			0
 #define GRAVITY_LOOKUP_DEBUG		0
 #define GRAVITY_SYMTABLE_DEBUG		0
@@ -60,10 +61,16 @@
 #define DEBUG_PARSER(...)
 #endif
 
-#if GRAVITY_SEMANTIC_DEBUG
-#define DEBUG_SEMANTIC(...)			PRINT_LINE(__VA_ARGS__)
+#if GRAVITY_SEMA1_DEBUG
+#define DEBUG_SEMA1(...)            PRINT_LINE(__VA_ARGS__)
 #else
-#define DEBUG_SEMANTIC(...)
+#define DEBUG_SEMA1(...)
+#endif
+
+#if GRAVITY_SEMA2_DEBUG
+#define DEBUG_SEMA2(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_SEMA2(...)
 #endif
 
 #if GRAVITY_LOOKUP_DEBUG

+ 1 - 0
src/compiler/gravity_ast.c

@@ -243,6 +243,7 @@ gnode_t *gnode_variable_create (gtoken_s token, const char *identifier, const ch
 	node->annotation_type = annotation_type;
 	node->expr = expr;
 	node->access = access_specifier;
+    node->iscomputed = false;
 	return (gnode_t *)node;
 }
 

+ 2 - 1
src/compiler/gravity_ast.h

@@ -138,6 +138,7 @@ typedef struct {
 	gtoken_t			access;				// optional access token (duplicated value from its gnode_variable_decl_t)
 	uint16_t			index;				// local variable index (if local)
 	bool				upvalue;			// flag set if this variable is used as an upvalue
+    bool                iscomputed;         // flag set is variable must not be backed
 } gnode_var_t;
 
 typedef struct {
@@ -323,7 +324,7 @@ void    *meta_from_node (gnode_t *node);
 
 #define NODE_TOKEN_TYPE(_node)				_node->base.token.type
 #define NODE_TAG(_node)						((gnode_base_t *)_node)->base.tag
-#define NODE_ISA(_node,_tag)				(NODE_TAG(_node) == _tag)
+#define NODE_ISA(_node,_tag)				((_node) && NODE_TAG(_node) == _tag)
 #define NODE_ISA_FUNCTION(_node)			(NODE_ISA(_node, NODE_FUNCTION_DECL))
 #define NODE_ISA_CLASS(_node)				(NODE_ISA(_node, NODE_CLASS_DECL))
 

+ 11 - 3
src/compiler/gravity_codegen.c

@@ -263,9 +263,12 @@ static bool context_is_class (gvisitor_t *self) {
     return false;
 }
 
-static bool node_is_closure (gnode_t *node) {
+static bool node_is_closure (gnode_t *node, uint16_t *local_index) {
     DEBUG_CODEGEN("node_is_closure");
     
+    // init local_index to 0 (means not a local index)
+    if (local_index) *local_index = 0;
+    
     if (node && NODE_ISA(node, NODE_IDENTIFIER_EXPR)) {
         gnode_identifier_expr_t *identifier = (gnode_identifier_expr_t *)node;
         node = identifier->symbol;
@@ -273,6 +276,7 @@ static bool node_is_closure (gnode_t *node) {
     
     if (node && NODE_ISA(node, NODE_VARIABLE)) {
         gnode_var_t *var = (gnode_var_t *)node;
+        if (local_index) *local_index = var->index;
         node = var->expr;
     }
     
@@ -320,7 +324,11 @@ static uint32_t compute_self_register (gvisitor_t *self, ircode_t *code, gnode_t
 
 	// no special register found
     // check if node is a closure defined inside a class context (so self is needed)
-    if (node_is_closure(node) && context_is_class(self)) return 0;
+    uint16_t local_index = 0;
+    if (node_is_closure(node, &local_index)) {
+        if (next_is_access) return local_index;
+        if (context_is_class(self)) return 0;
+    }
     
     // default case is to return the target register
 	return target_register;
@@ -1061,7 +1069,7 @@ static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node)
 			gravity_class_t *context_class = (is_static) ? ((gravity_class_t *)context_object)->objclass : (gravity_class_t *)context_object;
 
 			// check computed property case first
-			if ((p->expr) && (p->expr->tag == NODE_COMPOUND_STAT)) {
+			if ((p->expr) && (p->iscomputed)) {
 				process_getter_setter(self, p, context_class);
 				continue;
 			}

+ 5 - 3
src/compiler/gravity_lexer.c

@@ -639,13 +639,15 @@ void gravity_lexer_token_dump (gtoken_s token) {
 
 #if GRAVITY_LEXER_DEGUB
 void gravity_lexer_debug (gravity_lexer_t *lexer) {
-	//static int lineno = 0;
+	static uint32_t offset = UINT32_MAX;
 	if (lexer->peeking) return;
-	//if (lineno > 0) printf("\n");
+    
 	gtoken_s token = lexer->token;
 	if ((token.lineno == 0) && (token.colno == 0)) return;
+    if (offset == token.position) return;
+    offset = token.position;
+    
 	printf("(%02d, %02d) %s: ", token.lineno, token.colno, token_name(token.type));
 	printf("%.*s\t(offset: %d)\n", token.bytes, token.value, token.position);
-	//++lineno;
 }
 #endif

+ 10 - 4
src/compiler/gravity_parser.c

@@ -677,10 +677,11 @@ static gnode_t *parse_number_expression (gravity_parser_t *parser, gtoken_s toke
 		else if (c == 'X') {type = decode_number_hex(token, &n, &d); goto report_node;}
 	}
 
-	// number is decimal (check if is float)
+	// number is decimal (check if it is float)
 	bool isfloat = false;
 	for (uint32_t i=0; i<token.bytes; ++i) {
 		if (value[i] == '.') {isfloat = true; break;}
+        if (value[i] == 'e') {isfloat = true; break;}
 	}
 
 	STATIC_TOKEN_CSTRING(str, MAX_NUMBER_LENGTH, len, buffer, token);
@@ -1337,6 +1338,7 @@ loop:
 
 	// check for optional assignment or getter/setter declaration (ONLY = is ALLOWED here!)
 	expr = NULL;
+    bool is_computed = false;
 	peek = gravity_lexer_peek(lexer);
 	if (token_isvariable_assignment(peek)) {
 		gravity_lexer_next(lexer); // consume ASSIGNMENT
@@ -1347,6 +1349,7 @@ loop:
 		gravity_lexer_next(lexer); // consume TOK_OP_OPEN_CURLYBRACE
 		expr = parse_getter_setter(parser, meta);
 		parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+        is_computed = true;
 	}
 
 	// sanity checks
@@ -1354,7 +1357,10 @@ loop:
 	// 2. check if identifier is unique inside variable declarations
 
 	decl = gnode_variable_create(token2, identifier, type_annotation, access_specifier, expr, LAST_DECLARATION());
-	if (decl) gnode_array_push(decls, decl);
+    if (decl) {
+        ((gnode_var_t *)decl)->iscomputed = is_computed;
+        gnode_array_push(decls, decl);
+    }
 
 	peek = gravity_lexer_peek(lexer);
 	if (peek == TOK_OP_COMMA) {
@@ -1412,7 +1418,7 @@ static gnode_t *parse_enum_declaration (gravity_parser_t *parser, gtoken_t acces
 	// check and consume TOK_OP_OPEN_CURLYBRACE
 	parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
 
-	symboltable_t	*symtable = symboltable_create(true);	// enum symbol table (symtable is OK because order is not important inside an enum)
+	symboltable_t	*symtable = symboltable_create(SYMTABLE_TAG_ENUM);	// enum symbol table (symtable is OK because order is not important inside an enum)
 	int64_t			enum_autoint = 0;						// autoincrement value (in case of INT enum)
 	uint32_t		enum_counter = 0;						// enum internal counter (first value (if any) determines enum type)
 	gliteral_t		enum_type = LITERAL_INT;				// enum type (default to int)
@@ -2307,7 +2313,7 @@ static gnode_t *parse_jump_statement (gravity_parser_t *parser) {
 	assert((type == TOK_KEY_BREAK) || (type == TOK_KEY_CONTINUE) || (type == TOK_KEY_RETURN));
 
 	gnode_t	*expr = NULL;
-	if ((type == TOK_KEY_RETURN) && (gravity_lexer_peek(lexer) != TOK_OP_SEMICOLON)) {
+	if ((type == TOK_KEY_RETURN) && (gravity_lexer_peek(lexer) != TOK_OP_SEMICOLON) && (gravity_lexer_peek(lexer) != TOK_OP_CLOSED_CURLYBRACE)) {
 		expr = parse_expression(parser);
 	}
 

+ 39 - 23
src/compiler/gravity_semacheck1.c

@@ -13,10 +13,10 @@
 
 #define REPORT_ERROR(node,...)			{report_error(self, (gnode_t *)node, __VA_ARGS__); return;}
 
-#define DECLARE_SYMTABLE				symboltable_t *symtable = (symboltable_t *)self->data
-#define SAVE_SYMTABLE					symboltable_t *saved = symtable
-#define CREATE_SYMTABLE					INC_IDENT; SAVE_SYMTABLE; self->data = (void *)symboltable_create(false);
-#define RESTORE_SYMTABLE				DEC_IDENT; node->symtable = ((symboltable_t *)self->data); self->data = (void *)saved
+#define DECLARE_SYMTABLE()				symboltable_t *symtable = (symboltable_t *)self->data
+#define SAVE_SYMTABLE()					symboltable_t *saved = symtable
+#define CREATE_SYMTABLE(tag)			INC_IDENT; SAVE_SYMTABLE(); self->data = (void *)symboltable_create(tag);
+#define RESTORE_SYMTABLE()				DEC_IDENT; node->symtable = ((symboltable_t *)self->data); self->data = (void *)saved
 
 #if GRAVITY_SYMTABLE_DEBUG
 static int ident =0;
@@ -66,82 +66,98 @@ static void report_error (gvisitor_t *self, gnode_t *node, const char *format, .
 // MARK: - Declarations -
 
 static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
-	DECLARE_SYMTABLE;
+	DECLARE_SYMTABLE();
+    DEBUG_SYMTABLE("GLOBALS");
 
 	node->symtable = symtable;	// GLOBALS
-	gnode_array_each(node->stmts, {visit(val);});
+	gnode_array_each(node->stmts, {
+        visit(val);
+    });
 }
 
 static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
-	DECLARE_SYMTABLE;
-
+	DECLARE_SYMTABLE();
 	DEBUG_SYMTABLE("function: %s", node->identifier);
 
 	// function identifier
-	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+    if (!symboltable_insert(symtable, node->identifier, (void *)node)) {
 		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+    }
 
 	// we are just interested in non-local declarations so don't further scan function node
 	// node->symtable is NULL here and it will be created in semacheck2
 }
 
 static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
-	DECLARE_SYMTABLE;
+	DECLARE_SYMTABLE();
 
+    bool is_static = (node->storage == TOK_KEY_STATIC);
 	gnode_array_each(node->decls, {
 		gnode_var_t *p = (gnode_var_t *)val;
-		DEBUG_SYMTABLE("variable: %s", p->identifier);
-		if (!symboltable_insert(symtable, p->identifier, (void *)p))
+        
+        if (!symboltable_insert(symtable, p->identifier, (void *)p)) {
 			REPORT_ERROR(p, "Identifier %s redeclared.", p->identifier);
+        }
+        
+        // in CLASS case set a relative ivar index (if ivar is not computed)
+        if (symboltable_tag(symtable) == SYMTABLE_TAG_CLASS && p->iscomputed == false) {
+            p->index = symboltable_setivar(symtable, is_static);
+            DEBUG_SYMTABLE("ivar: %s index: %d", p->identifier, p->index);
+        } else {
+            DEBUG_SYMTABLE("variable: %s", p->identifier);
+        }
 	});
 }
 
 static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
-	DECLARE_SYMTABLE;
+	DECLARE_SYMTABLE();
 
 	DEBUG_SYMTABLE("enum: %s", node->identifier);
 
 	// check enum identifier uniqueness in current symbol table
-	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+    if (!symboltable_insert(symtable, node->identifier, (void *)node)) {
 		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+	}
 }
 
 static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
-	DECLARE_SYMTABLE;
+	DECLARE_SYMTABLE();
 
 	DEBUG_SYMTABLE("class: %s", node->identifier);
 
 	// class identifier
-	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+    if (!symboltable_insert(symtable, node->identifier, (void *)node)) {
 		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+    }
 
-	CREATE_SYMTABLE;
+	CREATE_SYMTABLE(SYMTABLE_TAG_CLASS);
 	gnode_array_each(node->decls, {
 		visit(val);
 	});
-	RESTORE_SYMTABLE;
+	RESTORE_SYMTABLE();
 }
 
 static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
-	DECLARE_SYMTABLE;
+	DECLARE_SYMTABLE();
 
 	DEBUG_SYMTABLE("module: %s", node->identifier);
 
 	// module identifier
-	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+    if (!symboltable_insert(symtable, node->identifier, (void *)node)) {
 		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+    }
 
-	CREATE_SYMTABLE;
+	CREATE_SYMTABLE(SYMTABLE_TAG_MODULE);
 	gnode_array_each(node->decls, {
 		visit(val);
 	});
-	RESTORE_SYMTABLE;
+	RESTORE_SYMTABLE();
 }
 
 // MARK: -
 
 bool gravity_semacheck1 (gnode_t *node, gravity_delegate_t *delegate) {
-	symboltable_t *context = symboltable_create(false);
+	symboltable_t *context = symboltable_create(SYMTABLE_TAG_GLOBAL);
 
 	gvisitor_t visitor = {
 		.nerr = 0,							// used to store number of found errors

+ 36 - 34
src/compiler/gravity_semacheck2.c

@@ -202,7 +202,7 @@ static gnode_t *lookup_identifier (gvisitor_t *self, const char *identifier, gno
 		uint16_t index = UINT16_MAX;
 		if (NODE_ISA(symbol, NODE_VARIABLE)) {
 			gnode_var_t *p = (gnode_var_t *)symbol;
-			index = p->index;
+			if (!p->iscomputed) index = p->index;
 		}
 
 		if (target_is_function) {
@@ -490,7 +490,7 @@ static bool check_class_ivar (gvisitor_t *self, gnode_class_decl_t *classnode, g
 	for (size_t i=0; i<count; ++i) {
 		gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
         if (!p) continue;
-		DEBUG_SEMANTIC("check_ivar %s", p->identifier);
+		DEBUG_SEMA2("check_ivar %s", p->identifier);
 
 		// do not check internal outer var
 		if (string_cmp(p->identifier, OUTER_IVAR_NAME) == 0) continue;
@@ -536,7 +536,7 @@ static void free_postfix_subexpr (gnode_postfix_subexpr_t *subnode) {
 // MARK: - Statements -
 
 static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_list_stmt");
+	DEBUG_SEMA2("visit_list_stmt");
 
 	PUSH_DECLARATION(node);
 	gnode_array_each(node->stmts, {visit(val);});
@@ -544,7 +544,7 @@ static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
 }
 
 static void visit_compound_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_compound_stmt");
+	DEBUG_SEMA2("visit_compound_stmt");
 
 	gnode_t			*top = TOP_DECLARATION();
 	symboltable_t	*symtable = symtable_from_node(top);
@@ -557,7 +557,7 @@ static void visit_compound_stmt (gvisitor_t *self, gnode_compound_stmt_t *node)
 }
 
 static void visit_label_stmt (gvisitor_t *self, gnode_label_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_label_stmt");
+	DEBUG_SEMA2("visit_label_stmt");
 
 	gtoken_t type = NODE_TOKEN_TYPE(node);
 	if (!TOP_STATEMENT_ISA_SWITCH()) {
@@ -570,7 +570,7 @@ static void visit_label_stmt (gvisitor_t *self, gnode_label_stmt_t *node) {
 }
 
 static void visit_flow_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_flow_stmt");
+	DEBUG_SEMA2("visit_flow_stmt");
 
 	// assignment has no side effect so report error in case of assignment
 	if (is_expression_assignment(node->cond)) REPORT_ERROR(node->cond, "Assignment not allowed here");
@@ -593,7 +593,7 @@ static void visit_flow_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
 }
 
 static void visit_loop_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_loop_stmt");
+	DEBUG_SEMA2("visit_loop_stmt");
 
 	gtoken_t type = NODE_TOKEN_TYPE(node);
 	PUSH_STATEMENT(type);
@@ -664,7 +664,7 @@ static void visit_loop_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
 }
 
 static void visit_jump_stmt (gvisitor_t *self, gnode_jump_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_jump_stmt");
+	DEBUG_SEMA2("visit_jump_stmt");
 
 	gtoken_t type = NODE_TOKEN_TYPE(node);
 	if (type == TOK_KEY_BREAK) {
@@ -691,7 +691,7 @@ static void visit_jump_stmt (gvisitor_t *self, gnode_jump_stmt_t *node) {
 }
 
 static void visit_empty_stmt (gvisitor_t *self, gnode_empty_stmt_t *node) {
-	DEBUG_SEMANTIC("visit_empty_stmt");
+	DEBUG_SEMA2("visit_empty_stmt");
 
 	// get top declaration
 	gnode_t *top = TOP_DECLARATION();
@@ -703,7 +703,7 @@ static void visit_empty_stmt (gvisitor_t *self, gnode_empty_stmt_t *node) {
 // MARK: - Declarations -
 
 static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
-	DEBUG_SEMANTIC("visit_function_decl %s", node->identifier);
+	DEBUG_SEMA2("visit_function_decl %s", node->identifier);
 
 	// set top declaration
 	gnode_t *top = TOP_DECLARATION();
@@ -716,7 +716,7 @@ static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node)
 
 	// enter function scope
 	PUSH_DECLARATION(node);
-	symboltable_t *symtable = symboltable_create(false);
+	symboltable_t *symtable = symboltable_create(SYMTABLE_TAG_FUNC);
 	symboltable_enter_scope(symtable);
 
 	// process parameters
@@ -730,7 +730,7 @@ static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node)
                 continue;
             }
 			SET_LOCAL_INDEX(p, symtable);
-			DEBUG_SEMANTIC("Local:%s index:%d", p->identifier, p->index);
+			DEBUG_SEMA2("Local:%s index:%d", p->identifier, p->index);
 		});
 	}
 
@@ -756,7 +756,7 @@ static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node)
 
 	POP_DECLARATION();
 
-	DEBUG_SEMANTIC("MAX LOCALS for function %s: %d", node->identifier, node->nlocals);
+	DEBUG_SEMA2("MAX LOCALS for function %s: %d", node->identifier, node->nlocals);
 }
 
 static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
@@ -772,7 +772,7 @@ static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node)
 	// loop to check each individual declaration
 	for (size_t i=0; i<count; ++i) {
 		gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
-		DEBUG_SEMANTIC("visit_variable_decl %s", p->identifier);
+		DEBUG_SEMA2("visit_variable_decl %s", p->identifier);
 
 		// set enclosing environment
 		p->env = top;
@@ -788,13 +788,15 @@ static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node)
                 continue;
             }
 			SET_LOCAL_INDEX(p, symtable);
-			DEBUG_SEMANTIC("Local:%s index:%d", p->identifier, p->index);
+			DEBUG_SEMA2("Local:%s index:%d", p->identifier, p->index);
 		} else if (env == NODE_CLASS_DECL) {
+            if (p->iscomputed) continue;
+            
 			// variable defined inside a class => property
 			gnode_class_decl_t *c = (gnode_class_decl_t *)top;
 
 			// compute new ivar index
-			uint32_t n1 = (node->storage == TOK_KEY_STATIC) ? c->nsvar++ : c->nivar++;
+			(node->storage == TOK_KEY_STATIC) ? ++c->nsvar : ++c->nivar;
 			uint32_t n2 = 0;
 
 			// super class is a static information so I can solve the fragile class problem at compilation time
@@ -806,14 +808,14 @@ static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node)
 				super = (gnode_class_decl_t *)super->superclass;
 			}
 
-			p->index = n1+n2;
-			DEBUG_SEMANTIC("Class: %s property:%s index:%d (static %d)", c->identifier, p->identifier, p->index, (node->storage == TOK_KEY_STATIC));
+			p->index += n2;
+			DEBUG_SEMA2("Class: %s property:%s index:%d (static %d)", c->identifier, p->identifier, p->index, (node->storage == TOK_KEY_STATIC));
 		}
 	}
 }
 
 static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
-	DEBUG_SEMANTIC("visit_enum_decl %s", node->identifier);
+	DEBUG_SEMA2("visit_enum_decl %s", node->identifier);
 
 	// check if optional access and storage specifiers make sense in current context
 	gnode_t *top = TOP_DECLARATION();
@@ -821,7 +823,7 @@ static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
 }
 
 static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
-	DEBUG_SEMANTIC("visit_class_decl %s", node->identifier);
+	DEBUG_SEMA2("visit_class_decl %s", node->identifier);
 
 	gnode_t *top = TOP_DECLARATION();
 
@@ -891,7 +893,7 @@ static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
 }
 
 static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
-	DEBUG_SEMANTIC("visit_module_decl %s", node->identifier);
+	DEBUG_SEMA2("visit_module_decl %s", node->identifier);
 
 	gnode_t *top = TOP_DECLARATION();
 
@@ -910,7 +912,7 @@ static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
 // MARK: - Expressions -
 
 static void visit_binary_expr (gvisitor_t *self, gnode_binary_expr_t *node) {
-	DEBUG_SEMANTIC("visit_binary_expr %s", token_name(node->op));
+	DEBUG_SEMA2("visit_binary_expr %s", token_name(node->op));
 
 	// sanity check
 	if (!is_expression(node->left)) REPORT_ERROR(node->left, "LValue must be an expression.");
@@ -929,13 +931,13 @@ static void visit_binary_expr (gvisitor_t *self, gnode_binary_expr_t *node) {
 }
 
 static void visit_unary_expr (gvisitor_t *self, gnode_unary_expr_t *node) {
-	DEBUG_SEMANTIC("visit_unary_expr %s", token_name(node->op));
+	DEBUG_SEMA2("visit_unary_expr %s", token_name(node->op));
 	visit(node->expr);
 	if (!is_expression_valid(node->expr)) REPORT_ERROR(node->expr, "Invalid expression.");
 }
 
 static void visit_postfix_expr (gvisitor_t *self, gnode_postfix_expr_t *node) {
-	DEBUG_SEMANTIC("visit_postfix_expr");
+	DEBUG_SEMA2("visit_postfix_expr");
     
     // sanity check
     if (!node->id) {REPORT_ERROR(node, "Invalid postfix expression."); return;}
@@ -1070,13 +1072,13 @@ static void visit_postfix_expr (gvisitor_t *self, gnode_postfix_expr_t *node) {
 		}
 
 		// should never reach this point
-		DEBUG_SEMANTIC("UNRECOGNIZED POSTFIX OPTIONAL EXPRESSION");
+		DEBUG_SEMA2("UNRECOGNIZED POSTFIX OPTIONAL EXPRESSION");
 		assert(0);
 	}
 }
 
 static void visit_file_expr (gvisitor_t *self, gnode_file_expr_t *node) {
-	DEBUG_SEMANTIC("visit_file_expr");
+	DEBUG_SEMA2("visit_file_expr");
 
 	gnode_r *decls = ((semacheck_t *)self->data)->declarations;
 	gnode_t *globals = gnode_array_get(decls, 0);
@@ -1088,7 +1090,7 @@ static void visit_file_expr (gvisitor_t *self, gnode_file_expr_t *node) {
 	n = 1;
 	for (size_t i=0; i<n; ++i) {
 		const char *identifier = gnode_array_get(node->identifiers, i);
-		DEBUG_SEMANTIC("LOOKUP %s", identifier);
+		DEBUG_SEMA2("LOOKUP %s", identifier);
 
 		gnode_t *symbol = lookup_node(target, identifier);
 		if (!symbol) {REPORT_ERROR(node, "Module identifier %s not found.", identifier); break;}
@@ -1103,8 +1105,8 @@ static void visit_literal_expr (gvisitor_t *self, gnode_literal_expr_t *node) {
 	#if GRAVITY_SEMANTIC_DEBUG
 	char value[256];
 	gnode_literal_dump(node, value, sizeof(value));
-	DEBUG_SEMANTIC("visit_literal_expr %s", value);
-	DEBUG_SEMANTIC("end visit_literal_expr");
+	DEBUG_SEMA2("visit_literal_expr %s", value);
+	DEBUG_SEMA2("end visit_literal_expr");
 	#endif
 
 	if (node->type == LITERAL_STRING_INTERPOLATED) {
@@ -1115,7 +1117,7 @@ static void visit_literal_expr (gvisitor_t *self, gnode_literal_expr_t *node) {
 }
 
 static void visit_identifier_expr (gvisitor_t *self, gnode_identifier_expr_t *node) {
-	DEBUG_SEMANTIC("visit_identifier_expr %s", node->value);
+	DEBUG_SEMA2("visit_identifier_expr %s", node->value);
 
 	gnode_t *symbol = lookup_identifier(self, node->value, node);
 	if (!symbol) REPORT_ERROR(node, "Identifier %s not found.", node->value);
@@ -1123,14 +1125,14 @@ static void visit_identifier_expr (gvisitor_t *self, gnode_identifier_expr_t *no
 
 static void visit_keyword_expr (gvisitor_t *self, gnode_keyword_expr_t *node) {
 	#pragma unused(self, node)
-	DEBUG_SEMANTIC("visit_keyword_expr %s", token_name(node->base.token.type));
+	DEBUG_SEMA2("visit_keyword_expr %s", token_name(node->base.token.type));
 }
 
 static void visit_list_expr (gvisitor_t *self, gnode_list_expr_t *node) {
 	size_t	n = gnode_array_size(node->list1);
 	bool	ismap = (node->list2 != NULL);
 
-	DEBUG_SEMANTIC("visit_list_expr (n: %zu ismap: %d)", n, ismap);
+	DEBUG_SEMA2("visit_list_expr (n: %zu ismap: %d)", n, ismap);
 
 	for (size_t j=0; j<n; ++j) {
 		gnode_t *e = gnode_array_get(node->list1, j);
@@ -1197,9 +1199,9 @@ bool gravity_semacheck2 (gnode_t *node, gravity_delegate_t *delegate) {
 		.visit_postfix_expr = visit_postfix_expr,
 	};
 
-	DEBUG_SEMANTIC("=== SEMANTIC CHECK STEP 2 ===");
+	DEBUG_SEMA2("=== SEMANTIC CHECK STEP 2 ===");
 	gvisit(&visitor, node);
-	DEBUG_SEMANTIC("\n");
+	DEBUG_SEMA2("\n");
 
 	marray_destroy(data.statements);
 	gnode_array_free(data.declarations);

+ 23 - 6
src/compiler/gravity_symboltable.c

@@ -28,7 +28,11 @@ typedef marray_t(gravity_hash_t*)	ghash_r;
 
 struct symboltable_t {
 	ghash_r		*stack;
-	uint32_t	counter;
+	uint16_t	    count1;     // used for local var
+    uint16_t        count2;     // used for ivar
+    uint16_t        count3;     // used for static ivar
+    uint16_t        unused;
+    symtable_tag    tag;
 };
 
 // MARK: -
@@ -62,14 +66,19 @@ static void symboltable_keyvalue_free (gravity_hash_t *hashtable, gravity_value_
 	gravity_value_free(NULL, key);
 }
 
-symboltable_t *symboltable_create (bool is_enum) {
+symboltable_t *symboltable_create (symtable_tag tag) {
+    bool is_enum = (tag == SYMTABLE_TAG_ENUM);
+    
 	symboltable_t	*table = mem_alloc(NULL, sizeof(symboltable_t));
 	gravity_hash_t	*hash = gravity_hash_create(0, gravity_value_hash, gravity_value_equals,
 												(is_enum) ? symboltable_keyvalue_free : symboltable_hash_free, NULL);
 	if (!table) return NULL;
 
 	// init symbol table
-	table->counter = 0;
+    table->tag = tag;
+	table->count1 = 0;
+    table->count2 = 0;
+    table->count3 = 0;
 	table->stack = mem_alloc(NULL, sizeof(ghash_r));
 	scope_stack_init(table->stack);
 	scope_stack_push(table->stack, hash);
@@ -107,7 +116,7 @@ bool symboltable_insert (symboltable_t *table, const char *identifier, gnode_t *
 	}
 	gravity_hash_insert(h, key, VALUE_FROM_NODE(node));
 
-	++table->counter;
+	++table->count1;
 	return true;
 }
 
@@ -129,6 +138,14 @@ uint32_t symboltable_count (symboltable_t *table, uint32_t index) {
 	return gravity_hash_count(h);
 }
 
+symtable_tag symboltable_tag (symboltable_t *table) {
+    return table->tag;
+}
+
+uint16_t symboltable_setivar (symboltable_t *table, bool is_static) {
+    return ((is_static) ? table->count3++ : table->count2++);
+}
+
 // MARK: -
 
 gnode_t *symboltable_global_lookup (symboltable_t *table, const char *identifier) {
@@ -145,7 +162,7 @@ void symboltable_enter_scope (symboltable_t *table) {
 }
 
 uint32_t symboltable_local_index (symboltable_t *table) {
-	return table->counter - 1;
+	return table->count1 - 1;
 }
 
 uint32_t symboltable_exit_scope (symboltable_t *table, uint32_t *nlevel) {
@@ -155,7 +172,7 @@ uint32_t symboltable_exit_scope (symboltable_t *table, uint32_t *nlevel) {
 		gravity_hash_iterate(h, check_upvalue_inscope, (void *)nlevel);
 	}
 	gravity_hash_free(h);
-	return table->counter;
+	return table->count1;
 }
 
 void symboltable_dump (symboltable_t *table) {

+ 11 - 1
src/compiler/gravity_symboltable.h

@@ -17,11 +17,21 @@
 typedef struct symboltable_t	symboltable_t;
 #endif
 
-symboltable_t	*symboltable_create (bool is_enum);
+typedef enum {
+    SYMTABLE_TAG_GLOBAL = 0,
+    SYMTABLE_TAG_FUNC = 1,
+    SYMTABLE_TAG_CLASS = 2,
+    SYMTABLE_TAG_MODULE = 3,
+    SYMTABLE_TAG_ENUM = 4
+} symtable_tag;
+
+symboltable_t	*symboltable_create (symtable_tag tag);
 gnode_t			*symboltable_lookup (symboltable_t *table, const char *identifier);
 gnode_t			*symboltable_global_lookup (symboltable_t *table, const char *identifier);
 bool			symboltable_insert (symboltable_t *table, const char *identifier, gnode_t *node);
 uint32_t		symboltable_count (symboltable_t *table, uint32_t index);
+symtable_tag    symboltable_tag (symboltable_t *table);
+uint16_t        symboltable_setivar (symboltable_t *table, bool is_static);
 
 void			symboltable_enter_scope (symboltable_t *table);
 uint32_t		symboltable_exit_scope (symboltable_t *table, uint32_t *nlevel);

+ 85 - 9
src/optionals/gravity_math.c

@@ -393,6 +393,51 @@ static bool math_lcm (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uin
     RETURN_VALUE(VALUE_FROM_INT(lcm_value), rindex);
 }
 
+// returns the linear interpolation from a to b of value t
+static bool math_lerp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+#pragma unused(vm, nargs)
+    // three arguments are required
+    if (nargs < 4) RETURN_VALUE(VALUE_FROM_UNDEFINED, rindex);
+
+    gravity_float_t a, b, t;
+
+    gravity_value_t value = GET_VALUE(1);
+    if (VALUE_ISA_INT(value)) {
+        a = (gravity_float_t)value.n;
+    } else {
+        if (VALUE_ISA_FLOAT(value)) {
+            a = value.f;
+        } else {
+            RETURN_VALUE(VALUE_FROM_UNDEFINED, rindex);
+        }
+    }
+
+    value = GET_VALUE(2);
+    if (VALUE_ISA_INT(value)) {
+        b = (gravity_float_t)value.n;
+    } else {
+        if (VALUE_ISA_FLOAT(value)) {
+            b = value.f;
+        } else {
+            RETURN_VALUE(VALUE_FROM_UNDEFINED, rindex);
+        }
+    }
+
+    value = GET_VALUE(3);
+    if (VALUE_ISA_INT(value)) {
+        t = (gravity_float_t)value.n;
+    } else {
+        if (VALUE_ISA_FLOAT(value)) {
+            t = value.f;
+        } else {
+            RETURN_VALUE(VALUE_FROM_UNDEFINED, rindex);
+        }
+    }
+
+    gravity_float_t lerp = a+(b-a)*t;
+    RETURN_VALUE(VALUE_FROM_FLOAT(lerp), rindex);
+}
+
 // returns the natural logarithm (base E) of x
 static bool math_log (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
     #pragma unused(vm, nargs)
@@ -475,36 +520,66 @@ static bool math_logx (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
 
 // returns the number with the highest value
 static bool math_max (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
-    gravity_float_t n = FLOAT_MIN;
-    gravity_value_t result = VALUE_FROM_UNDEFINED;
+    if (nargs == 1) RETURN_VALUE(VALUE_FROM_NULL, rindex);
+
+    gravity_float_t n = -FLOAT_MAX;
+    uint16_t maxindex = 1;
+    bool found = false;
 
     for (uint16_t i = 1; i<nargs; ++i) {
         gravity_value_t value = GET_VALUE(i);
         if (VALUE_ISA_INT(value)) {
-            if ((gravity_float_t)value.n > n) result = value;
+            found = true;
+            if ((gravity_float_t)value.n > n) {
+                n = (gravity_float_t)value.n;
+                maxindex = i;
+            }
         } else if (VALUE_ISA_FLOAT(value)) {
-            if (value.f > n) result = value;
+            found = true;
+            if (value.f > n) {
+                n = value.f;
+                maxindex = i;
+            }
         } else continue;
     }
 
-    RETURN_VALUE(result, rindex);
+    if (!found) {
+        RETURN_VALUE(VALUE_FROM_NULL, rindex);
+    }
+
+    RETURN_VALUE(GET_VALUE(maxindex), rindex);
 }
 
 // returns the number with the lowest value
 static bool math_min (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    if (nargs == 1) RETURN_VALUE(VALUE_FROM_NULL, rindex);
+
     gravity_float_t n = FLOAT_MAX;
-    gravity_value_t result = VALUE_FROM_UNDEFINED;
+    uint16_t minindex = 1;
+    bool found = false;
 
     for (uint16_t i = 1; i<nargs; ++i) {
         gravity_value_t value = GET_VALUE(i);
         if (VALUE_ISA_INT(value)) {
-            if ((gravity_float_t)value.n < n) result = value;
+            found = true;
+            if ((gravity_float_t)value.n < n) {
+                n = (gravity_float_t)value.n;
+                minindex = i;
+            }
         } else if (VALUE_ISA_FLOAT(value)) {
-            if (value.f < n) result = value;
+            found = true;
+            if (value.f < n) {
+                n = value.f;
+                minindex = i;
+            }
         } else continue;
     }
 
-    RETURN_VALUE(result, rindex);
+    if (!found) {
+        RETURN_VALUE(VALUE_FROM_NULL, rindex);
+    }
+
+    RETURN_VALUE(GET_VALUE(minindex), rindex);
 }
 
 // returns the value of x to the power of y
@@ -706,6 +781,7 @@ static void create_optional_class (void) {
     gravity_class_bind(meta, "floor", NEW_CLOSURE_VALUE(math_floor));
     gravity_class_bind(meta, "gcf", NEW_CLOSURE_VALUE(math_gcf));
     gravity_class_bind(meta, "lcm", NEW_CLOSURE_VALUE(math_lcm));
+    gravity_class_bind(meta, "lerp", NEW_CLOSURE_VALUE(math_lerp));
     gravity_class_bind(meta, "log", NEW_CLOSURE_VALUE(math_log));
     gravity_class_bind(meta, "log10", NEW_CLOSURE_VALUE(math_log10));
     gravity_class_bind(meta, "logx", NEW_CLOSURE_VALUE(math_logx));

+ 109 - 48
src/runtime/gravity_core.c

@@ -376,6 +376,7 @@ inline gravity_value_t convert_value2string (gravity_vm *vm, gravity_value_t v)
 	if (VALUE_ISA_BOOL(v)) return VALUE_FROM_CSTRING(vm, (v.n) ? "true" : "false");
 	if (VALUE_ISA_NULL(v)) return VALUE_FROM_CSTRING(vm, "null");
 	if (VALUE_ISA_UNDEFINED(v)) return VALUE_FROM_CSTRING(vm, "undefined");
+    
 	if (VALUE_ISA_FLOAT(v)) {
 		char buffer[512];
 		snprintf(buffer, sizeof(buffer), "%f", v.f);
@@ -417,6 +418,12 @@ inline gravity_value_t convert_value2string (gravity_vm *vm, gravity_value_t v)
         return VALUE_FROM_CSTRING(vm, buffer);
     }
 	
+    if (VALUE_ISA_FIBER(v)) {
+        char buffer[512];
+        snprintf(buffer, sizeof(buffer), "Fiber %p", VALUE_AS_OBJECT(v));
+        return VALUE_FROM_CSTRING(vm, buffer);
+    }
+	
 	// check if class implements the String method (avoiding infinte loop by checking for convert_object_string)
 	gravity_closure_t *closure = gravity_vm_fastlookup(vm, gravity_value_getclass(v), GRAVITY_STRING_INDEX);
 
@@ -575,9 +582,8 @@ static bool object_real_load (gravity_vm *vm, gravity_value_t *args, uint16_t na
     }
 	if (!obj) goto execute_notfound;
 
-	gravity_closure_t *closure;
 	if (OBJECT_ISA_CLOSURE(obj)) {
-		closure = (gravity_closure_t *)obj;
+		gravity_closure_t *closure = (gravity_closure_t *)obj;
 		if (!closure || !closure->f) goto execute_notfound;
 
 		// execute optimized default getter
@@ -602,10 +608,11 @@ static bool object_real_load (gravity_vm *vm, gravity_value_t *args, uint16_t na
 
 	RETURN_VALUE(VALUE_FROM_OBJECT(obj), rindex);
 
-execute_notfound:
-	// in case of not found error return the notfound function to be executed (MANDATORY)
-	closure = (gravity_closure_t *)gravity_class_lookup(c, gravity_vm_keyindex(vm, GRAVITY_NOTFOUND_INDEX));
-	RETURN_CLOSURE(VALUE_FROM_OBJECT(closure), rindex);
+execute_notfound: {
+		// in case of not found error return the notfound function to be executed (MANDATORY)
+    	gravity_closure_t *closure = (gravity_closure_t *)gravity_class_lookup(c, gravity_vm_keyindex(vm, GRAVITY_NOTFOUND_INDEX));
+		RETURN_CLOSURE(VALUE_FROM_OBJECT(closure), rindex);
+	}
 }
 
 static bool object_loads (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
@@ -1128,6 +1135,7 @@ static bool list_join (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
 
 	// create a new empty buffer
 	uint32_t alloc = (uint32_t) (marray_size(list->array) * 64);
+    if (alloc == 0) alloc = 256;
 	uint32_t len = 0;
 	uint32_t seplen = (sep) ? VALUE_AS_STRING(GET_VALUE(1))->len : 0;
 	char *_buffer = mem_alloc(vm, alloc);
@@ -1144,13 +1152,13 @@ static bool list_join (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
             RETURN_VALUE(value, rindex);
         }
 
-        // compute string to appen
+        // compute string to append
 		const char *s2 = VALUE_AS_STRING(value)->s;
 		uint32_t req = VALUE_AS_STRING(value)->len;
 		uint32_t free_mem = alloc - len;
 
 		// check if buffer needs to be reallocated
-		if (free_mem < req + seplen) {
+		if (free_mem < req + seplen + 1) {
             uint64_t to_alloc = alloc + (req + seplen) * 2 + 4096;
 			_buffer = mem_realloc(vm, _buffer, (uint32_t)to_alloc);
             if (!_buffer) {
@@ -1164,10 +1172,14 @@ static bool list_join (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
 		memcpy(_buffer+len, s2, req);
 		len += req;
 
+        // NULL terminate the C string
+        _buffer[len] = 0;
+		
 		// check for separator string
 		if (i+1 < n && seplen) {
 			memcpy(_buffer+len, sep, seplen);
 			len += seplen;
+            _buffer[len] = 0;
 		}
 
 		++i;
@@ -1356,8 +1368,8 @@ static bool range_iterator (gravity_vm *vm, gravity_value_t *args, uint16_t narg
 	#pragma unused(vm, nargs)
 	gravity_range_t *range = VALUE_AS_RANGE(GET_VALUE(0));
 
-	// check for empty range first
-	if (range->from == range->to) RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+	// check for invalid range first
+	if (range->to < range->from) RETURN_VALUE(VALUE_FROM_FALSE, rindex);
 
 	// check for start of iteration
 	if (VALUE_ISA_NULL(GET_VALUE(1))) RETURN_VALUE(VALUE_FROM_INT(range->from), rindex);
@@ -1491,11 +1503,19 @@ static bool closure_apply (gravity_vm *vm, gravity_value_t *args, uint16_t nargs
 }
 
 static bool closure_bind (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
-    if (nargs != 2) RETURN_ERROR("An argument is required by the setself function.");
+    if (nargs != 2) RETURN_ERROR("An argument is required by the bind function.");
+    
+    if (!VALUE_ISA_CLOSURE(GET_VALUE(0))) {
+        // Houston, we have a problem
+        RETURN_NOVALUE();
+    }
     
     gravity_closure_t *closure = VALUE_AS_CLOSURE(GET_VALUE(0));
-    gravity_value_t self_value = GET_VALUE(1);
-    closure->self_value = self_value;
+    if (VALUE_ISA_NULL(GET_VALUE(1))) {
+        closure->context = NULL;
+    } else if (gravity_value_isobject(GET_VALUE(1))) {
+        closure->context = VALUE_AS_OBJECT(GET_VALUE(1));
+    }
     
     RETURN_NOVALUE();
 }
@@ -1591,36 +1611,6 @@ static bool operator_float_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t
     
     // simpler equality test
 	if (v1.f == v2.f) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
-    
-    #if GRAVITY_ENABLE_DOUBLE
-    double diff = fabs(v1.f - v2.f);
-    #else
-    float diff = fabsf(v1.f - v2.f);
-    #endif
-    
-    // simple equality test
-    if (diff < FLOAT_EPSILON) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
-    
-    // a more accurate equality test is needed for floating point numbers
-    // from https://stackoverflow.com/questions/30808556/float-vs-double-comparison
-    // and http://floating-point-gui.de/errors/comparison/
-    // check also https://bitbashing.io/comparing-floats.html
-    // the ref should be https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
-    if (v1.f == 0 || v2.f == 0 || diff < FLOAT_MIN) {
-        if (diff < (FLOAT_EPSILON * FLOAT_MIN)) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
-    } else {
-        #if GRAVITY_ENABLE_DOUBLE
-        double a = fabs((double)v1.f);
-        double b = fabs((double)v2.f);
-        double min = fmin(a + b, FLOAT_MAX);
-        #else
-        float a = fabsf((float)v1.f);
-        float b = fabsf((float)v2.f);
-        float min = fminf(a + b, FLOAT_MAX);
-        #endif
-        if (diff / min < FLOAT_EPSILON) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
-    }
-    
 	if (v1.f > v2.f) RETURN_VALUE(VALUE_FROM_INT(1), rindex);
 	RETURN_VALUE(VALUE_FROM_INT(-1), rindex);
 }
@@ -1672,7 +1662,36 @@ static bool float_degrees (gravity_vm *vm, gravity_value_t *args, uint16_t nargs
 static bool float_radians (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
 	#pragma unused(vm, nargs)
 	// Convert the float from degrees to radians
-	RETURN_VALUE(VALUE_FROM_FLOAT(GET_VALUE(0).f*3.141592653589793/180), rindex);
+	RETURN_VALUE(VALUE_FROM_FLOAT(GET_VALUE(0).f*3.141592653589793/180.0), rindex);
+}
+
+static bool float_isclose (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    if (nargs < 2) RETURN_VALUE(VALUE_FROM_BOOL(true), rindex);
+    
+    DECLARE_2VARIABLES(v1, v2, 0, 1);
+    INTERNAL_CONVERT_FLOAT(v2);
+    gravity_float_t rel_tol = 1e-09;
+    gravity_float_t abs_tol=0.0;
+    if (nargs > 2 && VALUE_ISA_FLOAT(GET_VALUE(2))) rel_tol = VALUE_AS_FLOAT(GET_VALUE(2));
+    if (nargs > 3 && VALUE_ISA_FLOAT(GET_VALUE(3))) abs_tol = VALUE_AS_FLOAT(GET_VALUE(3));
+    
+    // abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
+    
+    #if GRAVITY_ENABLE_DOUBLE
+    gravity_float_t abs_diff = fabs(v1.f - v2.f);
+    gravity_float_t abs_a = fabs(v1.f);
+    gravity_float_t abs_b = fabs(v2.f);
+    gravity_float_t abs_max = fmax(abs_a, abs_b);
+    gravity_float_t result = fmax(rel_tol * abs_max, abs_tol);
+    #else
+    gravity_float_t abs_diff = fabsf(v1.f - v2.f);
+    gravity_float_t abs_a = fabsf(v1.f);
+    gravity_float_t abs_b = fabsf(v2.f);
+    gravity_float_t abs_max = fmaxf(abs_a, abs_b);
+    gravity_float_t result = fmaxf(rel_tol * abs_max, abs_tol);
+    #endif
+    
+    RETURN_VALUE(VALUE_FROM_BOOL(abs_diff <= result), rindex);
 }
 
 // MARK: - Int Class -
@@ -2421,10 +2440,12 @@ static bool fiber_run (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
 	gravity_fiber_t *fiber = VALUE_AS_FIBER(GET_VALUE(0));
 	if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
 
-    if (fiber->timewait > 0.0f) {
+    // always update elapsed time
+    fiber->elapsedtime = (nanotime() - fiber->lasttime) / 1000000000.0f;
+    
         // check if minimum timewait is passed
-        gravity_float_t elapsed = (nanotime() - fiber->lasttime) / 1000000000.0f;
-        if (elapsed < fiber->timewait) RETURN_NOVALUE();
+    if (fiber->timewait > 0.0f) {
+        if (fiber->elapsedtime < fiber->timewait) RETURN_NOVALUE();
     }
     
 	// remember who ran the fiber
@@ -2432,6 +2453,7 @@ static bool fiber_run (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
 
 	// set trying flag
 	fiber->trying = is_trying;
+    fiber->status = (is_trying) ? FIBER_TRYING : FIBER_RUNNING;
 
 	// switch currently running fiber
 	gravity_vm_setfiber(vm, fiber);
@@ -2507,13 +2529,37 @@ static bool fiber_yield_time (gravity_vm *vm, gravity_value_t *args, uint16_t na
     }
 }
 
-static bool fiber_status (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+static bool fiber_done (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
 	#pragma unused(nargs)
 
+    // returns true is the fiber has terminated execution
 	gravity_fiber_t *fiber = VALUE_AS_FIBER(GET_VALUE(0));
 	RETURN_VALUE(VALUE_FROM_BOOL(fiber->nframes == 0 || fiber->error), rindex);
 }
 
+static bool fiber_status (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    #pragma unused(nargs)
+    
+    // status codes:
+    // 0    never executed
+    // 1    aborted with error
+    // 2    terminated
+    // 3    running
+    // 4    trying
+    
+    gravity_fiber_t *fiber = VALUE_AS_FIBER(GET_VALUE(0));
+    if (fiber->error) RETURN_VALUE(VALUE_FROM_INT(FIBER_ABORTED_WITH_ERROR), rindex);
+    if (fiber->nframes == 0) RETURN_VALUE(VALUE_FROM_INT(FIBER_TERMINATED), rindex);
+    RETURN_VALUE(VALUE_FROM_INT(fiber->status), rindex);
+}
+
+static bool fiber_elapsed_time (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    #pragma unused(nargs)
+    
+    gravity_fiber_t *fiber = VALUE_AS_FIBER(GET_VALUE(0));
+    RETURN_VALUE(VALUE_FROM_FLOAT(fiber->elapsedtime), rindex);
+}
+
 static bool fiber_abort (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
 	gravity_value_t msg = (nargs > 0) ? GET_VALUE(1) : VALUE_FROM_NULL;
 	if (!VALUE_ISA_STRING(msg)) RETURN_ERROR("Fiber.abort expects a string as argument.");
@@ -2582,6 +2628,12 @@ static bool operator_null_not (gravity_vm *vm, gravity_value_t *args, uint16_t n
 #if GRAVITY_NULL_SILENT
 static bool operator_null_silent (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
 	#pragma unused(vm,args,nargs)
+    gravity_value_t key = GET_VALUE(1);
+    if (VALUE_ISA_STRING(key)) {
+        gravity_object_t *obj = (gravity_object_t *)gravity_class_lookup(gravity_class_null, key);
+        if (obj) RETURN_VALUE(VALUE_FROM_OBJECT(obj), rindex);
+    }
+    
     // every operation on NULL returns NULL
 	RETURN_VALUE(VALUE_FROM_NULL, rindex);
 }
@@ -2610,6 +2662,11 @@ static bool null_exec (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, ui
     RETURN_VALUE(VALUE_FROM_NULL, rindex);
 }
 
+static bool null_iterator (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    #pragma unused(vm, args, nargs)
+    RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+}
+
 // MARK: - System -
 
 static bool system_nanotime (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
@@ -2893,6 +2950,7 @@ static void gravity_core_init (void) {
 	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_NEG_NAME, NEW_CLOSURE_VALUE(operator_float_neg));
 	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_NOT_NAME, NEW_CLOSURE_VALUE(operator_float_not));
 	gravity_class_bind(gravity_class_float, "round", NEW_CLOSURE_VALUE(function_float_round));
+    gravity_class_bind(gravity_class_float, "isClose", NEW_CLOSURE_VALUE(float_isclose));
 	gravity_class_bind(gravity_class_float, "floor", NEW_CLOSURE_VALUE(function_float_floor));
 	gravity_class_bind(gravity_class_float, "ceil", NEW_CLOSURE_VALUE(function_float_ceil));
     closure = computed_property_create(NULL, NEW_FUNCTION(float_radians), NULL);
@@ -2954,6 +3012,8 @@ static void gravity_core_init (void) {
 	gravity_class_bind(fiber_meta, "yield", NEW_CLOSURE_VALUE(fiber_yield));
     gravity_class_bind(fiber_meta, "yieldWaitTime", NEW_CLOSURE_VALUE(fiber_yield_time));
 	gravity_class_bind(gravity_class_fiber, "status", NEW_CLOSURE_VALUE(fiber_status));
+    gravity_class_bind(gravity_class_fiber, "isDone", NEW_CLOSURE_VALUE(fiber_done));
+    gravity_class_bind(gravity_class_fiber, "elapsedTime", NEW_CLOSURE_VALUE(fiber_elapsed_time));
 	gravity_class_bind(fiber_meta, "abort", NEW_CLOSURE_VALUE(fiber_abort));
 
 	// BASIC OPERATIONS added also to NULL CLASS (and UNDEFINED since they points to the same class)
@@ -2974,6 +3034,7 @@ static void gravity_core_init (void) {
 	gravity_class_bind(gravity_class_null, GRAVITY_INTERNAL_STORE_NAME, NEW_CLOSURE_VALUE(operator_store_null_silent));
 	gravity_class_bind(gravity_class_null, GRAVITY_INTERNAL_NOTFOUND_NAME, NEW_CLOSURE_VALUE(operator_null_silent));
 	#endif
+    gravity_class_bind(gravity_class_null, "iterate", NEW_CLOSURE_VALUE(null_iterator));
 
 	// SYSTEM class
 	gravity_class_system = gravity_class_new_pair(NULL, GRAVITY_CLASS_SYSTEM_NAME, NULL, 0, 0);

+ 3 - 3
src/runtime/gravity_vm.c

@@ -1085,8 +1085,8 @@ static bool gravity_vm_exec (gravity_vm *vm) {
 					++r3;
 				}
 
-                // check for custom self
-                if (VALUE_ISA_VALID(closure->self_value)) SETVALUE(rwin, closure->self_value);
+                // check for closure autocaptured self context (or custom self set by the user)
+                if (closure->context) SETVALUE(rwin, VALUE_FROM_OBJECT(closure->context));
 				
 				DEBUG_STACK();
 
@@ -1525,7 +1525,7 @@ bool gravity_vm_runclosure (gravity_vm *vm, gravity_closure_t *closure, gravity_
 
 	DEBUG_STACK();
 
-    // self value is default to the context where the closure has been created
+    // self value is default to the context where the closure has been created (or set by the user)
     gravity_value_t selfvalue = (closure->context) ? VALUE_FROM_OBJECT(closure->context) : sender;
     
     // we need a way to give user the ability to access the sender value from a closure

+ 13 - 2
src/shared/gravity_value.c

@@ -1189,6 +1189,9 @@ void gravity_closure_blacken (gravity_vm *vm, gravity_closure_t *closure) {
 		gravity_gray_object(vm, (gravity_object_t*)upvalue[0]);
 		++upvalue;
 	}
+    
+    // mark context (if any)
+    if (closure->context) gravity_gray_object(vm, closure->context);
 }
 
 // MARK: -
@@ -1254,6 +1257,9 @@ gravity_fiber_t *gravity_fiber_new (gravity_vm *vm, gravity_closure_t *closure,
 	frame->dest = 0;
 	frame->stackstart = fiber->stack;
 
+    // replace self with fiber instance
+    frame->stackstart[0] = VALUE_FROM_OBJECT(fiber);
+	
 	gravity_vm_transfer(vm, (gravity_object_t*) fiber);
 	return fiber;
 }
@@ -1503,10 +1509,12 @@ void gravity_object_blacken (gravity_vm *vm, gravity_object_t *obj) {
 // MARK: -
 
 gravity_instance_t *gravity_instance_new (gravity_vm *vm, gravity_class_t *c) {
-	gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(NULL, sizeof(gravity_instance_t) + (c->nivars * sizeof(gravity_value_t)));
+	gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(NULL, sizeof(gravity_instance_t));
 
 	instance->isa = gravity_class_instance;
 	instance->objclass = c;
+    
+    if (c->nivars) instance->ivars = (gravity_value_t *)mem_alloc(NULL, c->nivars * sizeof(gravity_value_t));
 	for (uint32_t i=0; i<c->nivars; ++i) instance->ivars[i] = VALUE_FROM_NULL;
 
 	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) instance);
@@ -1516,12 +1524,14 @@ gravity_instance_t *gravity_instance_new (gravity_vm *vm, gravity_class_t *c) {
 gravity_instance_t *gravity_instance_clone (gravity_vm *vm, gravity_instance_t *src) {
 	gravity_class_t *c = src->objclass;
 
-	gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(NULL, sizeof(gravity_instance_t) + (c->nivars * sizeof(gravity_value_t)));
+	gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(NULL, sizeof(gravity_instance_t));
 	instance->isa = gravity_class_instance;
     instance->objclass = c; // TODO: if gravity_class_is_anon(c) then c must be deeply copied
     
     gravity_delegate_t *delegate = gravity_vm_delegate(vm);
     instance->xdata = (src->xdata && delegate->bridge_clone) ? delegate->bridge_clone(vm, src->xdata) : NULL;
+    
+    if (c->nivars) instance->ivars = (gravity_value_t *)mem_alloc(NULL, c->nivars * sizeof(gravity_value_t));
 	for (uint32_t i=0; i<c->nivars; ++i) instance->ivars[i] = src->ivars[i];
 
 	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) instance);
@@ -1545,6 +1555,7 @@ void gravity_instance_free (gravity_vm *vm, gravity_instance_t *i) {
 		if (delegate->bridge_free) delegate->bridge_free(vm, (gravity_object_t *)i);
 	}
 
+    if (i->ivars) mem_free(i->ivars);
 	mem_free((void *)i);
 }
 

+ 14 - 6
src/shared/gravity_value.h

@@ -66,8 +66,8 @@
 extern "C" {
 #endif
 
-#define GRAVITY_VERSION						"0.4.9"     // git tag 0.4.9
-#define GRAVITY_VERSION_NUMBER				0x000409    // git push --tags
+#define GRAVITY_VERSION						"0.5.0"     // git tag 0.5.0
+#define GRAVITY_VERSION_NUMBER				0x000500    // git push --tags
 #define GRAVITY_BUILD_DATE					__DATE__
 
 #ifndef GRAVITY_ENABLE_DOUBLE
@@ -290,10 +290,8 @@ typedef struct {
 	gravity_gc_t			gc;					// to be collectable by the garbage collector
 
 	gravity_function_t		*f;					// function prototype
-    gravity_object_t        *context;           // context where the closure has been created
+    gravity_object_t        *context;           // context where the closure has been created (or object bound by the user)
 	gravity_upvalue_t		**upvalue;			// upvalue array
-    
-    gravity_value_t         self_value;        // custom self value set by the user
 } gravity_closure_t;
 
 typedef struct {
@@ -321,6 +319,14 @@ typedef struct {
 	bool					outloop;			// special case for events or native code executed from C that must be executed separately
 } gravity_callframe_t;
 
+typedef enum {
+    FIBER_NEVER_EXECUTED = 0,
+    FIBER_ABORTED_WITH_ERROR = 1,
+    FIBER_TERMINATED = 2,
+    FIBER_RUNNING = 3,
+    FIBER_TRYING = 4
+} gravity_fiber_status;
+    
 // Fiber is the core executable model
 typedef struct fiber_s {
 	gravity_class_t			*isa;				// to be an object
@@ -341,8 +347,10 @@ typedef struct fiber_s {
 	struct fiber_s			*caller;			// optional caller fiber
 	gravity_value_t			result;				// end result of the fiber
     
+    gravity_fiber_status    status;             // Fiber status (see enum)
     nanotime_t              lasttime;           // last time Fiber has been called
     gravity_float_t         timewait;           // used in yieldTime
+    gravity_float_t         elapsedtime;        // time passed since last execution
 } gravity_fiber_t;
 
 typedef struct gravity_class_s {
@@ -377,7 +385,7 @@ typedef struct {
 
 	gravity_class_t			*objclass;			// real instance class
 	void					*xdata;				// extra bridged data
-	gravity_value_t			ivars[];			// instance variables (MUST BE LAST in the struct!)
+	gravity_value_t			*ivars;			    // instance variables
 } gravity_instance_t;
 
 typedef struct {

+ 32 - 0
test/unittest/closure_bind_2.gravity

@@ -0,0 +1,32 @@
+#unittest {
+	name: "Closure bind test 2.";
+	result: 183;
+};
+
+var g = 33;
+
+class Class1 {
+    func foo1 () {
+        g += 100;
+    }
+}
+
+class Class2 {
+    func foo1 () {
+        g += 50;
+    }
+}
+
+func main() {
+    var c1 = Class1();
+    var c2 = Class2();
+    var closure = {self.foo1()};
+    
+    closure.bind(c1);
+    closure();
+    
+    closure.bind(c2);
+    closure();
+    
+    return g;
+}

+ 37 - 0
test/unittest/game_loop.gravity

@@ -0,0 +1,37 @@
+#unittest {
+	name: "Game loop with Fibers.";
+	result: 100;
+};
+
+var g = 50;
+
+class FirePool {
+    func reset (index) {
+        g = index;
+    }
+}
+
+class Level {
+    var firePool = FirePool();
+}
+
+class Window1 {
+    var level = Level();
+}
+
+class Game {
+    var window1 = Window1();
+    
+    func start() {
+        var fireIndex = 100;
+        var reset = {window1.level.firePool.reset(fireIndex);}
+        var fiber = Fiber.create({reset()})
+        fiber.call();
+    }
+}
+
+func main() {
+    var game = Game();
+    game.start();
+    return g;
+}

+ 21 - 0
test/unittest/init_inline_exec.gravity

@@ -0,0 +1,21 @@
+#unittest {
+    name: "Init/exec inline.";
+    result: 600;
+};
+
+class Foo {
+    func test(value) {
+        return value * 2;
+    }
+}
+
+func main() {
+    var foo = Foo();
+    var n1 = foo.test(100);
+    if (n1 != 200) return 0;
+    
+    var n2 = Foo().test(200);
+    if (n2 != 400) return 0;
+    
+    return n1+n2;
+}

+ 19 - 0
test/unittest/ivar_access.gravity

@@ -0,0 +1,19 @@
+#unittest {
+    name: "ivar access.";
+    result: 100;
+};
+
+class Foo {
+    var v1;
+    
+    func test() {
+        return v2;
+    }
+    
+    var v2 = 100;
+}
+
+func main() {
+    var c = Foo();
+    return c.test();
+}

+ 38 - 0
test/unittest/ivar_nested_class.gravity

@@ -0,0 +1,38 @@
+#unittest {
+    name: "Ivar and nested classes.";
+    result: 666;
+};
+
+class _Window1 {
+    
+    var Button1;
+    var Button2;
+    
+    class _Button1 {
+    };
+    
+    class _Button2 {
+    };
+    
+    public var var1;
+    func DidShow() {
+        return var1;
+    }
+    
+    func _init_container() {
+        Button1 = "Button1";
+        Button2 = "Button2";
+        var1 = 666;
+    }
+    
+    func _free_container() {
+        Button1 = null;
+        Button2 = null;
+    }
+};
+
+func main() {
+    var Window1 = _Window1();
+    Window1._init_container();
+    return Window1.DidShow();
+}

+ 20 - 0
test/unittest/math_lerp.gravity

@@ -0,0 +1,20 @@
+#unittest {
+    name: "Math lerp test.";
+    result: 11.0;
+};
+
+func main() {
+    var n0 = Math.lerp(0,1,0);          // 0.0
+    if (n0 != 0.0) return 0;
+    
+    var n1 = Math.lerp(10, 100, 0);     // 10.0
+    if (n1 != 10.0) return 0;
+    
+    var n2 = Math.lerp(-1,1,1);         // 1.0
+    if (n2 != 1.0) return 0;
+    
+    var n3 = Math.lerp(-1,1,0.5);       // 0.0
+    if (n3 != 0.0) return 0;
+    
+    return n0+n1+n2+n3;
+}

+ 33 - 0
test/unittest/math_min_max.gravity

@@ -0,0 +1,33 @@
+#unittest {
+    name: "Math min/max test.";
+    result: -1;
+};
+
+func main() {
+    var n0 = Math.min(1,2,3);       // 1
+    if (n0 != 1) return 0;
+    
+    var n1 = Math.max(1,2,3);       // 3
+    if (n1 != 3) return 0;
+    
+    var n2 = Math.min(-1,-2,-3);    // -3
+    if (n2 != -3) return 0;
+    
+    var n3 = Math.max(-1,-2,-3);    // -1
+    if (n3 != -1) return 0;
+    
+    var n4 = Math.min(-1,-2,0,1);   // -2
+    if (n4 != -2) return 0;
+    
+    var n5 = Math.max(-1,-2,0,1);   // 1
+    if (n5 != 1) return 0;
+    
+    var n6 = Math.min("not a number", -1,-2, "not a number", 1,2);  // -2
+    if (n6 != -2) return 0;
+    
+    var n7 = Math.max("not a number", -1,-2, "not a number", 1,2);  // 2
+    if (n7 != 2) return 0;
+    
+    
+    return n0+n1+n2+n3+n4+n5+n6+n7;
+}

+ 16 - 0
test/unittest/range_test.gravity

@@ -0,0 +1,16 @@
+#unittest {
+	name: "Range test.";
+	result: 6;
+};
+
+func main() {
+    var count = 0;
+    
+    for (var i in 0..<0) {count +=1}    // 0
+    for (var i in 0..<1) {count +=1}    // +1
+    for (var i in 0..<2) {count +=1}    // +2
+    for (var i in 0...1) {count +=1}    // +2
+    for (var i in 0...0) {count +=1}    // +1
+    
+    return count;
+}

+ 35 - 0
test/unittest/superclass_declared_after.gravity

@@ -0,0 +1,35 @@
+#unittest {
+    name: "Superclass declared after subclass.";
+    result: 410;
+};
+
+class GameBehaviourChild:GameBehaviour {
+    func start () {
+        time = 200
+        view = 40
+        return view + destroy + time;
+    }
+}
+
+class GameBehaviour {
+    var destroy = 20;
+    var time = 100;
+    var view = null;
+    
+    func start () {
+        view = 30
+        return view + destroy + time;
+    }
+}
+
+func main() {
+    var c = GameBehaviourChild()
+    var n1 = c.start()
+    if (n1 != 260) return 0;
+    
+    var d = GameBehaviour()
+    var n2 = d.start()
+    if (n2 != 150) return 0;
+    
+    return n1 + n2;
+}