Browse Source

Add `"pure"` procedure types

gingerBill 5 years ago
parent
commit
aa029fe8d9
9 changed files with 72 additions and 17 deletions
  1. 17 0
      examples/demo/demo.odin
  2. 4 2
      src/check_decl.cpp
  3. 32 14
      src/check_expr.cpp
  4. 11 0
      src/check_stmt.cpp
  5. 4 1
      src/check_type.cpp
  6. 1 0
      src/checker.hpp
  7. 1 0
      src/ir_print.cpp
  8. 1 0
      src/parser.cpp
  9. 1 0
      src/parser.hpp

+ 17 - 0
examples/demo/demo.odin

@@ -1961,6 +1961,22 @@ relative_data_types :: proc() {
 	fmt.println(rel_slice[1]);
 }
 
+pure_procedures :: proc() {
+	fmt.println("\n#pure procedures");
+
+	square :: proc "pure" (x: int) -> int {
+		return x*x + 1;
+	}
+
+	do_math :: proc "pure" (x: int) -> int {
+		// Only "pure" procedure calls are allowed within a "pure" procedure
+		return square(x) + 1;
+	}
+
+	x := do_math(5);
+	fmt.println("do_math(5) ==", x);
+}
+
 main :: proc() {
 	when true {
 		the_basics();
@@ -1993,5 +2009,6 @@ main :: proc() {
 		union_maybe();
 		explicit_context_definition();
 		relative_data_types();
+		pure_procedures();
 	}
 }

+ 4 - 2
src/check_decl.cpp

@@ -1174,11 +1174,14 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
 	CheckerContext new_ctx = *ctx_;
 	CheckerContext *ctx = &new_ctx;
 
+	GB_ASSERT(type->kind == Type_Proc);
+
 	ctx->scope = decl->scope;
 	ctx->decl = decl;
 	ctx->proc_name = proc_name;
 	ctx->curr_proc_decl = decl;
 	ctx->curr_proc_sig  = type;
+	ctx->curr_proc_calling_convention = type->Proc.calling_convention;
 
 	ast_node(bs, BlockStmt, body);
 
@@ -1187,7 +1190,6 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
 	defer (array_free(&using_entities));
 
 	{
-		GB_ASSERT(type->kind == Type_Proc);
 		if (type->Proc.param_count > 0) {
 			TypeTuple *params = &type->Proc.params->Tuple;
 			for_array(i, params->variables) {
@@ -1242,7 +1244,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
 		// NOTE(bill, 2019-08-31): Don't check the body as the where clauses failed
 		return;
 	}
-	
+
 	check_open_scope(ctx, body);
 	{
 		for_array(i, using_entities) {

+ 32 - 14
src/check_expr.cpp

@@ -1165,6 +1165,11 @@ Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Typ
 		if (e->flags & EntityFlag_Value) {
 			o->mode = Addressing_Value;
 		}
+		if (c->curr_proc_calling_convention == ProcCC_Pure) {
+			if (e->scope->flags & (ScopeFlag_Global|ScopeFlag_File|ScopeFlag_Pkg)) {
+				error(n, "Global variables are not allowed within a \"pure\" procedure, got '%.*s'", LIT(e->token.string));
+			}
+		}
 		break;
 
 	case Entity_Procedure:
@@ -7434,6 +7439,14 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 		}
 	}
 
+	{
+		if (c->curr_proc_calling_convention == ProcCC_Pure) {
+			if (pt->kind == Type_Proc && pt->Proc.calling_convention != ProcCC_Pure) {
+				error(call, "Only \"pure\" procedure calls are allowed within a \"pure\" procedure");
+			}
+		}
+	}
+
 	#if 0
 	if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) {
 		init_core_context(c->checker);
@@ -7755,23 +7768,28 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 	case_ast_node(i, Implicit, node)
 		switch (i->kind) {
 		case Token_context:
-			if (c->proc_name.len == 0 && c->curr_proc_sig == nullptr) {
-				error(node, "'context' is only allowed within procedures %p", c->curr_proc_decl);
-				return kind;
-			}
+			{
+				if (c->proc_name.len == 0 && c->curr_proc_sig == nullptr) {
+					error(node, "'context' is only allowed within procedures %p", c->curr_proc_decl);
+					return kind;
+				}
+				if (c->curr_proc_calling_convention == ProcCC_Pure) {
+					error(node, "'context' is not allowed within a \"pure\" procedure");
+				} else {
+					if (unparen_expr(c->assignment_lhs_hint) == node) {
+						c->scope->flags |= ScopeFlag_ContextDefined;
+					}
 
-			if (unparen_expr(c->assignment_lhs_hint) == node) {
-				c->scope->flags |= ScopeFlag_ContextDefined;
-			}
+					if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) {
+						error(node, "'context' has not been defined within this scope");
+						// Continue with value
+					}
+				}
 
-			if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) {
-				error(node, "'context' has not been defined within this scope");
-				// Continue with value
+				init_core_context(c->checker);
+				o->mode = Addressing_Context;
+				o->type = t_context;
 			}
-
-			init_core_context(c->checker);
-			o->mode = Addressing_Context;
-			o->type = t_context;
 			break;
 
 		default:

+ 11 - 0
src/check_stmt.cpp

@@ -1301,6 +1301,11 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 	case_end;
 
 	case_ast_node(as, AssignStmt, node);
+		if (ctx->curr_proc_calling_convention == ProcCC_Pure) {
+			error(node, "Assignment statements are not allowed within a \"pure\" procedure");
+			// Continue
+		}
+
 		switch (as->op.kind) {
 		case Token_Eq: {
 			// a, b, c = 1, 2, 3;  // Multisided
@@ -2027,6 +2032,12 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 			check_arity_match(ctx, vd);
 			check_init_variables(ctx, entities, entity_count, vd->values, str_lit("variable declaration"));
 
+			if (ctx->curr_proc_calling_convention == ProcCC_Pure) {
+				if (vd->values.count == 0) {
+					error(node, "Variable declarations without assignment are not allowed within \"pure\" procedures");
+				}
+			}
+
 			for (isize i = 0; i < entity_count; i++) {
 				Entity *e = entities[i];
 

+ 4 - 1
src/check_type.cpp

@@ -2452,6 +2452,7 @@ void set_procedure_abi_types(gbAllocator allocator, Type *type) {
 			switch (type->Proc.calling_convention) {
 			case ProcCC_Odin:
 			case ProcCC_Contextless:
+			case ProcCC_Pure:
 				if (is_type_pointer(new_type) & !is_type_pointer(e->type)) {
 					e->flags |= EntityFlag_ImplicitReference;
 				}
@@ -2548,7 +2549,9 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
 		type->Proc.has_named_results = first->token.string != "";
 	}
 
-
+	if (result_count == 0 && cc == ProcCC_Pure) {
+		error(proc_type_node, "\"pure\" procedures must have at least 1 return value");
+	}
 
 
 	bool optional_ok = (pt->tags & ProcTag_optional_ok) != 0;

+ 1 - 0
src/checker.hpp

@@ -297,6 +297,7 @@ struct CheckerContext {
 	String         proc_name;
 	DeclInfo *     curr_proc_decl;
 	Type *         curr_proc_sig;
+	ProcCallingConvention curr_proc_calling_convention;
 	bool           in_proc_sig;
 	ForeignContext foreign_context;
 	gbAllocator    allocator;

+ 1 - 0
src/ir_print.cpp

@@ -1434,6 +1434,7 @@ void ir_print_calling_convention(irFileBuffer *f, irModule *m, ProcCallingConven
 	switch (cc) {
 	case ProcCC_Odin:        ir_write_str_lit(f, "");       break;
 	case ProcCC_Contextless: ir_write_str_lit(f, "");       break;
+	case ProcCC_Pure:        ir_write_str_lit(f, "");       break;
 	// case ProcCC_CDecl:       ir_write_str_lit(f, "ccc ");   break;
 	case ProcCC_CDecl:       ir_write_str_lit(f, "");   break;
 	case ProcCC_StdCall:     ir_write_str_lit(f, "cc 64 "); break;

+ 1 - 0
src/parser.cpp

@@ -2987,6 +2987,7 @@ Ast *parse_results(AstFile *f, bool *diverging) {
 ProcCallingConvention string_to_calling_convention(String s) {
 	if (s == "odin")        return ProcCC_Odin;
 	if (s == "contextless") return ProcCC_Contextless;
+	if (s == "pure")        return ProcCC_Pure;
 	if (s == "cdecl")       return ProcCC_CDecl;
 	if (s == "c")           return ProcCC_CDecl;
 	if (s == "stdcall")     return ProcCC_StdCall;

+ 1 - 0
src/parser.hpp

@@ -176,6 +176,7 @@ enum ProcCallingConvention {
 	ProcCC_Invalid = 0,
 	ProcCC_Odin,
 	ProcCC_Contextless,
+	ProcCC_Pure,
 	ProcCC_CDecl,
 	ProcCC_StdCall,
 	ProcCC_FastCall,