Parcourir la source

Added support for default argument values (unit test added).

Marco Bambini il y a 7 ans
Parent
commit
2dc8bdd988

+ 1 - 0
src/compiler/gravity_ast.h

@@ -123,6 +123,7 @@ typedef struct {
 	gnode_compound_stmt_t *block;			// internal function statements
 	uint16_t			nlocals;			// locals counter
 	uint16_t			nparams;			// formal parameters counter
+    bool                has_defaults;       // flag set if parmas has default values
 	gupvalue_r			*uplist;			// list of upvalues used in function (can be empty)
 } gnode_function_decl_t;
 typedef gnode_function_decl_t gnode_function_expr_t;

+ 20 - 3
src/compiler/gravity_codegen.c

@@ -133,15 +133,15 @@ static opcode_t token2opcode (gtoken_t op) {
 	assert(0);
 	return NOT;
 }
-
-#if 0
 static gravity_value_t literal2value (gnode_literal_expr_t *node) {
 	if (node->type == LITERAL_STRING) return VALUE_FROM_STRING(NULL, node->value.str, node->len);
 	if (node->type == LITERAL_FLOAT) return VALUE_FROM_FLOAT(node->value.d);
 	if (node->type == LITERAL_INT) return VALUE_FROM_INT(node->value.n64);
-	return VALUE_FROM_INT(node->value.n64); // default BOOLEAN case
+    if (node->type == LITERAL_BOOL) return VALUE_FROM_BOOL(node->value.n64);
+    return VALUE_FROM_NULL;
 }
 
+#if 0
 static gravity_list_t *literals2list (gvisitor_t *self, gnode_r *r, uint32_t start, uint32_t stop) {
 	gravity_list_t *list = gravity_list_new(GET_VM(), stop-start);
 
@@ -876,6 +876,23 @@ static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node)
 	// name must be CLASS_CONSTRUCTOR_NAME and context_object must be a class
 	bool is_constructor = (string_cmp(node->identifier, CLASS_CONSTRUCTOR_NAME) == 0) && (is_class_ctx);
 
+    // loop at each param to save name and check for default values
+    // loop from 1 in order to skip implicit self argument
+    size_t len = gnode_array_size(node->params);
+    for (size_t i=1; i<len; ++i) {
+        gnode_var_t *param = (gnode_var_t *)gnode_array_get(node->params, i);
+        gravity_value_t name = VALUE_FROM_CSTRING(NULL, param->identifier);
+        marray_push(gravity_value_t, f->pname, name);
+        
+        if (node->has_defaults) {
+            // LOOP for each node->params[i]->expr and convert literal to gravity_value_t
+            // if expr is NULL then compute value as UNDEFINED (and add each one to f->defaults preserving the index)
+            gnode_literal_expr_t *literal = (gnode_literal_expr_t *)param->expr;
+            gravity_value_t value = (literal) ? literal2value(literal) : VALUE_FROM_UNDEFINED;
+            marray_push(gravity_value_t, f->pvalue, value);
+        }
+    }
+    
 	CONTEXT_PUSH(f);
 
 	if (is_constructor) {

+ 34 - 7
src/compiler/gravity_parser.c

@@ -112,12 +112,13 @@ static grammar_rule rules[TOK_END];
 // MARK: - Prototypes -
 static const char *parse_identifier (gravity_parser_t *parser);
 static gnode_t *parse_statement (gravity_parser_t *parser);
-static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser, bool is_implicit);
+static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser, bool is_implicit, bool *has_default_values);
 static gnode_t *parse_compound_statement (gravity_parser_t *parser);
 static gnode_t *parse_expression (gravity_parser_t *parser);
 static gnode_t *parse_declaration_statement (gravity_parser_t *parser);
 static gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t access_specifier, gtoken_t storage_specifier);
 static gnode_t *adjust_assignment_expression (gravity_parser_t *parser, gtoken_t tok, gnode_t *lnode, gnode_t *rnode);
+static gnode_t *parse_literal_expression (gravity_parser_t *parser);
 static gnode_t *parse_macro_statement (gravity_parser_t *parser);
 
 // MARK: - Utils functions -
@@ -295,7 +296,8 @@ gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t
 	if (!is_implicit) parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
 
 	// parse optional parameter declaration clause
