Browse Source

Add range-based error messages to `-verbose-errors`
Example:
Cannot convert '(1 + 2)' to 'untyped bool' from 'untyped integer'

x := (1 + 2) * true;
^~~~~~^

gingerBill 4 years ago
parent
commit
9c54ed5792
9 changed files with 429 additions and 167 deletions
  1. 2 2
      src/check_builtin.cpp
  2. 9 8
      src/check_expr.cpp
  3. 1 1
      src/check_stmt.cpp
  4. 5 5
      src/check_type.cpp
  5. 2 2
      src/llvm_backend.cpp
  6. 34 124
      src/parser.cpp
  7. 3 3
      src/parser.hpp
  8. 331 0
      src/parser_pos.cpp
  9. 42 22
      src/tokenizer.cpp

+ 2 - 2
src/check_builtin.cpp

@@ -87,7 +87,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 
 	case BuiltinProc_DIRECTIVE: {
 		ast_node(bd, BasicDirective, ce->proc);
-		String name = bd->name;
+		String name = bd->name.string;
 		if (name == "defined") {
 			break;
 		}
@@ -124,7 +124,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 
 	case BuiltinProc_DIRECTIVE: {
 		ast_node(bd, BasicDirective, ce->proc);
-		String name = bd->name;
+		String name = bd->name.string;
 		if (name == "location") {
 			if (ce->args.count > 1) {
 				error(ce->args[0], "'#location' expects either 0 or 1 arguments, got %td", ce->args.count);

+ 9 - 8
src/check_expr.cpp

@@ -5516,7 +5516,7 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 	if (proc != nullptr &&
 	    proc->kind == Ast_BasicDirective) {
 		ast_node(bd, BasicDirective, proc);
-		String name = bd->name;
+		String name = bd->name.string;
 		if (name == "location" || name == "assert" || name == "panic" || name == "defined" || name == "config" || name == "load") {
 			operand->mode = Addressing_Builtin;
 			operand->builtin_id = BuiltinProc_DIRECTIVE;
@@ -6191,13 +6191,14 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 
 	case_ast_node(bd, BasicDirective, node);
 		o->mode = Addressing_Constant;
-		if (bd->name == "file") {
+		String name = bd->name.string;
+		if (name == "file") {
 			o->type = t_untyped_string;
 			o->value = exact_value_string(get_file_path_string(bd->token.pos.file_id));
-		} else if (bd->name == "line") {
+		} else if (name == "line") {
 			o->type = t_untyped_integer;
 			o->value = exact_value_i64(bd->token.pos.line);
-		} else if (bd->name == "procedure") {
+		} else if (name == "procedure") {
 			if (c->curr_proc_decl == nullptr) {
 				error(node, "#procedure may only be used within procedures");
 				o->type = t_untyped_string;
@@ -6206,7 +6207,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 				o->type = t_untyped_string;
 				o->value = exact_value_string(c->proc_name);
 			}
-		} else if (bd->name == "caller_location") {
+		} else if (name == "caller_location") {
 			init_core_source_code_location(c->checker);
 			error(node, "#caller_location may only be used as a default argument parameter");
 			o->type = t_source_code_location;
@@ -6373,7 +6374,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 					if (cl->type->ArrayType.tag != nullptr) {
 						Ast *tag = cl->type->ArrayType.tag;
 						GB_ASSERT(tag->kind == Ast_BasicDirective);
-						String name = tag->BasicDirective.name;
+						String name = tag->BasicDirective.name.string;
 						if (name == "soa") {
 							error(node, "#soa arrays are not supported for compound literals");
 							return kind;
@@ -6385,7 +6386,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 				if (cl->elems.count > 0) {
 					Ast *tag = cl->type->DynamicArrayType.tag;
 					GB_ASSERT(tag->kind == Ast_BasicDirective);
-					String name = tag->BasicDirective.name;
+					String name = tag->BasicDirective.name.string;
 					if (name == "soa") {
 						error(node, "#soa arrays are not supported for compound literals");
 						return kind;
@@ -8151,7 +8152,7 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
 
 	case_ast_node(bd, BasicDirective, node);
 		str = gb_string_append_rune(str, '#');
-		str = string_append_string(str, bd->name);
+		str = string_append_string(str, bd->name.string);
 	case_end;
 
 	case_ast_node(ud, Undef, node);

+ 1 - 1
src/check_stmt.cpp

@@ -7,7 +7,7 @@ bool is_diverging_stmt(Ast *stmt) {
 		return false;
 	}
 	if (expr->CallExpr.proc->kind == Ast_BasicDirective) {
-		String name = expr->CallExpr.proc->BasicDirective.name;
+		String name = expr->CallExpr.proc->BasicDirective.name.string;
 		return name == "panic";
 	}
 	Ast *proc = unparen_expr(expr->CallExpr.proc);

+ 5 - 5
src/check_type.cpp

@@ -1191,7 +1191,7 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 
 	if (allow_caller_location &&
 	    expr->kind == Ast_BasicDirective &&
-	    expr->BasicDirective.name == "caller_location") {
+	    expr->BasicDirective.name.string == "caller_location") {
 		init_core_source_code_location(ctx->checker);
 		param_value.kind = ParameterValue_Location;
 		o.type = t_source_code_location;
@@ -2711,7 +2711,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 				bool is_partial = false;
 				if (at->tag != nullptr) {
 					GB_ASSERT(at->tag->kind == Ast_BasicDirective);
-					String name = at->tag->BasicDirective.name;
+					String name = at->tag->BasicDirective.name.string;
 					if (name == "partial") {
 						is_partial = true;
 					} else {
@@ -2745,7 +2745,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 
 			if (at->tag != nullptr) {
 				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
-				String name = at->tag->BasicDirective.name;
+				String name = at->tag->BasicDirective.name.string;
 				if (name == "soa") {
 					*type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type);
 				} else if (name == "simd") {
@@ -2770,7 +2770,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 
 			if (at->tag != nullptr) {
 				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
-				String name = at->tag->BasicDirective.name;
+				String name = at->tag->BasicDirective.name.string;
 				if (name == "soa") {
 					*type = make_soa_struct_slice(ctx, e, at->elem, elem);
 				} else {
@@ -2790,7 +2790,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 		Type *elem = check_type(ctx, dat->elem);
 		if (dat->tag != nullptr) {
 			GB_ASSERT(dat->tag->kind == Ast_BasicDirective);
-			String name = dat->tag->BasicDirective.name;
+			String name = dat->tag->BasicDirective.name.string;
 			if (name == "soa") {
 				*type = make_soa_struct_dynamic_array(ctx, e, dat->elem, elem);
 			} else {

+ 2 - 2
src/llvm_backend.cpp

@@ -8887,7 +8887,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
 	switch (id) {
 	case BuiltinProc_DIRECTIVE: {
 		ast_node(bd, BasicDirective, ce->proc);
-		String name = bd->name;
+		String name = bd->name.string;
 		GB_ASSERT(name == "location");
 		String procedure = p->entity->token.string;
 		TokenPos pos = ast_token(ce->proc).pos;
@@ -11515,7 +11515,7 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) {
 
 	case_ast_node(bd, BasicDirective, expr);
 		TokenPos pos = bd->token.pos;
-		GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(bd->name));
+		GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(bd->name.string));
 	case_end;
 
 	case_ast_node(i, Implicit, expr);

+ 34 - 124
src/parser.cpp

@@ -1,109 +1,4 @@
-Token ast_token(Ast *node) {
-	switch (node->kind) {
-	case Ast_Ident:          return node->Ident.token;
-	case Ast_Implicit:       return node->Implicit;
-	case Ast_Undef:          return node->Undef;
-	case Ast_BasicLit:       return node->BasicLit.token;
-	case Ast_BasicDirective: return node->BasicDirective.token;
-	case Ast_ProcGroup:      return node->ProcGroup.token;
-	case Ast_ProcLit:        return ast_token(node->ProcLit.type);
-	case Ast_CompoundLit:
-		if (node->CompoundLit.type != nullptr) {
-			return ast_token(node->CompoundLit.type);
-		}
-		return node->CompoundLit.open;
-
-	case Ast_TagExpr:       return node->TagExpr.token;
-	case Ast_BadExpr:       return node->BadExpr.begin;
-	case Ast_UnaryExpr:     return node->UnaryExpr.op;
-	case Ast_BinaryExpr:    return ast_token(node->BinaryExpr.left);
-	case Ast_ParenExpr:     return node->ParenExpr.open;
-	case Ast_CallExpr:      return ast_token(node->CallExpr.proc);
-	case Ast_SelectorExpr:
-		if (node->SelectorExpr.selector != nullptr) {
-			return ast_token(node->SelectorExpr.selector);
-		}
-		return node->SelectorExpr.token;
-	case Ast_SelectorCallExpr:
-		if (node->SelectorCallExpr.expr != nullptr) {
-			return ast_token(node->SelectorCallExpr.expr);
-		}
-		return node->SelectorCallExpr.token;
-	case Ast_ImplicitSelectorExpr:
-		if (node->ImplicitSelectorExpr.selector != nullptr) {
-			return ast_token(node->ImplicitSelectorExpr.selector);
-		}
-		return node->ImplicitSelectorExpr.token;
-	case Ast_IndexExpr:          return node->IndexExpr.open;
-	case Ast_SliceExpr:          return node->SliceExpr.open;
-	case Ast_Ellipsis:           return node->Ellipsis.token;
-	case Ast_FieldValue:         return node->FieldValue.eq;
-	case Ast_DerefExpr:          return node->DerefExpr.op;
-	case Ast_TernaryIfExpr:      return ast_token(node->TernaryIfExpr.x);
-	case Ast_TernaryWhenExpr:    return ast_token(node->TernaryWhenExpr.x);
-	case Ast_TypeAssertion:      return ast_token(node->TypeAssertion.expr);
-	case Ast_TypeCast:           return node->TypeCast.token;
-	case Ast_AutoCast:           return node->AutoCast.token;
-	case Ast_InlineAsmExpr:      return node->InlineAsmExpr.token;
-
-	case Ast_BadStmt:            return node->BadStmt.begin;
-	case Ast_EmptyStmt:          return node->EmptyStmt.token;
-	case Ast_ExprStmt:           return ast_token(node->ExprStmt.expr);
-	case Ast_TagStmt:            return node->TagStmt.token;
-	case Ast_AssignStmt:         return node->AssignStmt.op;
-	case Ast_BlockStmt:          return node->BlockStmt.open;
-	case Ast_IfStmt:             return node->IfStmt.token;
-	case Ast_WhenStmt:           return node->WhenStmt.token;
-	case Ast_ReturnStmt:         return node->ReturnStmt.token;
-	case Ast_ForStmt:            return node->ForStmt.token;
-	case Ast_RangeStmt:          return node->RangeStmt.token;
-	case Ast_UnrollRangeStmt:    return node->UnrollRangeStmt.unroll_token;
-	case Ast_CaseClause:         return node->CaseClause.token;
-	case Ast_SwitchStmt:         return node->SwitchStmt.token;
-	case Ast_TypeSwitchStmt:     return node->TypeSwitchStmt.token;
-	case Ast_DeferStmt:          return node->DeferStmt.token;
-	case Ast_BranchStmt:         return node->BranchStmt.token;
-	case Ast_UsingStmt:          return node->UsingStmt.token;
-
-	case Ast_BadDecl:            return node->BadDecl.begin;
-	case Ast_Label:              return node->Label.token;
-
-	case Ast_ValueDecl:          return ast_token(node->ValueDecl.names[0]);
-	case Ast_PackageDecl:        return node->PackageDecl.token;
-	case Ast_ImportDecl:         return node->ImportDecl.token;
-	case Ast_ForeignImportDecl:  return node->ForeignImportDecl.token;
-
-	case Ast_ForeignBlockDecl:   return node->ForeignBlockDecl.token;
-
-	case Ast_Attribute:
-		return node->Attribute.token;
-
-	case Ast_Field:
-		if (node->Field.names.count > 0) {
-			return ast_token(node->Field.names[0]);
-		}
-		return ast_token(node->Field.type);
-	case Ast_FieldList:
-		return node->FieldList.token;
-
-	case Ast_TypeidType:       return node->TypeidType.token;
-	case Ast_HelperType:       return node->HelperType.token;
-	case Ast_DistinctType:     return node->DistinctType.token;
-	case Ast_PolyType:         return node->PolyType.token;
-	case Ast_ProcType:         return node->ProcType.token;
-	case Ast_RelativeType:     return ast_token(node->RelativeType.tag);
-	case Ast_PointerType:      return node->PointerType.token;
-	case Ast_ArrayType:        return node->ArrayType.token;
-	case Ast_DynamicArrayType: return node->DynamicArrayType.token;
-	case Ast_StructType:       return node->StructType.token;
-	case Ast_UnionType:        return node->UnionType.token;
-	case Ast_EnumType:         return node->EnumType.token;
-	case Ast_BitSetType:       return node->BitSetType.token;
-	case Ast_MapType:          return node->MapType.token;
-	}
-
-	return empty_token;
-}
+#include "parser_pos.cpp"
 
 Token token_end_of_line(AstFile *f, Token tok) {
 	u8 const *start = f->tokenizer.start + tok.pos.offset;
@@ -474,12 +369,15 @@ Ast *clone_ast(Ast *node) {
 
 void error(Ast *node, char const *fmt, ...) {
 	Token token = {};
+	TokenPos end_pos = {};
 	if (node != nullptr) {
 		token = ast_token(node);
+		end_pos = ast_end_pos(node);
 	}
+
 	va_list va;
 	va_start(va, fmt);
-	error_va(token.pos, fmt, va);
+	error_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
@@ -501,16 +399,28 @@ void error_no_newline(Ast *node, char const *fmt, ...) {
 }
 
 void warning(Ast *node, char const *fmt, ...) {
+	Token token = {};
+	TokenPos end_pos = {};
+	if (node != nullptr) {
+		token = ast_token(node);
+		end_pos = ast_end_pos(node);
+	}
 	va_list va;
 	va_start(va, fmt);
-	warning_va(ast_token(node).pos, fmt, va);
+	warning_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 }
 
 void syntax_error(Ast *node, char const *fmt, ...) {
+	Token token = {};
+	TokenPos end_pos = {};
+	if (node != nullptr) {
+		token = ast_token(node);
+		end_pos = ast_end_pos(node);
+	}
 	va_list va;
 	va_start(va, fmt);
-	syntax_error_va(ast_token(node).pos, fmt, va);
+	syntax_error_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
@@ -682,7 +592,7 @@ Ast *ast_basic_lit(AstFile *f, Token basic_lit) {
 	return result;
 }
 
-Ast *ast_basic_directive(AstFile *f, Token token, String name) {
+Ast *ast_basic_directive(AstFile *f, Token token, Token name) {
 	Ast *result = alloc_ast_node(f, Ast_BasicDirective);
 	result->BasicDirective.token = token;
 	result->BasicDirective.name = name;
@@ -2042,27 +1952,27 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 		if (name.string == "type") {
 			return ast_helper_type(f, token, parse_type(f));
 		} else if (name.string == "file") {
-			return ast_basic_directive(f, token, name.string);
-		} else if (name.string == "line") { return ast_basic_directive(f, token, name.string);
-		} else if (name.string == "procedure") { return ast_basic_directive(f, token, name.string);
-		} else if (name.string == "caller_location") { return ast_basic_directive(f, token, name.string);
+			return ast_basic_directive(f, token, name);
+		} else if (name.string == "line") { return ast_basic_directive(f, token, name);
+		} else if (name.string == "procedure") { return ast_basic_directive(f, token, name);
+		} else if (name.string == "caller_location") { return ast_basic_directive(f, token, name);
 		} else if (name.string == "location") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "load") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "assert") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "defined") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "config") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "soa" || name.string == "simd") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			Ast *original_type = parse_type(f);
 			Ast *type = unparen_expr(original_type);
 			switch (type->kind) {
@@ -2074,7 +1984,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			}
 			return original_type;
 		} else if (name.string == "partial") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			Ast *original_type = parse_type(f);
 			Ast *type = unparen_expr(original_type);
 			switch (type->kind) {
@@ -2107,7 +2017,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			}
 			return operand;
 		} else if (name.string == "relative") {
-			Ast *tag = ast_basic_directive(f, token, name.string);
+			Ast *tag = ast_basic_directive(f, token, name);
 			tag = parse_call_expr(f, tag);
 			Ast *type = parse_type(f);
 			return ast_relative_type(f, tag, type);
@@ -4554,10 +4464,10 @@ Ast *parse_stmt(AstFile *f) {
 			}
 			return s;
 		} else if (tag == "assert") {
-			Ast *t = ast_basic_directive(f, hash_token, tag);
+			Ast *t = ast_basic_directive(f, hash_token, name);
 			return ast_expr_stmt(f, parse_call_expr(f, t));
 		} else if (tag == "panic") {
-			Ast *t = ast_basic_directive(f, hash_token, tag);
+			Ast *t = ast_basic_directive(f, hash_token, name);
 			return ast_expr_stmt(f, parse_call_expr(f, t));
 		} else if (name.string == "force_inline" ||
 		           name.string == "force_no_inline") {

+ 3 - 3
src/parser.hpp

@@ -286,8 +286,8 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = {
 		Token token; \
 	}) \
 	AST_KIND(BasicDirective, "basic directive", struct { \
-		Token  token; \
-		String name; \
+		Token token; \
+		Token name; \
 	}) \
 	AST_KIND(Ellipsis,       "ellipsis", struct { \
 		Token    token; \
@@ -324,7 +324,7 @@ AST_KIND(_ExprBegin,  "",  bool) \
 	AST_KIND(ImplicitSelectorExpr, "implicit selector expression",    struct { Token token; Ast *selector; }) \
 	AST_KIND(SelectorCallExpr, "selector call expression",    struct { Token token; Ast *expr, *call; bool modified_call; }) \
 	AST_KIND(IndexExpr,    "index expression",       struct { Ast *expr, *index; Token open, close; }) \
-	AST_KIND(DerefExpr,    "dereference expression", struct { Token op; Ast *expr; }) \
+	AST_KIND(DerefExpr,    "dereference expression", struct { Ast *expr; Token op; }) \
 	AST_KIND(SliceExpr,    "slice expression", struct { \
 		Ast *expr; \
 		Token open, close; \

+ 331 - 0
src/parser_pos.cpp

@@ -0,0 +1,331 @@
+Token ast_token(Ast *node) {
+	switch (node->kind) {
+	case Ast_Ident:          return node->Ident.token;
+	case Ast_Implicit:       return node->Implicit;
+	case Ast_Undef:          return node->Undef;
+	case Ast_BasicLit:       return node->BasicLit.token;
+	case Ast_BasicDirective: return node->BasicDirective.token;
+	case Ast_ProcGroup:      return node->ProcGroup.token;
+	case Ast_ProcLit:        return ast_token(node->ProcLit.type);
+	case Ast_CompoundLit:
+		if (node->CompoundLit.type != nullptr) {
+			return ast_token(node->CompoundLit.type);
+		}
+		return node->CompoundLit.open;
+
+	case Ast_TagExpr:       return node->TagExpr.token;
+	case Ast_BadExpr:       return node->BadExpr.begin;
+	case Ast_UnaryExpr:     return node->UnaryExpr.op;
+	case Ast_BinaryExpr:    return ast_token(node->BinaryExpr.left);
+	case Ast_ParenExpr:     return node->ParenExpr.open;
+	case Ast_CallExpr:      return ast_token(node->CallExpr.proc);
+	case Ast_SelectorExpr:
+		if (node->SelectorExpr.selector != nullptr) {
+			return ast_token(node->SelectorExpr.selector);
+		}
+		return node->SelectorExpr.token;
+	case Ast_SelectorCallExpr:
+		if (node->SelectorCallExpr.expr != nullptr) {
+			return ast_token(node->SelectorCallExpr.expr);
+		}
+		return node->SelectorCallExpr.token;
+	case Ast_ImplicitSelectorExpr:
+		if (node->ImplicitSelectorExpr.selector != nullptr) {
+			return ast_token(node->ImplicitSelectorExpr.selector);
+		}
+		return node->ImplicitSelectorExpr.token;
+	case Ast_IndexExpr:          return node->IndexExpr.open;
+	case Ast_SliceExpr:          return node->SliceExpr.open;
+	case Ast_Ellipsis:           return node->Ellipsis.token;
+	case Ast_FieldValue:         return node->FieldValue.eq;
+	case Ast_DerefExpr:          return node->DerefExpr.op;
+	case Ast_TernaryIfExpr:      return ast_token(node->TernaryIfExpr.x);
+	case Ast_TernaryWhenExpr:    return ast_token(node->TernaryWhenExpr.x);
+	case Ast_TypeAssertion:      return ast_token(node->TypeAssertion.expr);
+	case Ast_TypeCast:           return node->TypeCast.token;
+	case Ast_AutoCast:           return node->AutoCast.token;
+	case Ast_InlineAsmExpr:      return node->InlineAsmExpr.token;
+
+	case Ast_BadStmt:            return node->BadStmt.begin;
+	case Ast_EmptyStmt:          return node->EmptyStmt.token;
+	case Ast_ExprStmt:           return ast_token(node->ExprStmt.expr);
+	case Ast_TagStmt:            return node->TagStmt.token;
+	case Ast_AssignStmt:         return node->AssignStmt.op;
+	case Ast_BlockStmt:          return node->BlockStmt.open;
+	case Ast_IfStmt:             return node->IfStmt.token;
+	case Ast_WhenStmt:           return node->WhenStmt.token;
+	case Ast_ReturnStmt:         return node->ReturnStmt.token;
+	case Ast_ForStmt:            return node->ForStmt.token;
+	case Ast_RangeStmt:          return node->RangeStmt.token;
+	case Ast_UnrollRangeStmt:    return node->UnrollRangeStmt.unroll_token;
+	case Ast_CaseClause:         return node->CaseClause.token;
+	case Ast_SwitchStmt:         return node->SwitchStmt.token;
+	case Ast_TypeSwitchStmt:     return node->TypeSwitchStmt.token;
+	case Ast_DeferStmt:          return node->DeferStmt.token;
+	case Ast_BranchStmt:         return node->BranchStmt.token;
+	case Ast_UsingStmt:          return node->UsingStmt.token;
+
+	case Ast_BadDecl:            return node->BadDecl.begin;
+	case Ast_Label:              return node->Label.token;
+
+	case Ast_ValueDecl:          return ast_token(node->ValueDecl.names[0]);
+	case Ast_PackageDecl:        return node->PackageDecl.token;
+	case Ast_ImportDecl:         return node->ImportDecl.token;
+	case Ast_ForeignImportDecl:  return node->ForeignImportDecl.token;
+
+	case Ast_ForeignBlockDecl:   return node->ForeignBlockDecl.token;
+
+	case Ast_Attribute:
+		return node->Attribute.token;
+
+	case Ast_Field:
+		if (node->Field.names.count > 0) {
+			return ast_token(node->Field.names[0]);
+		}
+		return ast_token(node->Field.type);
+	case Ast_FieldList:
+		return node->FieldList.token;
+
+	case Ast_TypeidType:       return node->TypeidType.token;
+	case Ast_HelperType:       return node->HelperType.token;
+	case Ast_DistinctType:     return node->DistinctType.token;
+	case Ast_PolyType:         return node->PolyType.token;
+	case Ast_ProcType:         return node->ProcType.token;
+	case Ast_RelativeType:     return ast_token(node->RelativeType.tag);
+	case Ast_PointerType:      return node->PointerType.token;
+	case Ast_ArrayType:        return node->ArrayType.token;
+	case Ast_DynamicArrayType: return node->DynamicArrayType.token;
+	case Ast_StructType:       return node->StructType.token;
+	case Ast_UnionType:        return node->UnionType.token;
+	case Ast_EnumType:         return node->EnumType.token;
+	case Ast_BitSetType:       return node->BitSetType.token;
+	case Ast_MapType:          return node->MapType.token;
+	}
+
+	return empty_token;
+}
+
+TokenPos token_pos_end(Token const &token) {
+	TokenPos pos = token.pos;
+	pos.offset += cast(i32)token.string.len;
+	for (isize i = 0; i < token.string.len; i++) {
+		// TODO(bill): This assumes ASCII
+		char c = token.string[i];
+		if (c == '\n') {
+			pos.line += 1;
+			pos.column = 1;
+		} else {
+			pos.column += 1;
+		}
+	}
+	return pos;
+}
+
+Token ast_end_token(Ast *node) {
+	GB_ASSERT(node != nullptr);
+
+	switch (node->kind) {
+	case Ast_Ident:          return node->Ident.token;
+	case Ast_Implicit:       return node->Implicit;
+	case Ast_Undef:          return node->Undef;
+	case Ast_BasicLit:       return node->BasicLit.token;
+	case Ast_BasicDirective: return node->BasicDirective.token;
+	case Ast_ProcGroup:      return node->ProcGroup.close;
+	case Ast_ProcLit:
+		if (node->ProcLit.body) {
+			return ast_end_token(node->ProcLit.body);
+		}
+		return ast_end_token(node->ProcLit.type);
+	case Ast_CompoundLit:
+		return node->CompoundLit.close;
+
+	case Ast_BadExpr:       return node->BadExpr.end;
+	case Ast_TagExpr:       return ast_end_token(node->TagExpr.expr);
+	case Ast_UnaryExpr:     return ast_end_token(node->UnaryExpr.expr);
+	case Ast_BinaryExpr:    return ast_end_token(node->BinaryExpr.right);
+	case Ast_ParenExpr:     return node->ParenExpr.close;
+	case Ast_CallExpr:      return node->CallExpr.close;
+	case Ast_SelectorExpr:
+		return ast_end_token(node->SelectorExpr.selector);
+	case Ast_SelectorCallExpr:
+		return ast_end_token(node->SelectorCallExpr.call);
+	case Ast_ImplicitSelectorExpr:
+		return ast_end_token(node->SelectorExpr.selector);
+	case Ast_IndexExpr:          return node->IndexExpr.close;
+	case Ast_SliceExpr:          return node->SliceExpr.close;
+	case Ast_Ellipsis:
+		if (node->Ellipsis.expr) {
+			return ast_end_token(node->Ellipsis.expr);
+		}
+		return node->Ellipsis.token;
+	case Ast_FieldValue:         return ast_end_token(node->FieldValue.value);
+	case Ast_DerefExpr:          return node->DerefExpr.op;
+	case Ast_TernaryIfExpr:      return ast_end_token(node->TernaryIfExpr.y);
+	case Ast_TernaryWhenExpr:    return ast_end_token(node->TernaryWhenExpr.y);
+	case Ast_TypeAssertion:      return ast_end_token(node->TypeAssertion.type);
+	case Ast_TypeCast:           return ast_end_token(node->TypeCast.expr);
+	case Ast_AutoCast:           return ast_end_token(node->AutoCast.expr);
+	case Ast_InlineAsmExpr:      return node->InlineAsmExpr.close;
+
+	case Ast_BadStmt:            return node->BadStmt.end;
+	case Ast_EmptyStmt:          return node->EmptyStmt.token;
+	case Ast_ExprStmt:           return ast_end_token(node->ExprStmt.expr);
+	case Ast_TagStmt:            return ast_end_token(node->TagStmt.stmt);
+	case Ast_AssignStmt:
+		if (node->AssignStmt.rhs.count > 0) {
+			return ast_end_token(node->AssignStmt.rhs[node->AssignStmt.rhs.count-1]);
+		}
+		return node->AssignStmt.op;
+	case Ast_BlockStmt:          return node->BlockStmt.close;
+	case Ast_IfStmt:
+		if (node->IfStmt.else_stmt) {
+			return ast_end_token(node->IfStmt.else_stmt);
+		}
+		return ast_end_token(node->IfStmt.body);
+	case Ast_WhenStmt:
+		if (node->WhenStmt.else_stmt) {
+			return ast_end_token(node->WhenStmt.else_stmt);
+		}
+		return ast_end_token(node->WhenStmt.body);
+	case Ast_ReturnStmt:
+		if (node->ReturnStmt.results.count > 0) {
+			return ast_end_token(node->ReturnStmt.results[node->ReturnStmt.results.count-1]);
+		}
+		return node->ReturnStmt.token;
+	case Ast_ForStmt:            return ast_end_token(node->ForStmt.body);
+	case Ast_RangeStmt:          return ast_end_token(node->RangeStmt.body);
+	case Ast_UnrollRangeStmt:    return ast_end_token(node->UnrollRangeStmt.body);
+	case Ast_CaseClause:
+		if (node->CaseClause.stmts.count) {
+			return ast_end_token(node->CaseClause.stmts[node->CaseClause.stmts.count-1]);
+		} else if (node->CaseClause.list.count) {
+			return ast_end_token(node->CaseClause.list[node->CaseClause.list.count-1]);
+		}
+		return node->CaseClause.token;
+	case Ast_SwitchStmt:         return ast_end_token(node->SwitchStmt.body);
+	case Ast_TypeSwitchStmt:     return ast_end_token(node->TypeSwitchStmt.body);
+	case Ast_DeferStmt:          return ast_end_token(node->DeferStmt.stmt);
+	case Ast_BranchStmt:
+		if (node->BranchStmt.label) {
+			return ast_end_token(node->BranchStmt.label);
+		}
+		return node->BranchStmt.token;
+	case Ast_UsingStmt:
+		if (node->UsingStmt.list.count > 0) {
+			return ast_end_token(node->UsingStmt.list[node->UsingStmt.list.count-1]);
+		}
+		return node->UsingStmt.token;
+
+	case Ast_BadDecl:            return node->BadDecl.end;
+	case Ast_Label:
+		if (node->Label.name) {
+			return ast_end_token(node->Label.name);
+		}
+		return node->Label.token;
+
+	case Ast_ValueDecl:
+		if (node->ValueDecl.values.count > 0) {
+			return ast_end_token(node->ValueDecl.values[node->ValueDecl.values.count-1]);
+		}
+		if (node->ValueDecl.type) {
+			return ast_end_token(node->ValueDecl.type);
+		}
+		if (node->ValueDecl.names.count > 0) {
+			return ast_end_token(node->ValueDecl.names[node->ValueDecl.names.count-1]);
+		}
+		return {};
+
+	case Ast_PackageDecl:        return node->PackageDecl.name;
+	case Ast_ImportDecl:         return node->ImportDecl.relpath;
+	case Ast_ForeignImportDecl:
+		if (node->ForeignImportDecl.filepaths.count > 0) {
+			return node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1];
+		}
+		if (node->ForeignImportDecl.library_name.kind != Token_Invalid) {
+			return node->ForeignImportDecl.library_name;
+		}
+		return node->ForeignImportDecl.token;
+
+	case Ast_ForeignBlockDecl:
+		return ast_end_token(node->ForeignBlockDecl.body);
+
+	case Ast_Attribute:
+		if (node->Attribute.close.kind != Token_Invalid) {
+			return node->Attribute.close;
+		}
+		return ast_end_token(node->Attribute.elems[node->Attribute.elems.count-1]);
+
+	case Ast_Field:
+		if (node->Field.tag.kind != Token_Invalid) {
+			return node->Field.tag;
+		}
+		if (node->Field.default_value) {
+			return ast_end_token(node->Field.default_value);
+		}
+		if (node->Field.type) {
+			return ast_end_token(node->Field.type);
+		}
+		return ast_end_token(node->Field.names[node->Field.names.count-1]);
+	case Ast_FieldList:
+		if (node->FieldList.list.count > 0) {
+			return ast_end_token(node->FieldList.list[node->FieldList.list.count-1]);
+		}
+		return node->FieldList.token;
+
+	case Ast_TypeidType:
+		if (node->TypeidType.specialization) {
+			return ast_end_token(node->TypeidType.specialization);
+		}
+		return node->TypeidType.token;
+	case Ast_HelperType:       return ast_end_token(node->HelperType.type);
+	case Ast_DistinctType:     return ast_end_token(node->DistinctType.type);
+	case Ast_PolyType:
+		if (node->PolyType.specialization) {
+			return ast_end_token(node->PolyType.specialization);
+		}
+		return ast_end_token(node->PolyType.type);
+	case Ast_ProcType:
+		if (node->ProcType.results) {
+			return ast_end_token(node->ProcType.results);
+		}
+		if (node->ProcType.params) {
+			return ast_end_token(node->ProcType.params);
+		}
+		return node->ProcType.token;
+	case Ast_RelativeType:
+		return ast_end_token(node->RelativeType.type);
+	case Ast_PointerType:      return ast_end_token(node->PointerType.type);
+	case Ast_ArrayType:        return ast_end_token(node->ArrayType.elem);
+	case Ast_DynamicArrayType: return ast_end_token(node->DynamicArrayType.elem);
+	case Ast_StructType:
+		if (node->StructType.fields.count > 0) {
+			return ast_end_token(node->StructType.fields[node->StructType.fields.count-1]);
+		}
+		return node->StructType.token;
+	case Ast_UnionType:
+		if (node->UnionType.variants.count > 0) {
+			return ast_end_token(node->UnionType.variants[node->UnionType.variants.count-1]);
+		}
+		return node->UnionType.token;
+	case Ast_EnumType:
+		if (node->EnumType.fields.count > 0) {
+			return ast_end_token(node->EnumType.fields[node->EnumType.fields.count-1]);
+		}
+		if (node->EnumType.base_type) {
+			return ast_end_token(node->EnumType.base_type);
+		}
+		return node->EnumType.token;
+	case Ast_BitSetType:
+		if (node->BitSetType.underlying) {
+			return ast_end_token(node->BitSetType.underlying);
+		}
+		return ast_end_token(node->BitSetType.elem);
+	case Ast_MapType:          return ast_end_token(node->MapType.value);
+	}
+
+	return empty_token;
+}
+
+TokenPos ast_end_pos(Ast *node) {
+	return token_pos_end(ast_end_token(node));
+}

+ 42 - 22
src/tokenizer.cpp

@@ -423,7 +423,7 @@ void error_out(char const *fmt, ...) {
 }
 
 
-bool show_error_on_line(TokenPos const &pos) {
+bool show_error_on_line(TokenPos const &pos, TokenPos end) {
 	if (!show_error_line()) {
 		return false;
 	}
@@ -435,6 +435,8 @@ bool show_error_on_line(TokenPos const &pos) {
 	if (the_line != nullptr) {
 		String line = make_string(cast(u8 const *)the_line, gb_string_length(the_line));
 
+		// TODO(bill): This assumes ASCII
+
 		enum {
 			MAX_LINE_LENGTH  = 76,
 			MAX_TAB_WIDTH    = 8,
@@ -462,15 +464,33 @@ bool show_error_on_line(TokenPos const &pos) {
 		}
 		error_out("\n\t");
 
-		for (i32 i = 0; i < offset; i++) error_out(" ");
-		error_out("^\n");
-		error_out("\n");
+		for (i32 i = 0; i < offset; i++) {
+			error_out(" ");
+		}
+		error_out("^");
+		if (end.file_id == pos.file_id) {
+			if (end.line > pos.line) {
+				for (i32 i = offset; i < line.len; i++) {
+					error_out("~");
+				}
+			} else if (end.line == pos.line && end.column > pos.column) {
+				i32 length = gb_min(end.offset - pos.offset, cast(i32)(line.len-offset));
+				for (i32 i = 1; i < length-1; i++) {
+					error_out("~");
+				}
+				if (length > 1) {
+					error_out("^");
+				}
+			}
+		}
+
+		error_out("\n\n");
 		return true;
 	}
 	return false;
 }
 
-void error_va(TokenPos pos, char const *fmt, va_list va) {
+void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
@@ -481,7 +501,7 @@ void error_va(TokenPos pos, char const *fmt, va_list va) {
 		error_out("%s %s\n",
 		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
-		show_error_on_line(pos);
+		show_error_on_line(pos, end);
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
@@ -489,9 +509,9 @@ void error_va(TokenPos pos, char const *fmt, va_list va) {
 	}
 }
 
-void warning_va(TokenPos const &pos, char const *fmt, va_list va) {
+void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	if (global_warnings_as_errors()) {
-		error_va(pos, fmt, va);
+		error_va(pos, end, fmt, va);
 		return;
 	}
 	gb_mutex_lock(&global_error_collector.mutex);
@@ -505,7 +525,7 @@ void warning_va(TokenPos const &pos, char const *fmt, va_list va) {
 			error_out("%s Warning: %s\n",
 			          token_pos_to_string(pos),
 			          gb_bprintf_va(fmt, va));
-			show_error_on_line(pos);
+			show_error_on_line(pos, end);
 		}
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
@@ -537,7 +557,7 @@ void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) {
 }
 
 
-void syntax_error_va(TokenPos const &pos, char const *fmt, va_list va) {
+void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
@@ -546,7 +566,7 @@ void syntax_error_va(TokenPos const &pos, char const *fmt, va_list va) {
 		error_out("%s Syntax Error: %s\n",
 		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
-		show_error_on_line(pos);
+		show_error_on_line(pos, end);
 	} else if (pos.line == 0) {
 		error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va));
 	}
@@ -557,9 +577,9 @@ void syntax_error_va(TokenPos const &pos, char const *fmt, va_list va) {
 	}
 }
 
-void syntax_warning_va(TokenPos const &pos, char const *fmt, va_list va) {
+void syntax_warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	if (global_warnings_as_errors()) {
-		syntax_error_va(pos, fmt, va);
+		syntax_error_va(pos, end, fmt, va);
 		return;
 	}
 	gb_mutex_lock(&global_error_collector.mutex);
@@ -571,7 +591,7 @@ void syntax_warning_va(TokenPos const &pos, char const *fmt, va_list va) {
 			error_out("%s Syntax Warning: %s\n",
 			          token_pos_to_string(pos),
 			          gb_bprintf_va(fmt, va));
-			show_error_on_line(pos);
+			show_error_on_line(pos, end);
 		} else if (pos.line == 0) {
 			error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
 		}
@@ -584,14 +604,14 @@ void syntax_warning_va(TokenPos const &pos, char const *fmt, va_list va) {
 void warning(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	warning_va(token.pos, fmt, va);
+	warning_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
 void error(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	error_va(token.pos, fmt, va);
+	error_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
@@ -600,7 +620,7 @@ void error(TokenPos pos, char const *fmt, ...) {
 	va_start(va, fmt);
 	Token token = {};
 	token.pos = pos;
-	error_va(pos, fmt, va);
+	error_va(pos, {}, fmt, va);
 	va_end(va);
 }
 
@@ -615,21 +635,21 @@ void error_line(char const *fmt, ...) {
 void syntax_error(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	syntax_error_va(token.pos, fmt, va);
+	syntax_error_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
 void syntax_error(TokenPos pos, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	syntax_error_va(pos, fmt, va);
+	syntax_error_va(pos, {}, fmt, va);
 	va_end(va);
 }
 
 void syntax_warning(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	syntax_warning_va(token.pos, fmt, va);
+	syntax_warning_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
@@ -748,7 +768,7 @@ void tokenizer_err(Tokenizer *t, char const *msg, ...) {
 	pos.offset = cast(i32)(t->read_curr - t->start);
 
 	va_start(va, msg);
-	syntax_error_va(pos, msg, va);
+	syntax_error_va(pos, {}, msg, va);
 	va_end(va);
 
 	t->error_count++;
@@ -762,7 +782,7 @@ void tokenizer_err(Tokenizer *t, TokenPos const &pos, char const *msg, ...) {
 	}
 
 	va_start(va, msg);
-	syntax_error_va(pos, msg, va);
+	syntax_error_va(pos, {}, msg, va);
 	va_end(va);
 
 	t->error_count++;