-	gnode_r *params = parse_optional_parameter_declaration(parser, is_implicit);
+    bool has_default_values = false;
+	gnode_r *params = parse_optional_parameter_declaration(parser, is_implicit, &has_default_values);
 
 	// check and consume TOK_OP_CLOSED_PARENTHESIS
 	if (!is_implicit) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
@@ -313,6 +315,7 @@ gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t
 	if (!is_inside_var_declaration) parse_semicolon(parser);
 
     // finish func setup
+    func->has_defaults = has_default_values;
     func->params = params;
     func->block = compound;
     return (gnode_t *)func;
@@ -470,12 +473,31 @@ static const char *parse_optional_type_annotation (gravity_parser_t *parser) {
 
 		// parse identifier
 		type_annotation = parse_identifier(parser);
-		if (!type_annotation) return NULL;
 	}
 
 	return type_annotation;
 }
 
+static gnode_t *parse_optional_default_value (gravity_parser_t *parser) {
+    DECLARE_LEXER;
+    gnode_t     *default_value = NULL;
+    gtoken_t    peek = gravity_lexer_peek(lexer);
+    
+    // optional literal default value
+    // function foo (a: string = "Hello", b: number = 3)
+    // type annotation not enforced here
+    
+    // check for optional default value
+    if (peek == TOK_OP_ASSIGN) {
+        gravity_lexer_next(lexer); // consume TOK_OP_ASSIGN
+        
+        // parse literal value
+        default_value = parse_literal_expression(parser);
+    }
+    
+    return default_value;
+}
+
 static gnode_t *parse_parentheses_expression (gravity_parser_t *parser) {
 	DEBUG_PARSER("parse_parentheses_expression");
 
@@ -833,7 +855,7 @@ return_string:
 	return (r) ? gnode_string_interpolation_create(token, r, LAST_DECLARATION()) : gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION());
 }
 
-static gnode_t *parse_literal_expression (gravity_parser_t *parser) {
+gnode_t *parse_literal_expression (gravity_parser_t *parser) {
 	DEBUG_PARSER("parse_literal_expression");
 	DECLARE_LEXER;
 
@@ -1225,7 +1247,7 @@ static gnode_t *parse_getter_setter (gravity_parser_t *parser, gravity_hash_t *m
 			// check if parameters are explicit
 			if (gravity_lexer_peek(lexer) == TOK_OP_OPEN_PARENTHESIS) {
 				parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
-				params = parse_optional_parameter_declaration(parser, false);	// add implicit SELF
+				params = parse_optional_parameter_declaration(parser, false, NULL);	// add implicit SELF
 				parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
 			} else {
 				params = gnode_array_create();	// add implicit SELF and VALUE params
@@ -1744,12 +1766,13 @@ static gnode_t *parse_class_declaration (gravity_parser_t *parser, gtoken_t acce
 	return (gnode_t *)node;
 }
 
-static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser, bool is_implicit) {
+static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser, bool is_implicit, bool *has_default_values) {
 	DEBUG_PARSER("parse_parameter_declaration");
 	DECLARE_LEXER;
 
 	gtoken_s	token = NO_TOKEN;
 	gnode_t		*node = NULL;
+    gnode_t     *default_value = NULL;
 	const char	*identifier = NULL;
 	const char	*type_annotation = NULL;
 
@@ -1791,8 +1814,12 @@ loop:
         parser->delegate->type_callback(&token, type_annotation, parser->delegate->xdata);
     }
 
+    // parse optional default LITERAL value
+    default_value = parse_optional_default_value(parser);
+    if (default_value && has_default_values) *has_default_values = true;
+    
 	// fill parameters array with the new node
-	node = gnode_variable_create(token, identifier, type_annotation, 0, NULL, LAST_DECLARATION());
+	node = gnode_variable_create(token, identifier, type_annotation, 0, default_value, LAST_DECLARATION());
 	if (node) gnode_array_push(params, node);
     DEBUG_PARSER("PARAMETER: %s %s", identifier, (type_annotation) ? type_annotation : "");
 

+ 8 - 0
src/runtime/gravity_vm.c

@@ -1063,6 +1063,14 @@ static bool gravity_vm_exec (gravity_vm *vm) {
 				execute_call_function:
 				switch(closure->f->tag) {
 					case EXEC_TYPE_NATIVE: {
+                        // support for default arg values
+                        if (marray_size(closure->f->pvalue)) {
+                            uint32_t n = 1; // from 1 in order to skip self implicit argument
+                            while (n < closure->f->nparams) {
+                                if (VALUE_ISA_UNDEFINED(STACK_GET(rwin+n))) SETVALUE(rwin+n, marray_get(closure->f->pvalue, n-1));
+                                ++n;
+                            }
+                        }
 						PUSH_FRAME(closure, &stackstart[rwin], r1, r3);
 					} break;
 

+ 2 - 0
src/shared/gravity_macros.h

@@ -111,6 +111,8 @@
 #define GRAVITY_JSON_LABELVALUE             "value"
 #define GRAVITY_JSON_LABELIDENTIFIER		"identifier"
 #define GRAVITY_JSON_LABELPOOL				"pool"
+#define GRAVITY_JSON_LABELPVALUES           "pvalues"
+#define GRAVITY_JSON_LABELPNAMES            "pnames"
 #define GRAVITY_JSON_LABELMETA				"meta"
 #define GRAVITY_JSON_LABELBYTECODE			"bytecode"
 #define GRAVITY_JSON_LABELNPARAM			"nparam"

+ 160 - 21
src/shared/gravity_value.c

@@ -488,6 +488,8 @@ gravity_function_t *gravity_function_new (gravity_vm *vm, const char *identifier
 	f->useargs = false;
 	f->bytecode = (uint32_t *)code;
 	marray_init(f->cpool);
+    marray_init(f->pvalue);
+    marray_init(f->pname);
 
 	if (vm) gravity_vm_transfer(vm, (gravity_object_t*)f);
 	return f;
@@ -545,47 +547,82 @@ void gravity_function_setxdata (gravity_function_t *f, void *xdata) {
 	f->xdata = xdata;
 }
 
-static void gravity_function_cpool_serialize (gravity_function_t *f, json_t *json) {
+static void gravity_function_array_serialize (gravity_function_t *f, json_t *json, gravity_value_r r) {
 	assert(f->tag == EXEC_TYPE_NATIVE);
-	size_t n = marray_size(f->cpool);
+	size_t n = marray_size(r);
 
 	for (size_t i=0; i<n; i++) {
-		gravity_value_t v = marray_get(f->cpool, i);
+		gravity_value_t v = marray_get(r, i);
 		gravity_value_serialize(v, json);
 	}
 }
 
-static void gravity_function_cpool_dump (gravity_function_t *f) {
+static void gravity_function_array_dump (gravity_function_t *f, gravity_value_r r) {
 	assert(f->tag == EXEC_TYPE_NATIVE);
-	size_t n = marray_size(f->cpool);
+	size_t n = marray_size(r);
 
 	for (size_t i=0; i<n; i++) {
-		gravity_value_t v = marray_get(f->cpool, i);
+		gravity_value_t v = marray_get(r, i);
 
-		if (v.isa == gravity_class_bool) {
+        if (VALUE_ISA_NULL(v)) {
+            printf("%05zu\tNULL\n", i);
+            continue;
+        }
+        
+        if (VALUE_ISA_UNDEFINED(v)) {
+            printf("%05zu\tUNDEFINED\n", i);
+            continue;
+        }
+        
+		if (VALUE_ISA_BOOL(v)) {
 			printf("%05zu\tBOOL: %d\n", i, (v.n == 0) ? 0 : 1);
-		} else if (v.isa == gravity_class_int) {
+            continue;
+        }
+        
+        if (VALUE_ISA_INT(v)) {
 			printf("%05zu\tINT: %" PRId64 "\n", i, (int64_t)v.n);
-		} else if (v.isa == gravity_class_float) {
+            continue;
+		}
+        
+        if (VALUE_ISA_FLOAT(v)) {
 			printf("%05zu\tFLOAT: %f\n", i, (double)v.f);
-		} else if (v.isa == gravity_class_function) {
+            continue;
+		}
+        
+        if (VALUE_ISA_FUNCTION(v)) {
 			gravity_function_t *vf = VALUE_AS_FUNCTION(v);
 			printf("%05zu\tFUNC: %s\n", i, (vf->identifier) ? vf->identifier : "$anon");
-		}  else if (v.isa == gravity_class_class) {
+            continue;
+		}
+        
+        if (VALUE_ISA_CLASS(v)) {
 			gravity_class_t *c = VALUE_AS_CLASS(v);
 			printf("%05zu\tCLASS: %s\n", i, (c->identifier) ? c->identifier: "$anon");
-		} else if (v.isa == gravity_class_string) {
+            continue;
+            
+		}
+        
+        if (VALUE_ISA_STRING(v)) {
 			printf("%05zu\tSTRING: %s\n", i, VALUE_AS_CSTRING(v));
-		} else if (v.isa == gravity_class_list) {
+            continue;
+		}
+        
+        if (VALUE_ISA_LIST(v)) {
 			gravity_list_t *value = VALUE_AS_LIST(v);
 			size_t count = marray_size(value->array);
 			printf("%05zu\tLIST: %zu items\n", i, count);
-		} else if (v.isa == gravity_class_map) {
+            continue;
+            
+		}
+        
+        if (VALUE_ISA_MAP(v)) {
 			gravity_map_t *map = VALUE_AS_MAP(v);
 			printf("%05zu\tMAP: %u items\n", i, gravity_hash_count(map->hash));
-		} else {
-			assert(0);
+            continue;
 		}
+        
+        // should never reach this point
+        assert(0);
 	}
 }
 
@@ -659,10 +696,16 @@ void gravity_function_dump (gravity_function_t *f, code_dump_function codef) {
 	printf("Params:%d Locals:%d Temp:%d Upvalues:%d Tag:%d xdata:%p\n", f->nparams, f->nlocals, f->ntemps, f->nupvalues, f->tag, f->xdata);
 
 	if (f->tag == EXEC_TYPE_NATIVE) {
-		if (marray_size(f->cpool)) printf("======= CPOOL =======\n");
-		gravity_function_cpool_dump(f);
-
-		printf("======= BYTECODE ====\n");
+		if (marray_size(f->cpool)) printf("======= CONST POOL =======\n");
+		gravity_function_array_dump(f, f->cpool);
+        
+        if (marray_size(f->pname)) printf("======= PARAM NAMES =======\n");
+        gravity_function_array_dump(f, f->pname);
+        
+        if (marray_size(f->pvalue)) printf("======= PARAM VALUES =======\n");
+        gravity_function_array_dump(f, f->pvalue);
+
+		printf("======= BYTECODE =======\n");
 		if ((f->bytecode) && (codef)) codef(f->bytecode);
 	}
 
@@ -730,10 +773,24 @@ void gravity_function_serialize (gravity_function_t *f, json_t *json) {
 
 		// constant pool
 		json_begin_array(json, GRAVITY_JSON_LABELPOOL);
-		gravity_function_cpool_serialize(f, json);
+		gravity_function_array_serialize(f, json, f->cpool);
+		json_end_array(json);
+        
+        // default values (if any)
+        if (marray_size(f->pvalue)) {
+            json_begin_array(json, GRAVITY_JSON_LABELPVALUES);
+            gravity_function_array_serialize(f, json, f->pvalue);
 		json_end_array(json);
 	}
 
+        // arg names (if any)
+        if (marray_size(f->pname)) {
+            json_begin_array(json, GRAVITY_JSON_LABELPNAMES);
+            gravity_function_array_serialize(f, json, f->pname);
+            json_end_array(json);
+        }
+	}
+	
 	if (identifier) json_end_object(json);
 }
 
@@ -875,6 +932,59 @@ gravity_function_t *gravity_function_deserialize (gravity_vm *vm, json_value *js
 			continue;
 		}
 
+        // names
+        if (string_casencmp(label, GRAVITY_JSON_LABELPNAMES, label_size) == 0) {
+            if (value->type != json_array) goto abort_load;
+            if (f->tag != EXEC_TYPE_NATIVE) goto abort_load;
+            uint32_t m = value->u.array.length;
+            for (uint32_t j=0; j<m; ++j) {
+                json_value *r = value->u.array.values[j];
+                if (r->type != json_string) goto abort_load;
+                marray_push(gravity_value_t, f->pname, VALUE_FROM_STRING(NULL, r->u.string.ptr, r->u.string.length));
+            }
+        }
+        
+        // defaults
+        if (string_casencmp(label, GRAVITY_JSON_LABELPVALUES, label_size) == 0) {
+            if (value->type != json_array) goto abort_load;
+            if (f->tag != EXEC_TYPE_NATIVE) goto abort_load;
+            
+            uint32_t m = value->u.array.length;
+            for (uint32_t j=0; j<m; ++j) {
+                json_value *r = value->u.array.values[j];
+                switch (r->type) {
+                    case json_integer:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_INT((gravity_int_t)r->u.integer));
+                        break;
+                        
+                    case json_double:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_FLOAT((gravity_float_t)r->u.dbl));
+                        break;
+                        
+                    case json_boolean:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_BOOL(r->u.boolean));
+                        break;
+                        
+                    case json_string:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_STRING(NULL, r->u.string.ptr, r->u.string.length));
+                        break;
+                        
+                    case json_object:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_UNDEFINED);
+                        break;
+                        
+                    case json_null:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_NULL);
+                        break;
+                        
+                    case json_none:
+                    case json_array:
+                        marray_push(gravity_value_t, f->pvalue, VALUE_FROM_NULL);
+                        break;
+                }
+            }
+        }
+		
 		// cpool
 		if (string_casencmp(label, GRAVITY_JSON_LABELPOOL, label_size) == 0) {
 			if (value->type != json_array) goto abort_load;
@@ -963,6 +1073,23 @@ void gravity_function_free (gravity_vm *vm, gravity_function_t *f) {
 	if (f->identifier) mem_free((void *)f->identifier);
 	if (f->tag == EXEC_TYPE_NATIVE) {
 		if (f->bytecode) mem_free((void *)f->bytecode);
+        
+        // FREE EACH DEFAULT value
+        size_t n = marray_size(f->pvalue);
+        for (size_t i=0; i<n; i++) {
+            gravity_value_t v = marray_get(f->pvalue, i);
+            gravity_value_free(NULL, v);
+        }
+        marray_destroy(f->pvalue);
+        
+        // FREE EACH PARAM name
+        n = marray_size(f->pname);
+        for (size_t i=0; i<n; i++) {
+            gravity_value_t v = marray_get(f->pname, i);
+            gravity_value_free(NULL, v);
+        }
+        marray_destroy(f->pname);
+        
 		// DO NOT FREE EACH INDIVIDUAL CPOOL ITEM HERE
 		marray_destroy(f->cpool);
     }
@@ -1598,6 +1725,18 @@ static void gravity_map_serialize_iterator (gravity_hash_t *hashtable, gravity_v
 }
 
 void gravity_value_serialize (gravity_value_t v, json_t *json) {
+    // NULL
+    if (VALUE_ISA_NULL(v)) {
+        json_add_null(json, NULL);
+        return;
+    }
+    
+    // UNDEFINED (convention used to represent an UNDEFINED value)
+    if (VALUE_ISA_UNDEFINED(v)) {
+        json_begin_object(json, NULL);
+        json_end_object(json);
+        return;
+    }
 
 	// BOOL
 	if (VALUE_ISA_BOOL(v)) {

+ 4 - 2
src/shared/gravity_value.h

@@ -66,8 +66,8 @@
 extern "C" {
 #endif
 
-#define GRAVITY_VERSION						"0.4.1"     // git tag 0.4.1
-#define GRAVITY_VERSION_NUMBER				0x000401    // git push --tags
+#define GRAVITY_VERSION						"0.4.2"     // git tag 0.4.2
+#define GRAVITY_VERSION_NUMBER				0x000402    // git push --tags
 #define GRAVITY_BUILD_DATE					__DATE__
 
 #ifndef GRAVITY_ENABLE_DOUBLE
@@ -255,6 +255,8 @@ typedef struct {
 		// tag == EXEC_TYPE_NATIVE
 		struct {
 			gravity_value_r	cpool;				// constant pool
+            gravity_value_r pvalue;             // default param value
+            gravity_value_r pname;              // param names
 			uint32_t		ninsts;				// number of instructions in the bytecode
 			uint32_t		*bytecode;			// bytecode as array of 32bit values
 			float			purity;				// experimental value

+ 19 - 0
test/unittest/default_values.gravity

@@ -0,0 +1,19 @@
+#unittest {
+	name: "Default argument values.";
+	result: 380;
+};
+
+func test (n1 = 10, n2 = 20, n3 = 30, n4 = 40) {
+    return n1+n2+n3+n4;
+}
+
+func main() {
+    var v1 = test(1,2,3,4);     // 10
+    var v2 = test(1,2,3);       // 46
+    var v3 = test(1,2);         // 73
+    var v4 = test(1);           // 91
+    var v5 = test();            // 100
+    var v6 = test(0,,0);        // 60
+    
+    return v1+v2+v3+v4+v5+v6;
+}