Quellcode durchsuchen

Merge branch 'master' of https://github.com/odin-lang/Odin

jockus vor 4 Jahren
Ursprung
Commit
4455ba5b65
66 geänderte Dateien mit 5327 neuen und 2418 gelöschten Zeilen
  1. 2 0
      .github/ISSUE_TEMPLATE/feature_request.md
  2. 1 1
      LICENSE
  3. 23 18
      Makefile
  4. 8 0
      core/bytes/bytes.odin
  5. 3 3
      core/fmt/fmt.odin
  6. 8 0
      core/intrinsics/intrinsics.odin
  7. 2 2
      core/math/rand/rand.odin
  8. 7 11
      core/odin/ast/ast.odin
  9. 41 0
      core/odin/format/format.odin
  10. 1 1
      core/odin/parser/parse_files.odin
  11. 79 50
      core/odin/parser/parser.odin
  12. 924 0
      core/odin/printer/printer.odin
  13. 1540 0
      core/odin/printer/visit.odin
  14. 2 0
      core/odin/tokenizer/token.odin
  15. 7 4
      core/odin/tokenizer/tokenizer.odin
  16. 2 2
      core/os/file_windows.odin
  17. 16 45
      core/os/os2/errors.odin
  18. 7 16
      core/os/os2/file_stream.odin
  19. 1 0
      core/os/os2/file_util.odin
  20. 10 10
      core/os/os2/file_windows.odin
  21. 1 1
      core/os/os2/pipe_windows.odin
  22. 3 3
      core/os/os2/stat_windows.odin
  23. 2 2
      core/os/os2/temp_file_windows.odin
  24. 1 1
      core/os/os_freebsd.odin
  25. 3 3
      core/os/os_linux.odin
  26. 41 2
      core/runtime/default_allocators.odin
  27. 12 33
      core/runtime/internal.odin
  28. 8 0
      core/runtime/os_specific.odin
  29. 2 2
      core/runtime/os_specific_any.odin
  30. 2 2
      core/runtime/os_specific_freestanding.odin
  31. 41 71
      core/runtime/os_specific_windows.odin
  32. 9 5
      core/runtime/procs_essence.odin
  33. 1 1
      core/strings/builder.odin
  34. 8 0
      core/strings/strings.odin
  35. 2 0
      core/sync/sync2/atomic.odin
  36. 0 886
      core/sync/sync2/channel.odin
  37. 0 17
      core/sync/sync2/channel_unix.odin
  38. 0 34
      core/sync/sync2/channel_windows.odin
  39. 2 2
      core/sync/sync2/primitives.odin
  40. 243 159
      core/sync/sync2/primitives_atomic.odin
  41. 1 1
      core/sync/sync2/primitives_pthreads.odin
  42. 0 10
      core/unicode/tables.odin
  43. 29 7
      examples/demo/demo.odin
  44. 5 5
      examples/demo_insert_semicolon/demo.odin
  45. 5 0
      src/build_settings.cpp
  46. 164 2
      src/check_builtin.cpp
  47. 18 14
      src/check_decl.cpp
  48. 96 96
      src/check_expr.cpp
  49. 70 55
      src/check_stmt.cpp
  50. 45 43
      src/check_type.cpp
  51. 10 200
      src/checker.cpp
  52. 1 1
      src/checker.hpp
  53. 16 0
      src/checker_builtin_procs.hpp
  54. 1 0
      src/entity.cpp
  55. 77 54
      src/llvm_abi.cpp
  56. 579 302
      src/llvm_backend.cpp
  57. 9 3
      src/llvm_backend.hpp
  58. 127 4
      src/llvm_backend_opt.cpp
  59. 7 1
      src/main.cpp
  60. 126 143
      src/parser.cpp
  61. 25 20
      src/parser.hpp
  62. 340 0
      src/parser_pos.cpp
  63. 170 51
      src/tokenizer.cpp
  64. 0 19
      src/types.cpp
  65. 216 0
      tools/odinfmt/flag/flag.odin
  66. 125 0
      tools/odinfmt/main.odin

+ 2 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -7,6 +7,8 @@ assignees: ''
 
 ---
 
+# PLEASE POST THIS IN THE DISCUSSION TAB UNDER "PROPOSALS" OR "IDEAS/REQUESTS"
+
 **Is your feature request related to a problem? Please describe.**
 A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
 

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2016-2020 Ginger Bill. All rights reserved.
+Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

+ 23 - 18
Makefile

@@ -8,26 +8,31 @@ CC=clang
 OS=$(shell uname)
 
 ifeq ($(OS), Darwin)
-    LLVM_CONFIG=llvm-config
-
-    LDFLAGS:=$(LDFLAGS) -liconv
-    CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
-    LDFLAGS:=$(LDFLAGS) -lLLVM-C
+	LLVM_CONFIG=llvm-config
+	ifneq ($(shell llvm-config --version | grep '^11\.'),)
+		LLVM_CONFIG=llvm-config
+	else
+		$(error "Requirement: llvm-config must be version 11")
+	endif
+
+	LDFLAGS:=$(LDFLAGS) -liconv
+	CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
+	LDFLAGS:=$(LDFLAGS) -lLLVM-C
 endif
 ifeq ($(OS), Linux)
-    LLVM_CONFIG=llvm-config-11
-    ifneq ($(shell which llvm-config-11 2>/dev/null),)
-        LLVM_CONFIG=llvm-config-11
-    else
-            ifneq ($(shell llvm-config --version | grep '^11\.'),)
-                    LLVM_CONFIG=llvm-config
-            else
-                    $(error "Requirement: llvm-config must be version 11")
-            endif
-    endif
-
-    CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
-    LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
+	LLVM_CONFIG=llvm-config-11
+	ifneq ($(shell which llvm-config-11 2>/dev/null),)
+		LLVM_CONFIG=llvm-config-11
+	else
+		ifneq ($(shell llvm-config --version | grep '^11\.'),)
+			LLVM_CONFIG=llvm-config
+		else
+			$(error "Requirement: llvm-config must be version 11")
+		endif
+	endif
+
+	CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
+	LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
 endif
 
 all: debug demo

+ 8 - 0
core/bytes/bytes.odin

@@ -526,6 +526,14 @@ replace :: proc(s, old, new: []byte, n: int, allocator := context.allocator) ->
 	return;
 }
 
+remove :: proc(s, key: []byte, n: int, allocator := context.allocator) -> (output: []byte, was_allocation: bool) {
+	return replace(s, key, {}, n, allocator);
+}
+
+remove_all :: proc(s, key: []byte, allocator := context.allocator) -> (output: []byte, was_allocation: bool) {
+	return remove(s, key, -1, allocator);
+}
+
 @(private) _ascii_space := [256]u8{'\t' = 1, '\n' = 1, '\v' = 1, '\f' = 1, '\r' = 1, ' ' = 1};
 
 

+ 3 - 3
core/fmt/fmt.odin

@@ -641,9 +641,9 @@ fmt_write_padding :: proc(fi: ^Info, width: int) {
 		return;
 	}
 
-	pad_byte: byte = '0';
-	if fi.space {
-		pad_byte = ' ';
+	pad_byte: byte = ' ';
+	if !fi.space {
+		pad_byte = '0';
 	}
 
 	for i := 0; i < width; i += 1 {

+ 8 - 0
core/intrinsics/intrinsics.odin

@@ -31,6 +31,13 @@ overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
 overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
 overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
 
+sqrt :: proc(x: $T) -> T where type_is_float(T) ---
+
+mem_copy                 :: proc(dst, src: rawptr, len: int) ---
+mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
+mem_zero                 :: proc(ptr: rawptr, len: int) ---
+
+
 fixed_point_mul     :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
 fixed_point_div     :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
 fixed_point_mul_sat :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
@@ -159,6 +166,7 @@ type_is_simd_vector      :: proc($T: typeid) -> bool ---
 type_has_nil :: proc($T: typeid) -> bool ---
 
 type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
+type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
 
 type_has_field :: proc($T: typeid, $name: string) -> bool ---
 

+ 2 - 2
core/math/rand/rand.odin

@@ -6,9 +6,9 @@ Rand :: struct {
 }
 
 
-@(private, static)
+@(private)
 _GLOBAL_SEED_DATA := 1234567890;
-@(private, static)
+@(private)
 global_rand := create(u64(uintptr(&_GLOBAL_SEED_DATA)));
 
 set_global_seed :: proc(seed: u64) {

+ 7 - 11
core/odin/ast/ast.odin

@@ -16,16 +16,12 @@ Proc_Inlining :: enum u32 {
 	No_Inline = 2,
 }
 
-Proc_Calling_Convention :: enum i32 {
-	Invalid = 0,
-	Odin,
-	Contextless,
-	C_Decl,
-	Std_Call,
-	Fast_Call,
-	None,
-
-	Foreign_Block_Default = -1,
+Proc_Calling_Convention_Extra :: enum i32 {
+	Foreign_Block_Default,
+}
+Proc_Calling_Convention :: union {
+	string,
+	Proc_Calling_Convention_Extra,
 }
 
 Node_State_Flag :: enum {
@@ -69,7 +65,7 @@ File :: struct {
 	pkg: ^Package,
 
 	fullpath: string,
-	src:      []byte,
+	src:      string,
 
 	docs: ^Comment_Group,
 

+ 41 - 0
core/odin/format/format.odin

@@ -0,0 +1,41 @@
+package odin_format
+
+import "core:odin/printer"
+import "core:odin/parser"
+import "core:odin/ast"
+
+default_style := printer.default_style;
+
+simplify :: proc(file: ^ast.File) {
+
+}
+
+format :: proc(filepath: string, source: string, config: printer.Config, parser_flags := parser.Flags{}, allocator := context.allocator) -> (string, bool) {
+	config := config;
+
+	pkg := ast.Package {
+		kind = .Normal,
+	};
+
+	file := ast.File {
+		pkg = &pkg,
+		src = source,
+		fullpath = filepath,
+	};
+
+	config.newline_limit      = clamp(config.newline_limit, 0, 16);
+	config.spaces             = clamp(config.spaces, 1, 16);
+	config.align_length_break = clamp(config.align_length_break, 0, 64);
+
+	p := parser.default_parser(parser_flags);
+
+	ok := parser.parse_file(&p, &file);
+
+	if !ok || file.syntax_error_count > 0  {
+		return {}, false;
+	}
+
+	prnt := printer.make_printer(config, allocator);
+
+	return printer.print(&prnt, &file), true;
+}

+ 1 - 1
core/odin/parser/parse_files.odin

@@ -39,7 +39,7 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
 		}
 		file := ast.new(ast.File, NO_POS, NO_POS);
 		file.pkg = pkg;
-		file.src = src;
+		file.src = string(src);
 		file.fullpath = fullpath;
 		pkg.files[fullpath] = file;
 	}

+ 79 - 50
core/odin/parser/parser.odin

@@ -8,10 +8,21 @@ import "core:fmt"
 Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any);
 Error_Handler   :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any);
 
+Flag :: enum u32 {
+	Optional_Semicolons,
+}
+
+Flags :: distinct bit_set[Flag; u32];
+
+
 Parser :: struct {
 	file: ^ast.File,
 	tok: tokenizer.Tokenizer,
 
+	// If .Optional_Semicolons is true, semicolons are completely as statement terminators
+	// different to .Insert_Semicolon in tok.flags
+	flags: Flags,
+
 	warn: Warning_Handler,
 	err:  Error_Handler,
 
@@ -100,8 +111,9 @@ end_pos :: proc(tok: tokenizer.Token) -> tokenizer.Pos {
 	return pos;
 }
 
-default_parser :: proc() -> Parser {
+default_parser :: proc(flags := Flags{}) -> Parser {
 	return Parser {
+		flags = flags,
 		err  = default_error_handler,
 		warn = default_warning_handler,
 	};
@@ -128,6 +140,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
 		p.line_comment     = nil;
 	}
 
+	if .Optional_Semicolons in p.flags {
+		p.tok.flags += {.Insert_Semicolon};
+	}
+
 	p.file = file;
 	tokenizer.init(&p.tok, file.src, file.fullpath, p.err);
 	if p.tok.ch <= 0 {
@@ -400,6 +416,11 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	if node == nil {
 		return false;
 	}
+
+	if .Optional_Semicolons in p.flags {
+		return true;
+	}
+
 	switch n in node.derived {
 	case ast.Empty_Stmt, ast.Block_Stmt:
 		return true;
@@ -439,14 +460,34 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	return false;
 }
 
+expect_semicolon_newline_error :: proc(p: ^Parser, token: tokenizer.Token, s: ^ast.Node) {
+	if .Optional_Semicolons not_in p.flags && .Insert_Semicolon in p.tok.flags && token.text == "\n" {
+		#partial switch token.kind {
+		case .Close_Brace:
+		case .Close_Paren:
+		case .Else:
+			return;
+		}
+		if is_semicolon_optional_for_node(p, s) {
+			return;
+		}
+
+		tok := token;
+		tok.pos.column -= 1;
+		error(p, tok.pos, "expected ';', got newline");
+	}
+}
+
 
 expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	if allow_token(p, .Semicolon) {
+		expect_semicolon_newline_error(p, p.prev_tok, node);
 		return true;
 	}
 
 	prev := p.prev_tok;
 	if prev.kind == .Semicolon {
+		expect_semicolon_newline_error(p, p.prev_tok, node);
 		return true;
 	}
 
@@ -615,7 +656,7 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
 		cond = parse_expr(p, false);
 	} else {
 		init = parse_simple_stmt(p, nil);
-		if allow_token(p, .Semicolon) {
+		if parse_control_statement_semicolon_separator(p) {
 			cond = parse_expr(p, false);
 		} else {
 			cond = convert_stmt_to_expr(p, init, "boolean expression");
@@ -668,6 +709,18 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
 	return if_stmt;
 }
 
+parse_control_statement_semicolon_separator :: proc(p: ^Parser) -> bool {
+	tok := peek_token(p);
+	if tok.kind != .Open_Brace {
+		return allow_token(p, .Semicolon);
+	}
+	if tok.text == ";" {
+		return allow_token(p, .Semicolon);
+	}
+	return false;
+
+}
+
 parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 	if p.curr_proc == nil {
 		error(p, p.curr_tok.pos, "you cannot use a for statement in the file scope");
@@ -716,7 +769,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 			}
 		}
 
-		if !is_range && allow_token(p, .Semicolon) {
+		if !is_range && parse_control_statement_semicolon_separator(p) {
 			init = cond;
 			cond = nil;
 			if p.curr_tok.kind != .Semicolon {
@@ -820,7 +873,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 			tag = parse_simple_stmt(p, {Stmt_Allow_Flag.In});
 			if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In {
 				is_type_switch = true;
-			} else if allow_token(p, .Semicolon) {
+			} else if parse_control_statement_semicolon_separator(p) {
 				init = tag;
 				tag = nil;
 				if p.curr_tok.kind != .Open_Brace {
@@ -831,6 +884,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 	}
 
 
+	skip_possible_newline(p);
 	open := expect_token(p, .Open_Brace);
 
 	for p.curr_tok.kind == .Case {
@@ -958,6 +1012,7 @@ parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Bl
 	defer p.in_foreign_block = prev_in_foreign_block;
 	p.in_foreign_block = true;
 
+	skip_possible_newline_for_literal(p);
 	open := expect_token(p, .Open_Brace);
 	for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
 		decl := parse_foreign_block_decl(p);
@@ -1287,7 +1342,7 @@ token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int {
 	#partial switch kind {
 	case .Question, .If, .When:
 		return 1;
-	case .Ellipsis, .Range_Half:
+	case .Ellipsis, .Range_Half, .Range_Full:
 		if !p.allow_range {
 			return 0;
 		}
@@ -1884,24 +1939,12 @@ parse_results :: proc(p: ^Parser) -> (list: ^ast.Field_List, diverging: bool) {
 
 string_to_calling_convention :: proc(s: string) -> ast.Proc_Calling_Convention {
 	if s[0] != '"' && s[0] != '`' {
-		return .Invalid;
+		return nil;
 	}
-	switch s[1:len(s)-1] {
-	case "odin":
-		return .Odin;
-	case "contextless":
-		return .Contextless;
-	case "cdecl", "c":
-		return .C_Decl;
-	case "stdcall", "std":
-		return .Std_Call;
-	case "fast", "fastcall":
-		return .Fast_Call;
-
-	case "none":
-		return .None;
+	if len(s) == 2 {
+		return nil;
 	}
-	return .Invalid;
+	return s;
 }
 
 parse_proc_tags :: proc(p: ^Parser) -> (tags: ast.Proc_Tags) {
@@ -1926,21 +1969,17 @@ parse_proc_tags :: proc(p: ^Parser) -> (tags: ast.Proc_Tags) {
 }
 
 parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
-	cc := ast.Proc_Calling_Convention.Invalid;
+	cc: ast.Proc_Calling_Convention;
 	if p.curr_tok.kind == .String {
 		str := expect_token(p, .String);
 		cc = string_to_calling_convention(str.text);
-		if cc == ast.Proc_Calling_Convention.Invalid {
+		if cc == nil {
 			error(p, str.pos, "unknown calling convention '%s'", str.text);
 		}
 	}
 
-	if cc == ast.Proc_Calling_Convention.Invalid {
-		if p.in_foreign_block {
-			cc = ast.Proc_Calling_Convention.Foreign_Block_Default;
-		} else {
-			cc = ast.Proc_Calling_Convention.Odin;
-		}
+	if cc == nil && p.in_foreign_block {
+		cc = .Foreign_Block_Default;
 	}
 
 	expect_token(p, .Open_Paren);
@@ -1976,23 +2015,6 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
 	return pt;
 }
 
-check_poly_params_for_type :: proc(p: ^Parser, poly_params: ^ast.Field_List, tok: tokenizer.Token) {
-	if poly_params == nil {
-		return;
-	}
-	for field in poly_params.list {
-		for name in field.names {
-			if name == nil {
-				continue;
-			}
-			if _, ok := name.derived.(ast.Poly_Type); ok {
-				error(p, name.pos, "polymorphic names are not needed for %s parameters", tok.text);
-				return;
-			}
-		}
-	}
-}
-
 parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr {
 	expr := parse_unary_expr(p, lhs);
 
@@ -2224,6 +2246,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			p.expr_level = -1;
 			where_clauses = parse_rhs_expr_list(p);
 			p.expr_level = prev_level;
+			tags = parse_proc_tags(p);
 		}
 		if p.allow_type && p.expr_level < 0 {
 			if where_token.kind != .Invalid {
@@ -2233,6 +2256,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		}
 		body: ^ast.Stmt;
 
+		skip_possible_newline_for_literal(p);
+
 		if allow_token(p, .Undef) {
 			body = nil;
 			if where_token.kind != .Invalid {
@@ -2358,7 +2383,6 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 				poly_params = nil;
 			}
 			expect_token_after(p, .Close_Paren, "parameter list");
-			check_poly_params_for_type(p, poly_params, tok);
 		}
 
 		prev_level := p.expr_level;
@@ -2405,6 +2429,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			p.expr_level = where_prev_level;
 		}
 
+		skip_possible_newline_for_literal(p);
 		expect_token(p, .Open_Brace);
 		fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct);
 		close := expect_token(p, .Close_Brace);
@@ -2434,7 +2459,6 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 				poly_params = nil;
 			}
 			expect_token_after(p, .Close_Paren, "parameter list");
-			check_poly_params_for_type(p, poly_params, tok);
 		}
 
 		prev_level := p.expr_level;
@@ -2473,6 +2497,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 
 		variants: [dynamic]^ast.Expr;
 
+		skip_possible_newline_for_literal(p);
 		expect_token_after(p, .Open_Brace, "union");
 
 		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
@@ -2503,6 +2528,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		if p.curr_tok.kind != .Open_Brace {
 			base_type = parse_type(p);
 		}
+
+		skip_possible_newline_for_literal(p);
 		open := expect_token(p, .Open_Brace);
 		fields := parse_elem_list(p);
 		close := expect_token(p, .Close_Brace);
@@ -2601,6 +2628,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			}
 		}
 
+		skip_possible_newline_for_literal(p);
 		open := expect_token(p, .Open_Brace);
 		asm_string := parse_expr(p, false);
 		expect_token(p, .Comma);
@@ -2811,7 +2839,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 			open := expect_token(p, .Open_Bracket);
 
 			#partial switch p.curr_tok.kind {
-			case .Colon, .Ellipsis, .Range_Half:
+			case .Colon, .Ellipsis, .Range_Half, .Range_Full:
 				// NOTE(bill): Do not err yet
 				break;
 			case:
@@ -2819,7 +2847,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 			}
 
 			#partial switch p.curr_tok.kind {
-			case .Ellipsis, .Range_Half:
+			case .Ellipsis, .Range_Half, .Range_Full:
 				error(p, p.curr_tok.pos, "expected a colon, not a range");
 				fallthrough;
 			case .Colon:
@@ -3150,6 +3178,7 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
 					case ast.For_Stmt:         n.label = label;
 					case ast.Switch_Stmt:      n.label = label;
 					case ast.Type_Switch_Stmt: n.label = label;
+					case ast.Range_Stmt:	   n.label = label;
 					}
 				}
 

+ 924 - 0
core/odin/printer/printer.odin

@@ -0,0 +1,924 @@
+package odin_printer
+
+import "core:odin/ast"
+import "core:odin/tokenizer"
+import "core:strings"
+import "core:runtime"
+import "core:fmt"
+import "core:unicode/utf8"
+import "core:mem"
+
+Type_Enum :: enum {Line_Comment, Value_Decl, Switch_Stmt, Struct, Assign, Call, Enum, If, For, Proc_Lit};
+
+Line_Type :: bit_set[Type_Enum];
+
+/*
+	Represents an unwrapped line
+*/
+Line :: struct {
+	format_tokens: [dynamic]Format_Token,
+	finalized:     bool,
+	used:          bool,
+	depth:         int,
+	types:         Line_Type, //for performance, so you don't have to verify what types are in it by going through the tokens - might give problems when adding linebreaking
+}
+
+/*
+	Represents a singular token in a unwrapped line
+*/
+Format_Token :: struct {
+	kind:            tokenizer.Token_Kind,
+	text:            string,
+	type:            Type_Enum,
+	spaces_before:   int,
+	parameter_count: int,
+}
+
+Printer :: struct {
+	string_builder:       strings.Builder,
+	config:               Config,
+	depth:                int, //the identation depth
+	comments:             [dynamic]^ast.Comment_Group,
+	latest_comment_index: int,
+	allocator:            mem.Allocator,
+	file:                 ^ast.File,
+	source_position:      tokenizer.Pos,
+	last_source_position: tokenizer.Pos,
+	lines:                [dynamic]Line, //need to look into a better data structure, one that can handle inserting lines rather than appending
+	skip_semicolon:       bool,
+	current_line:         ^Line,
+	current_line_index:   int,
+	last_line_index:      int,
+	last_token:           ^Format_Token,
+	merge_next_token:     bool,
+	space_next_token:     bool,
+	debug:                bool,
+}
+
+Config :: struct {
+	spaces:               int,  //Spaces per indentation
+	newline_limit:        int,  //The limit of newlines between statements and declarations.
+	tabs:                 bool, //Enable or disable tabs
+	convert_do:           bool, //Convert all do statements to brace blocks
+	semicolons:           bool, //Enable semicolons
+	split_multiple_stmts: bool,
+	align_switch:         bool,
+	brace_style:          Brace_Style,
+	align_assignments:    bool,
+	align_structs:        bool,
+	align_style:          Alignment_Style,
+	align_enums:          bool,
+	align_length_break:   int,
+	indent_cases:         bool,
+	newline_style:        Newline_Style,
+}
+
+Brace_Style :: enum {
+	_1TBS,
+	Allman,
+	Stroustrup,
+	K_And_R,
+}
+
+Block_Type :: enum {
+	None,
+	If_Stmt,
+	Proc,
+	Generic,
+	Comp_Lit,
+	Switch_Stmt,
+}
+
+Alignment_Style :: enum {
+	Align_On_Type_And_Equals,
+	Align_On_Colon_And_Equals,
+}
+
+Newline_Style :: enum {
+	CRLF,
+	LF,
+}
+
+default_style := Config {
+	spaces               = 4,
+	newline_limit        = 2,
+	convert_do           = false,
+	semicolons           = true,
+	tabs                 = true,
+	brace_style          = ._1TBS,
+	split_multiple_stmts = true,
+	align_assignments    = true,
+	align_style          = .Align_On_Type_And_Equals,
+	indent_cases         = false,
+	align_switch         = true,
+	align_structs        = true,
+	align_enums          = true,
+	newline_style        = .CRLF,
+	align_length_break   = 9,
+};
+
+make_printer :: proc(config: Config, allocator := context.allocator) -> Printer {
+	return {
+		config = config,
+		allocator = allocator,
+		debug = false,
+	};
+}
+
+print :: proc(p: ^Printer, file: ^ast.File) -> string {
+	p.comments = file.comments;
+
+	if len(file.decls) > 0 {
+		p.lines = make([dynamic]Line, 0, (file.decls[len(file.decls) - 1].end.line - file.decls[0].pos.line) * 2, context.temp_allocator);
+	}
+
+	set_source_position(p, file.pkg_token.pos);
+
+	p.last_source_position.line = 1;
+
+	set_line(p, 0);
+
+	push_generic_token(p, .Package, 0);
+	push_ident_token(p, file.pkg_name, 1);
+
+	for decl in file.decls {
+		visit_decl(p, cast(^ast.Decl)decl);
+	}
+
+	if len(p.comments) > 0 {
+		infinite := p.comments[len(p.comments) - 1].end;
+		infinite.offset = 9999999;
+		push_comments(p, infinite);
+	}
+
+	fix_lines(p);
+
+	builder := strings.make_builder(0, mem.megabytes(5), p.allocator);
+
+	last_line := 0;
+
+	newline: string;
+
+	if p.config.newline_style == .LF {
+		newline = "\n";
+	} else {
+		newline = "\r\n";
+	}
+
+	for line, line_index in p.lines {
+		diff_line := line_index - last_line;
+
+		for i := 0; i < diff_line; i += 1 {
+			strings.write_string(&builder, newline);
+		}
+
+		if p.config.tabs {
+			for i := 0; i < line.depth; i += 1 {
+				strings.write_byte(&builder, '\t');
+			}
+		} else {
+			for i := 0; i < line.depth * p.config.spaces; i += 1 {
+				strings.write_byte(&builder, ' ');
+			}
+		}
+
+		if p.debug {
+			strings.write_string(&builder, fmt.tprintf("line %v: ", line_index));
+		}
+
+		for format_token in line.format_tokens {
+
+			for i := 0; i < format_token.spaces_before; i += 1 {
+				strings.write_byte(&builder, ' ');
+			}
+
+			strings.write_string(&builder, format_token.text);
+		}
+
+		last_line = line_index;
+	}
+
+	strings.write_string(&builder, newline);
+
+	return strings.to_string(builder);
+}
+
+fix_lines :: proc(p: ^Printer) {
+	align_var_decls(p);
+	format_generic(p);
+	align_comments(p); //align them last since they rely on the other alignments
+}
+
+format_value_decl :: proc(p: ^Printer, index: int) {
+
+	eq_found := false;
+	eq_token: Format_Token;
+	eq_line:  int;
+	largest := 0;
+
+	found_eq: for line, line_index in p.lines[index:] {
+		for format_token in line.format_tokens {
+
+			largest += len(format_token.text) + format_token.spaces_before;
+
+			if format_token.kind == .Eq {
+				eq_token = format_token;
+				eq_line = line_index + index;
+				eq_found = true;
+				break found_eq;
+			}
+		}
+	}
+
+	if !eq_found {
+		return;
+	}
+
+	align_next := false;
+
+	//check to see if there is a binary operator in the last token(this is guaranteed by the ast visit), otherwise it's not multilined
+	for line, line_index in p.lines[eq_line:] {
+
+		if len(line.format_tokens) == 0 {
+			break;
+		}
+
+		if align_next {
+			line.format_tokens[0].spaces_before = largest + 1;
+			align_next = false;
+		}
+
+		kind := find_last_token(line.format_tokens).kind;
+
+		if tokenizer.Token_Kind.B_Operator_Begin < kind && kind <= tokenizer.Token_Kind.Cmp_Or {
+			align_next = true;
+		}
+
+		if !align_next {
+			break;
+		}
+	}
+}
+
+find_last_token :: proc(format_tokens: [dynamic]Format_Token) -> Format_Token {
+
+	for i := len(format_tokens) - 1; i >= 0; i -= 1 {
+
+		if format_tokens[i].kind != .Comment {
+			return format_tokens[i];
+		}
+	}
+
+	panic("not possible");
+}
+
+format_assignment :: proc(p: ^Printer, index: int) {
+}
+
+format_call :: proc(p: ^Printer, line_index: int, format_index: int) {
+
+	paren_found := false;
+	paren_token:       Format_Token;
+	paren_line:        int;
+	paren_token_index: int;
+	largest := 0;
+
+	found_paren: for line, i in p.lines[line_index:] {
+		for format_token, j in line.format_tokens {
+
+			largest += len(format_token.text) + format_token.spaces_before;
+
+			if i == 0 && j < format_index {
+				continue;
+			}
+
+			if format_token.kind == .Open_Paren && format_token.type == .Call {
+				paren_token = format_token;
+				paren_line = line_index + i;
+				paren_found = true;
+				paren_token_index = j;
+				break found_paren;
+			}
+		}
+	}
+
+	if !paren_found {
+		panic("Should not be possible");
+	}
+
+	paren_count := 1;
+	done        := false;
+
+	for line, line_index in p.lines[paren_line:] {
+
+		if len(line.format_tokens) == 0 {
+			continue;
+		}
+
+		for format_token, i in line.format_tokens {
+
+			if format_token.kind == .Comment {
+				continue;
+			}
+
+			if line_index == 0 && i <= paren_token_index {
+				continue;
+			}
+
+			if format_token.kind == .Open_Paren {
+				paren_count += 1;
+			} else if format_token.kind == .Close_Paren {
+				paren_count -= 1;
+			}
+
+			if paren_count == 0 {
+				done = true;
+			}
+		}
+
+		if line_index != 0 {
+			line.format_tokens[0].spaces_before = largest;
+		}
+
+		if done {
+			return;
+		}
+	}
+}
+
+format_keyword_to_brace :: proc(p: ^Printer, line_index: int, format_index: int, keyword: tokenizer.Token_Kind) {
+
+	keyword_found := false;
+	keyword_token: Format_Token;
+	keyword_line:  int;
+
+	largest := 0;
+	brace_count := 0;
+	done        := false;
+
+	found_keyword: for line, i in p.lines[line_index:] {
+		for format_token in line.format_tokens {
+
+			largest += len(format_token.text) + format_token.spaces_before;
+
+			if format_token.kind == keyword {
+				keyword_token = format_token;
+				keyword_line = line_index + i;
+				keyword_found = true;
+				break found_keyword;
+			}
+		}
+	}
+
+	if !keyword_found {
+		panic("Should not be possible");
+	}
+
+	for line, line_index in p.lines[keyword_line:] {
+
+		if len(line.format_tokens) == 0 {
+			continue;
+		}
+
+		for format_token, i in line.format_tokens {
+
+			if format_token.kind == .Comment {
+				break;
+			} else if format_token.kind == .Undef {
+				return;
+			}
+
+			if line_index == 0 && i <= format_index {
+				continue;
+			}
+
+			if format_token.kind == .Open_Brace {
+				brace_count += 1;
+			} else if format_token.kind == .Close_Brace {
+				brace_count -= 1;
+			}
+
+			if brace_count == 1 {
+				done = true;
+			}
+		}
+
+		if line_index != 0 {
+			line.format_tokens[0].spaces_before = largest + 1;
+		}
+
+		if done {
+			return;
+		}
+	}
+}
+
+format_generic :: proc(p: ^Printer) {
+	next_struct_line := 0;
+
+	for line, line_index in p.lines {
+
+		if len(line.format_tokens) <= 0 {
+			continue;
+		}
+
+		for format_token, token_index in line.format_tokens {
+			#partial switch format_token.kind {
+			case .For, .If, .When, .Switch:
+				format_keyword_to_brace(p, line_index, token_index, format_token.kind);
+			case .Proc:
+				if format_token.type == .Proc_Lit {
+					format_keyword_to_brace(p, line_index, token_index, format_token.kind);
+				}
+			case:
+				if format_token.type == .Call {
+					format_call(p, line_index, token_index);
+				}
+			}
+		}
+
+		if .Switch_Stmt in line.types && p.config.align_switch {
+			align_switch_stmt(p, line_index);
+		}
+
+		if .Enum in line.types && p.config.align_enums {
+			align_enum(p, line_index);
+		}
+
+		if .Struct in line.types && p.config.align_structs && next_struct_line <= 0 {
+			next_struct_line = align_struct(p, line_index);
+		}
+
+		if .Value_Decl in line.types {
+			format_value_decl(p, line_index);
+		}
+
+		if .Assign in line.types {
+			format_assignment(p, line_index);
+		}
+
+		next_struct_line -= 1;
+	}
+}
+
+align_var_decls :: proc(p: ^Printer) {
+
+	current_line:        int;
+	current_typed:       bool;
+	current_not_mutable: bool;
+
+	largest_lhs := 0;
+	largest_rhs := 0;
+
+	TokenAndLength :: struct {
+		format_token: ^Format_Token,
+		length:       int,
+	};
+
+	colon_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator);
+	type_tokens  := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator);
+	equal_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator);
+
+	for line, line_index in p.lines {
+
+		//It is only possible to align value decls that are one one line, otherwise just ignore them
+		if .Value_Decl not_in line.types {
+			continue;
+		}
+
+		typed         := true;
+		not_mutable   := false;
+		continue_flag := false;
+
+		for i := 0; i < len(line.format_tokens); i += 1 {
+			if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Eq {
+				typed = false;
+			}
+
+			if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Colon {
+				not_mutable = true;
+			}
+
+			if line.format_tokens[i].kind == .Union ||
+			   line.format_tokens[i].kind == .Enum ||
+			   line.format_tokens[i].kind == .Struct ||
+			   line.format_tokens[i].kind == .For ||
+			   line.format_tokens[i].kind == .If ||
+			   line.format_tokens[i].kind == .Comment {
+				continue_flag = true;
+			}
+
+			//enforced undef is always on the last line, if it exists
+			if line.format_tokens[i].kind == .Proc && line.format_tokens[len(line.format_tokens)-1].kind != .Undef {
+				continue_flag = true;
+			}
+
+		}
+
+		if continue_flag {
+			continue;
+		}
+
+		if line_index != current_line + 1 || typed != current_typed || not_mutable != current_not_mutable {
+
+			if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
+				for colon_token in colon_tokens {
+					colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1;
+				}
+			} else if p.config.align_style == .Align_On_Type_And_Equals {
+				for type_token in type_tokens {
+					type_token.format_token.spaces_before = largest_lhs - type_token.length + 1;
+				}
+			}
+
+			if current_typed {
+				for equal_token in equal_tokens {
+					equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1;
+				}
+			} else {
+				for equal_token in equal_tokens {
+					equal_token.format_token.spaces_before = 0;
+				}
+			}
+
+			clear(&colon_tokens);
+			clear(&type_tokens);
+			clear(&equal_tokens);
+
+			largest_rhs = 0;
+			largest_lhs = 0;
+			current_typed = typed;
+			current_not_mutable = not_mutable;
+		}
+
+		current_line = line_index;
+
+		current_token_index := 0;
+		lhs_length          := 0;
+		rhs_length          := 0;
+
+		//calcuate the length of lhs of a value decl i.e. `a, b:`
+		for; current_token_index < len(line.format_tokens); current_token_index += 1 {
+
+			lhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before;
+
+			if line.format_tokens[current_token_index].kind == .Colon {
+				append(&colon_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = lhs_length});
+
+				if len(line.format_tokens) > current_token_index && line.format_tokens[current_token_index + 1].kind != .Eq {
+					append(&type_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index + 1], length = lhs_length});
+				}
+
+				current_token_index += 1;
+				largest_lhs = max(largest_lhs, lhs_length);
+				break;
+			}
+		}
+
+		//calcuate the length of the rhs i.e. `[dynamic]int = 123123`
+		for; current_token_index < len(line.format_tokens); current_token_index += 1 {
+
+			rhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before;
+
+			if line.format_tokens[current_token_index].kind == .Eq {
+				append(&equal_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = rhs_length});
+				largest_rhs = max(largest_rhs, rhs_length);
+				break;
+			}
+		}
+
+	}
+
+	//repeating myself, move to sub procedure
+	if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
+		for colon_token in colon_tokens {
+			colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1;
+		}
+	} else if p.config.align_style == .Align_On_Type_And_Equals {
+		for type_token in type_tokens {
+			type_token.format_token.spaces_before = largest_lhs - type_token.length + 1;
+		}
+	}
+
+	if current_typed {
+		for equal_token in equal_tokens {
+			equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1;
+		}
+	} else {
+		for equal_token in equal_tokens {
+			equal_token.format_token.spaces_before = 0;
+		}
+	}
+}
+
+align_switch_stmt :: proc(p: ^Printer, index: int) {
+	switch_found := false;
+	brace_token: Format_Token;
+	brace_line:  int;
+
+	found_switch_brace: for line, line_index in p.lines[index:] {
+		for format_token in line.format_tokens {
+			if format_token.kind == .Open_Brace && switch_found {
+				brace_token = format_token;
+				brace_line = line_index + index;
+				break found_switch_brace;
+			} else if format_token.kind == .Open_Brace {
+				break;
+			} else if format_token.kind == .Switch {
+				switch_found = true;
+			}
+		}
+	}
+
+	if !switch_found {
+		return;
+	}
+
+	largest    := 0;
+	case_count := 0;
+
+	TokenAndLength :: struct {
+		format_token: ^Format_Token,
+		length:       int,
+	};
+
+	format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator);
+
+	//find all the switch cases that are one lined
+	for line, line_index in p.lines[brace_line + 1:] {
+
+		case_found  := false;
+		colon_found := false;
+		length      := 0;
+
+		for format_token, i in line.format_tokens {
+
+			if format_token.kind == .Comment {
+				break;
+			}
+
+			//this will only happen if the case is one lined
+			if case_found && colon_found {
+				append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length});
+				largest = max(length, largest);
+				break;
+			}
+
+			if format_token.kind == .Case {
+				case_found = true;
+				case_count += 1;
+			} else if format_token.kind == .Colon {
+				colon_found = true;
+			}
+
+			length += len(format_token.text) + format_token.spaces_before;
+		}
+
+		if case_count >= brace_token.parameter_count {
+			break;
+		}
+	}
+
+	for token in format_tokens {
+		token.format_token.spaces_before = largest - token.length + 1;
+	}
+
+}
+
+align_enum :: proc(p: ^Printer, index: int) {
+	enum_found := false;
+	brace_token: Format_Token;
+	brace_line:  int;
+
+	found_enum_brace: for line, line_index in p.lines[index:] {
+		for format_token in line.format_tokens {
+			if format_token.kind == .Open_Brace && enum_found {
+				brace_token = format_token;
+				brace_line = line_index + index;
+				break found_enum_brace;
+			} else if format_token.kind == .Open_Brace {
+				break;
+			} else if format_token.kind == .Enum {
+				enum_found = true;
+			}
+		}
+	}
+
+	if !enum_found {
+		return;
+	}
+
+	largest     := 0;
+	comma_count := 0;
+
+	TokenAndLength :: struct {
+		format_token: ^Format_Token,
+		length:       int,
+	};
+
+	format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator);
+
+	for line, line_index in p.lines[brace_line + 1:] {
+		length := 0;
+
+		for format_token, i in line.format_tokens {
+			if format_token.kind == .Comment {
+				break;
+			}
+
+			if format_token.kind == .Eq {
+				append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length});
+				largest = max(length, largest);
+				break;
+			} else if format_token.kind == .Comma {
+				comma_count += 1;
+			}
+
+			length += len(format_token.text) + format_token.spaces_before;
+		}
+
+		if comma_count >= brace_token.parameter_count {
+			break;
+		}
+	}
+
+	for token in format_tokens {
+		token.format_token.spaces_before = largest - token.length + 1;
+	}
+
+}
+
+align_struct :: proc(p: ^Printer, index: int) -> int {
+	struct_found := false;
+	brace_token: Format_Token;
+	brace_line:  int;
+
+	found_struct_brace: for line, line_index in p.lines[index:] {
+		for format_token in line.format_tokens {
+			if format_token.kind == .Open_Brace && struct_found {
+				brace_token = format_token;
+				brace_line = line_index + index;
+				break found_struct_brace;
+			} else if format_token.kind == .Open_Brace {
+				break;
+			} else if format_token.kind == .Struct {
+				struct_found = true;
+			}
+		}
+	}
+
+	if !struct_found {
+		return 0;
+	}
+
+	largest     := 0;
+	colon_count := 0;
+	nested      := false;
+	seen_brace  := false;
+
+	TokenAndLength :: struct {
+		format_token: ^Format_Token,
+		length:       int,
+	};
+
+	format_tokens := make([]TokenAndLength, brace_token.parameter_count, context.temp_allocator);
+
+	if brace_token.parameter_count == 0 {
+		return 0;
+	}
+
+	end_line_index := 0;
+
+	for line, line_index in p.lines[brace_line + 1:] {
+		length := 0;
+
+		for format_token, i in line.format_tokens {
+
+			//give up on nested structs
+			if format_token.kind == .Comment {
+				break;
+			} else if format_token.kind == .Open_Paren {
+				break;
+			} else if format_token.kind == .Open_Brace {
+				seen_brace = true;
+			} else if format_token.kind == .Close_Brace {
+				seen_brace = false;
+			} else if seen_brace {
+				continue;
+			}
+
+			if format_token.kind == .Colon {
+				format_tokens[colon_count] = {format_token = &line.format_tokens[i + 1], length = length};
+
+				if format_tokens[colon_count].format_token.kind == .Struct {
+					nested = true;
+				}
+
+				colon_count += 1;
+				largest = max(length, largest);
+			}
+
+			length += len(format_token.text) + format_token.spaces_before;
+		}
+
+		if nested {
+			end_line_index = line_index + brace_line + 1;
+		}
+
+		if colon_count >= brace_token.parameter_count {
+			break;
+		}
+	}
+
+	//give up aligning nested, it never looks good
+	if nested {
+		for line, line_index in p.lines[end_line_index:] {
+			for format_token in line.format_tokens {
+				if format_token.kind == .Close_Brace {
+					return end_line_index + line_index - index;
+				}
+			}
+		}
+	}
+
+	for token in format_tokens {
+		token.format_token.spaces_before = largest - token.length + 1;
+	}
+
+	return 0;
+}
+
+align_comments :: proc(p: ^Printer) {
+
+	Comment_Align_Info :: struct {
+		length: int,
+		begin:  int,
+		end:    int,
+		depth:  int,
+	};
+
+	comment_infos := make([dynamic]Comment_Align_Info, 0, context.temp_allocator);
+
+	current_info: Comment_Align_Info;
+
+	for line, line_index in p.lines {
+		if len(line.format_tokens) <= 0 {
+			continue;
+		}
+
+		if .Line_Comment in line.types {
+			if current_info.end + 1 != line_index || current_info.depth != line.depth ||
+			   (current_info.begin == current_info.end && current_info.length == 0) {
+
+				if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
+					append(&comment_infos, current_info);
+				}
+
+				current_info.begin = line_index;
+				current_info.end = line_index;
+				current_info.depth = line.depth;
+				current_info.length = 0;
+			}
+
+			length := 0;
+
+			for format_token, i in line.format_tokens {
+				if format_token.kind == .Comment {
+					current_info.length = max(current_info.length, length);
+					current_info.end = line_index;
+				}
+
+				length += format_token.spaces_before + len(format_token.text);
+			}
+		}
+	}
+
+	if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
+		append(&comment_infos, current_info);
+	}
+
+	for info in comment_infos {
+
+		if info.begin == info.end || info.length == 0 {
+			continue;
+		}
+
+		for i := info.begin; i <= info.end; i += 1 {
+			l := p.lines[i];
+
+			length := 0;
+
+			for format_token, i in l.format_tokens {
+				if format_token.kind == .Comment {
+					if len(l.format_tokens) == 1 {
+						l.format_tokens[i].spaces_before = info.length + 1;
+					} else {
+						l.format_tokens[i].spaces_before = info.length - length + 1;
+					}
+				}
+
+				length += format_token.spaces_before + len(format_token.text);
+			}
+		}
+	}
+}

+ 1540 - 0
core/odin/printer/visit.odin

@@ -0,0 +1,1540 @@
+package odin_printer
+
+import "core:odin/ast"
+import "core:odin/tokenizer"
+import "core:strings"
+import "core:runtime"
+import "core:fmt"
+import "core:unicode/utf8"
+import "core:mem"
+import "core:sort"
+
+//right now the attribute order is not linearly parsed(bug?)
+@(private)
+sort_attribute :: proc(s: ^[dynamic]^ast.Attribute) -> sort.Interface {
+	return sort.Interface {
+		collection = rawptr(s),
+		len = proc(it: sort.Interface) -> int {
+			s := (^[dynamic]^ast.Attribute)(it.collection);
+			return len(s^);
+		},
+		less = proc(it: sort.Interface, i, j: int) -> bool {
+			s := (^[dynamic]^ast.Attribute)(it.collection);
+			return s[i].pos.offset < s[j].pos.offset;
+		},
+		swap = proc(it: sort.Interface, i, j: int) {
+			s := (^[dynamic]^ast.Attribute)(it.collection);
+			s[i], s[j] = s[j], s[i];
+		},
+	};
+}
+
+@(private)
+comment_before_position :: proc(p: ^Printer, pos: tokenizer.Pos) -> bool {
+	if len(p.comments) <= p.latest_comment_index {
+		return false;
+	}
+
+	comment := p.comments[p.latest_comment_index];
+
+	return comment.pos.offset < pos.offset;
+}
+
+@(private)
+next_comment_group :: proc(p: ^Printer) {
+	p.latest_comment_index += 1;
+}
+
+@(private)
+push_comment :: proc(p: ^Printer, comment: tokenizer.Token) -> int {
+	if len(comment.text) == 0 {
+		return 0;
+	}
+
+	if comment.text[:2] != "/*" {
+		format_token := Format_Token {
+			spaces_before = 1,
+			kind = .Comment,
+			text = comment.text,
+		};
+
+		if len(p.current_line.format_tokens) == 0 {
+			format_token.spaces_before = 0;
+		}
+
+		if !p.current_line.used {
+			p.current_line.used = true;
+			p.current_line.depth = p.depth;
+		}
+
+		append(&p.current_line.format_tokens, format_token);
+		p.last_token = &p.current_line.format_tokens[len(p.current_line.format_tokens) - 1];
+
+		hint_current_line(p, {.Line_Comment});
+
+		return 0;
+	} else {
+		builder := strings.make_builder(context.temp_allocator);
+
+		c_len      := len(comment.text);
+		trim_space := true;
+
+		multilines: [dynamic]string;
+
+		for i := 0; i < len(comment.text); i += 1 {
+			c := comment.text[i];
+
+			if c != ' ' && c != '\t' {
+				trim_space = false;
+			}
+
+			switch {
+			case (c == ' ' || c == '\t' || c == '\n') && trim_space:
+				continue;
+			case c == '\r' && comment.text[min(c_len - 1, i + 1)] == '\n':
+				append(&multilines, strings.to_string(builder));
+				builder = strings.make_builder(context.temp_allocator);
+				trim_space = true;
+				i += 1;
+			case c == '\n':
+				append(&multilines, strings.to_string(builder));
+				builder = strings.make_builder(context.temp_allocator);
+				trim_space = true;
+			case c == '/' && comment.text[min(c_len - 1, i + 1)] == '*':
+				strings.write_string(&builder, "/*");
+				trim_space = true;
+				i += 1;
+			case c == '*' && comment.text[min(c_len - 1, i + 1)] == '/':
+				trim_space = true;
+				strings.write_string(&builder, "*/");
+				i += 1;
+			case:
+				strings.write_byte(&builder, c);
+			}
+		}
+
+		if strings.builder_len(builder) > 0 {
+			append(&multilines, strings.to_string(builder));
+		}
+
+		for line in multilines {
+			format_token := Format_Token {
+				spaces_before = 1,
+				kind = .Comment,
+				text = line,
+			};
+
+			if len(p.current_line.format_tokens) == 0 {
+				format_token.spaces_before = 0;
+			}
+
+			if strings.contains(line, "*/") {
+				unindent(p);
+			}
+
+			if !p.current_line.used {
+				p.current_line.used = true;
+				p.current_line.depth = p.depth;
+			}
+
+			append(&p.current_line.format_tokens, format_token);
+			p.last_token = &p.current_line.format_tokens[len(p.current_line.format_tokens) - 1];
+
+			if strings.contains(line, "/*") {
+				indent(p);
+			}
+
+			newline_position(p, 1);
+		}
+
+		return len(multilines);
+	}
+}
+
+@(private)
+push_comments :: proc(p: ^Printer, pos: tokenizer.Pos) {
+	prev_comment:       ^tokenizer.Token;
+	prev_comment_lines: int;
+
+	for comment_before_position(p, pos) {
+		comment_group := p.comments[p.latest_comment_index];
+
+		if prev_comment == nil {
+			lines := comment_group.pos.line - p.last_source_position.line;
+			set_line(p, p.last_line_index + min(p.config.newline_limit+1, lines));
+		}
+
+		for comment, i in comment_group.list {
+			if prev_comment != nil && p.last_source_position.line != comment.pos.line {
+				newline_position(p, min(p.config.newline_limit+1, comment.pos.line - prev_comment.pos.line - prev_comment_lines));
+			}
+
+			prev_comment_lines = push_comment(p, comment);
+			prev_comment = &comment_group.list[i];
+		}
+
+		next_comment_group(p);
+	}
+
+	if prev_comment != nil {
+		newline_position(p, min(p.config.newline_limit+1, p.source_position.line - prev_comment.pos.line - prev_comment_lines));
+	}
+}
+
+@(private)
+append_format_token :: proc(p: ^Printer, format_token: Format_Token) -> ^Format_Token {
+	format_token := format_token;
+
+	if p.last_token != nil && (
+           p.last_token.kind == .Ellipsis ||
+           p.last_token.kind == .Range_Half || p.last_token.kind == .Range_Full ||
+	   p.last_token.kind == .Open_Paren || p.last_token.kind == .Period ||
+	   p.last_token.kind == .Open_Brace || p.last_token.kind == .Open_Bracket) {
+		format_token.spaces_before = 0;
+	} else if p.merge_next_token {
+		format_token.spaces_before = 0;
+		p.merge_next_token = false;
+	} else if p.space_next_token {
+		format_token.spaces_before = 1;
+		p.space_next_token = false;
+	}
+
+	push_comments(p, p.source_position);
+
+	unwrapped_line := p.current_line;
+
+	if !unwrapped_line.used {
+		unwrapped_line.used = true;
+		unwrapped_line.depth = p.depth;
+	}
+
+	if len(unwrapped_line.format_tokens) == 0 && format_token.spaces_before == 1 {
+		format_token.spaces_before = 0;
+	}
+
+	p.last_source_position = p.source_position;
+	p.last_line_index = p.current_line_index;
+
+	append(&unwrapped_line.format_tokens, format_token);
+	return &unwrapped_line.format_tokens[len(unwrapped_line.format_tokens) - 1];
+}
+
+@(private)
+push_format_token :: proc(p: ^Printer, format_token: Format_Token) {
+	p.last_token = append_format_token(p, format_token);
+}
+
+@(private)
+push_generic_token :: proc(p: ^Printer, kind: tokenizer.Token_Kind, spaces_before: int, value := "") {
+	format_token := Format_Token {
+		spaces_before = spaces_before,
+		kind = kind,
+		text = tokenizer.tokens[kind],
+	};
+
+	if value != "" {
+		format_token.text = value;
+	}
+
+	p.last_token = append_format_token(p, format_token);
+}
+
+@(private)
+push_string_token :: proc(p: ^Printer, text: string, spaces_before: int) {
+	format_token := Format_Token {
+		spaces_before = spaces_before,
+		kind = .String,
+		text = text,
+	};
+
+	p.last_token = append_format_token(p, format_token);
+}
+
+@(private)
+push_ident_token :: proc(p: ^Printer, text: string, spaces_before: int) {
+	format_token := Format_Token {
+		spaces_before = spaces_before,
+		kind = .Ident,
+		text = text,
+	};
+
+	p.last_token = append_format_token(p, format_token);
+}
+
+@(private)
+set_source_position :: proc(p: ^Printer, pos: tokenizer.Pos) {
+	p.source_position = pos;
+}
+
+@(private)
+move_line :: proc(p: ^Printer, pos: tokenizer.Pos) {
+	move_line_limit(p, pos, p.config.newline_limit+1);
+}
+
+@(private)
+move_line_limit :: proc(p: ^Printer, pos: tokenizer.Pos, limit: int) -> bool {
+	lines := min(pos.line - p.source_position.line, limit);
+
+	if lines < 0 {
+		return false;
+	}
+
+	p.source_position = pos;
+	p.current_line_index += lines;
+	set_line(p, p.current_line_index);
+	return lines > 0;
+}
+
+@(private)
+set_line :: proc(p: ^Printer, line: int) -> ^Line {
+	unwrapped_line: ^Line;
+
+	if line >= len(p.lines) {
+		for i := len(p.lines); i <= line; i += 1 {
+			new_line: Line;
+			new_line.format_tokens = make([dynamic]Format_Token, 0, 50, p.allocator);
+			append(&p.lines, new_line);
+		}
+		unwrapped_line = &p.lines[line];
+	} else {
+		unwrapped_line = &p.lines[line];
+	}
+
+	p.current_line = unwrapped_line;
+	p.current_line_index = line;
+
+	return unwrapped_line;
+}
+
+@(private)
+newline_position :: proc(p: ^Printer, count: int) {
+	p.current_line_index += count;
+	set_line(p, p.current_line_index);
+}
+
+@(private)
+indent :: proc(p: ^Printer) {
+	p.depth += 1;
+}
+
+@(private)
+unindent :: proc(p: ^Printer) {
+	p.depth -= 1;
+}
+
+@(private)
+merge_next_token :: proc(p: ^Printer) {
+	p.merge_next_token = true;
+}
+
+@(private)
+space_next_token :: proc(p: ^Printer) {
+	p.space_next_token = true;
+}
+
+@(private)
+hint_current_line :: proc(p: ^Printer, hint: Line_Type) {
+	p.current_line.types |= hint;
+}
+
+@(private)
+visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
+	using ast;
+
+	if decl == nil {
+		return;
+	}
+
+	switch v in &decl.derived {
+	case Expr_Stmt:
+		move_line(p, decl.pos);
+		visit_expr(p, v.expr);
+		if p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case When_Stmt:
+		visit_stmt(p, cast(^Stmt)decl);
+	case Foreign_Import_Decl:
+		if len(v.attributes) > 0 {
+			sort.sort(sort_attribute(&v.attributes));
+			move_line(p, v.attributes[0].pos);
+			visit_attributes(p, v.attributes);
+		}
+
+		move_line(p, decl.pos);
+
+		push_generic_token(p, v.foreign_tok.kind, 0);
+		push_generic_token(p, v.import_tok.kind, 1);
+
+		if v.name != nil {
+			push_ident_token(p, v.name.name, 1);
+		}
+
+		for path in v.fullpaths {
+			push_ident_token(p, path, 0);
+		}
+	case Foreign_Block_Decl:
+		if len(v.attributes) > 0 {
+			sort.sort(sort_attribute(&v.attributes));
+			move_line(p, v.attributes[0].pos);
+			visit_attributes(p, v.attributes);
+		}
+
+		move_line(p, decl.pos);
+
+		push_generic_token(p, .Foreign, 0);
+
+		visit_expr(p, v.foreign_library);
+		visit_stmt(p, v.body);
+	case Import_Decl:
+		move_line(p, decl.pos);
+
+		if v.name.text != "" {
+			push_generic_token(p, v.import_tok.kind, 1);
+			push_generic_token(p, v.name.kind, 1, v.name.text);
+			push_ident_token(p, v.fullpath, 1);
+		} else {
+			push_generic_token(p, v.import_tok.kind, 1);
+			push_ident_token(p, v.fullpath, 1);
+		}
+
+	case Value_Decl:
+		if len(v.attributes) > 0 {
+			sort.sort(sort_attribute(&v.attributes));
+			move_line(p, v.attributes[0].pos);
+			visit_attributes(p, v.attributes);
+		}
+
+		move_line(p, decl.pos);
+
+		if v.is_using {
+			push_generic_token(p, .Using, 0);
+		}
+
+		visit_exprs(p, v.names, {.Add_Comma});
+
+		hint_current_line(p, {.Value_Decl});
+
+		if v.type != nil {
+			if !v.is_mutable {
+				push_generic_token(p, .Colon, 0);
+			} else {
+				push_generic_token(p, .Colon, 0);
+			}
+
+			visit_expr(p, v.type);
+		} else {
+			if !v.is_mutable {
+				push_generic_token(p, .Colon, 1);
+				push_generic_token(p, .Colon, 0);
+			} else {
+				push_generic_token(p, .Colon, 1);
+			}
+		}
+
+		if v.is_mutable && v.type != nil && len(v.values) != 0 {
+			push_generic_token(p, .Eq, 1);
+		} else if v.is_mutable && v.type == nil && len(v.values) != 0 {
+			push_generic_token(p, .Eq, 0);
+		} else if !v.is_mutable && v.type != nil {
+			push_generic_token(p, .Colon, 0);
+		}
+
+		if len(v.values) == 1 {
+			visit_expr(p, v.values[0]); //this is too ensure that one value are never newlined(procs, structs, etc.)
+		} else {
+			visit_exprs(p, v.values, {.Add_Comma});
+		}
+
+		add_semicolon := true;
+
+		for value in v.values {
+			switch a in value.derived {
+			case Union_Type, Enum_Type, Struct_Type:
+				add_semicolon = false || called_in_stmt;
+			case Proc_Lit:
+				add_semicolon = false;
+			}
+		}
+
+		if add_semicolon && p.config.semicolons && !p.skip_semicolon {
+			push_generic_token(p, .Semicolon, 0);
+		}
+
+	case:
+		panic(fmt.aprint(decl.derived));
+	}
+}
+
+@(private)
+visit_exprs :: proc(p: ^Printer, list: []^ast.Expr, options := List_Options{}) {
+	if len(list) == 0 {
+		return;
+	}
+
+	// we have to newline the expressions to respect the source
+	for expr, i in list {
+		// Don't move the first expression, it looks bad
+		if i != 0 && .Enforce_Newline in options {
+			newline_position(p, 1);
+		} else if i != 0 {
+			move_line_limit(p, expr.pos, 1);
+		}
+
+		visit_expr(p, expr, options);
+
+		if (i != len(list) - 1 || .Trailing in options) && .Add_Comma in options {
+			push_generic_token(p, .Comma, 0);
+		}
+	}
+
+	if len(list) > 1 && .Enforce_Newline in options {
+		newline_position(p, 1);
+	}
+}
+
+@(private)
+visit_attributes :: proc(p: ^Printer, attributes: [dynamic]^ast.Attribute) {
+	if len(attributes) == 0 {
+		return;
+	}
+
+	for attribute, i in attributes {
+		move_line_limit(p, attribute.pos, 1);
+
+		push_generic_token(p, .At, 0);
+		push_generic_token(p, .Open_Paren, 0);
+
+		visit_exprs(p, attribute.elems, {.Add_Comma});
+
+		push_generic_token(p, .Close_Paren, 0);
+	}
+}
+
+@(private)
+visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Generic, empty_block := false, block_stmt := false) {
+	using ast;
+
+	if stmt == nil {
+		return;
+	}
+
+	switch v in stmt.derived {
+	case Import_Decl:
+		visit_decl(p, cast(^Decl)stmt, true);
+		return;
+	case Value_Decl:
+		visit_decl(p, cast(^Decl)stmt, true);
+		return;
+	case Foreign_Import_Decl:
+		visit_decl(p, cast(^Decl)stmt, true);
+		return;
+	case Foreign_Block_Decl:
+		visit_decl(p, cast(^Decl)stmt, true);
+		return;
+	}
+
+	switch v in stmt.derived {
+	case Using_Stmt:
+		move_line(p, v.pos);
+
+		push_generic_token(p, .Using, 1);
+
+		visit_exprs(p, v.list, {.Add_Comma});
+
+		if p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case Block_Stmt:
+		move_line(p, v.pos);
+
+		if v.pos.line == v.end.line {
+			if !empty_block {
+				push_generic_token(p, .Open_Brace, 1);
+			}
+
+			set_source_position(p, v.pos);
+
+			visit_block_stmts(p, v.stmts, len(v.stmts) > 1 && p.config.split_multiple_stmts);
+
+			set_source_position(p, v.end);
+
+			if !empty_block {
+				push_generic_token(p, .Close_Brace, 0);
+			}
+		} else {
+			if !empty_block {
+				visit_begin_brace(p, v.pos, block_type, len(v.stmts));
+			}
+
+			set_source_position(p, v.pos);
+
+			visit_block_stmts(p, v.stmts, len(v.stmts) > 1 && p.config.split_multiple_stmts);
+
+			if !empty_block {
+				visit_end_brace(p, v.end);
+			}
+		}
+	case If_Stmt:
+		move_line(p, v.pos);
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+			push_generic_token(p, .Colon, 0);
+		}
+
+		push_generic_token(p, .If, 1);
+
+		hint_current_line(p, {.If});
+
+		if v.init != nil {
+			p.skip_semicolon = true;
+			visit_stmt(p, v.init);
+			p.skip_semicolon = false;
+			push_generic_token(p, .Semicolon, 0);
+		}
+
+		visit_expr(p, v.cond);
+
+		uses_do := false;
+
+		if check_stmt, ok := v.body.derived.(Block_Stmt); ok && check_stmt.uses_do {
+			uses_do = true;
+		}
+
+		if uses_do && !p.config.convert_do {
+			push_generic_token(p, .Do, 1);
+			visit_stmt(p, v.body, .If_Stmt, true);
+		} else {
+			if uses_do {
+				newline_position(p, 1);
+			}
+
+			set_source_position(p, v.body.pos);
+
+			visit_stmt(p, v.body, .If_Stmt);
+
+			set_source_position(p, v.body.end);
+		}
+
+		if v.else_stmt != nil {
+
+			if p.config.brace_style == .Allman || p.config.brace_style == .Stroustrup {
+				newline_position(p, 1);
+			}
+
+			push_generic_token(p, .Else, 1);
+
+			set_source_position(p, v.else_stmt.pos);
+
+			visit_stmt(p, v.else_stmt);
+		}
+	case Switch_Stmt:
+		move_line(p, v.pos);
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+			push_generic_token(p, .Colon, 0);
+		}
+
+		if v.partial {
+			push_ident_token(p, "#partial", 1);
+		}
+
+		push_generic_token(p, .Switch, 1);
+
+		hint_current_line(p, {.Switch_Stmt});
+
+		if v.init != nil {
+			p.skip_semicolon = true;
+			visit_stmt(p, v.init);
+			p.skip_semicolon = false;
+		}
+
+		if v.init != nil && v.cond != nil {
+			push_generic_token(p, .Semicolon, 0);
+		}
+
+		visit_expr(p, v.cond);
+		visit_stmt(p, v.body);
+	case Case_Clause:
+		move_line(p, v.pos);
+
+		if !p.config.indent_cases {
+			unindent(p);
+		}
+
+		push_generic_token(p, .Case, 0);
+
+		if v.list != nil {
+			visit_exprs(p, v.list, {.Add_Comma});
+		}
+
+		push_generic_token(p, v.terminator.kind, 0);
+
+		indent(p);
+
+		visit_block_stmts(p, v.body);
+
+		unindent(p);
+
+		if !p.config.indent_cases {
+			indent(p);
+		}
+	case Type_Switch_Stmt:
+		move_line(p, v.pos);
+
+		hint_current_line(p, {.Switch_Stmt});
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+			push_generic_token(p, .Colon, 0);
+		}
+
+		if v.partial {
+			push_ident_token(p, "#partial", 1);
+		}
+
+		push_generic_token(p, .Switch, 1);
+
+		visit_stmt(p, v.tag);
+		visit_stmt(p, v.body);
+	case Assign_Stmt:
+		move_line(p, v.pos);
+
+		hint_current_line(p, {.Assign});
+
+		visit_exprs(p, v.lhs, {.Add_Comma});
+
+		push_generic_token(p, v.op.kind, 1);
+
+		visit_exprs(p, v.rhs, {.Add_Comma});
+
+		if block_stmt && p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case Expr_Stmt:
+		move_line(p, v.pos);
+		visit_expr(p, v.expr);
+		if block_stmt && p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case For_Stmt:
+		// this should be simplified
+		move_line(p, v.pos);
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+			push_generic_token(p, .Colon, 0);
+		}
+
+		push_generic_token(p, .For, 1);
+
+		hint_current_line(p, {.For});
+
+		if v.init != nil {
+			p.skip_semicolon = true;
+			visit_stmt(p, v.init);
+			p.skip_semicolon = false;
+			push_generic_token(p, .Semicolon, 0);
+		} else if v.post != nil {
+			push_generic_token(p, .Semicolon, 0);
+		}
+
+		if v.cond != nil {
+			move_line(p, v.cond.pos);
+			visit_expr(p, v.cond);
+		}
+
+		if v.post != nil {
+			push_generic_token(p, .Semicolon, 0);
+			move_line(p, v.post.pos);
+			visit_stmt(p, v.post);
+		} else if v.post == nil && v.cond != nil && v.init != nil {
+			push_generic_token(p, .Semicolon, 0);
+		}
+
+		visit_stmt(p, v.body);
+
+	case Inline_Range_Stmt:
+		move_line(p, v.pos);
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+			push_generic_token(p, .Colon, 0);
+		}
+
+		push_ident_token(p, "#unroll", 0);
+
+		push_generic_token(p, .For, 1);
+
+		hint_current_line(p, {.For});
+
+		visit_expr(p, v.val0);
+
+		if v.val1 != nil {
+			push_generic_token(p, .Comma, 0);
+			visit_expr(p, v.val1);
+		}
+
+		push_generic_token(p, .In, 1);
+
+		visit_expr(p, v.expr);
+		visit_stmt(p, v.body);
+
+	case Range_Stmt:
+		move_line(p, v.pos);
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+			push_generic_token(p, .Colon, 0);
+		}
+
+		push_generic_token(p, .For, 1);
+
+		hint_current_line(p, {.For});
+
+		if len(v.vals) >= 1 {
+			visit_expr(p, v.vals[0]);
+		}
+
+		if len(v.vals) >= 2 {
+			push_generic_token(p, .Comma, 0);
+			visit_expr(p, v.vals[1]);
+		}
+
+		push_generic_token(p, .In, 1);
+
+		visit_expr(p, v.expr);
+
+		visit_stmt(p, v.body);
+	case Return_Stmt:
+		move_line(p, v.pos);
+
+		push_generic_token(p, .Return, 1);
+
+		if v.results != nil {
+			visit_exprs(p, v.results, {.Add_Comma});
+		}
+
+		if block_stmt && p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case Defer_Stmt:
+		move_line(p, v.pos);
+		push_generic_token(p, .Defer, 0);
+
+		visit_stmt(p, v.stmt);
+
+		if p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case When_Stmt:
+		move_line(p, v.pos);
+		push_generic_token(p, .When, 1);
+		visit_expr(p, v.cond);
+
+		visit_stmt(p, v.body);
+
+		if v.else_stmt != nil {
+
+			if p.config.brace_style == .Allman {
+				newline_position(p, 1);
+			}
+
+			push_generic_token(p, .Else, 1);
+
+			set_source_position(p, v.else_stmt.pos);
+
+			visit_stmt(p, v.else_stmt);
+		}
+
+	case Branch_Stmt:
+		move_line(p, v.pos);
+
+		push_generic_token(p, v.tok.kind, 0);
+
+		if v.label != nil {
+			visit_expr(p, v.label);
+		}
+
+		if p.config.semicolons {
+			push_generic_token(p, .Semicolon, 0);
+		}
+	case:
+		panic(fmt.aprint(stmt.derived));
+	}
+
+	set_source_position(p, stmt.end);
+}
+
+@(private)
+push_where_clauses :: proc(p: ^Printer, clauses: []^ast.Expr) {
+	if len(clauses) == 0 {
+		return;
+	}
+
+	// TODO(bill): This is not outputting correctly at all
+
+	move_line(p, clauses[0].pos);
+	push_generic_token(p, .Where, 1);
+
+	force_newline := false;
+
+	for expr, i in clauses {
+		// Don't move the first expression, it looks bad
+		if i != 0 && i != len(clauses)-1 && force_newline {
+			newline_position(p, 1);
+		} else if i != 0 {
+			move_line_limit(p, expr.pos, 1);
+		}
+
+		visit_expr(p, expr);
+
+		if i != len(clauses) - 1 {
+			push_generic_token(p, .Comma, 0);
+		}
+	}
+
+	if len(clauses) > 1 && force_newline {
+		newline_position(p, 1);
+	}
+}
+
+@(private)
+push_poly_params :: proc(p: ^Printer, poly_params: ^ast.Field_List) {
+	if poly_params != nil {
+		push_generic_token(p, .Open_Paren, 0);
+		visit_field_list(p, poly_params, {.Add_Comma, .Enforce_Poly_Names});
+		push_generic_token(p, .Close_Paren, 0);
+	}
+}
+
+
+@(private)
+visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
+	using ast;
+
+	if expr == nil {
+		return;
+	}
+
+	set_source_position(p, expr.pos);
+
+	switch v in expr.derived {
+	case Inline_Asm_Expr:
+		push_generic_token(p, v.tok.kind, 1, v.tok.text);
+
+		push_generic_token(p, .Open_Paren, 1);
+		visit_exprs(p, v.param_types, {.Add_Comma});
+		push_generic_token(p, .Close_Paren, 0);
+
+		push_generic_token(p, .Sub, 1);
+		push_generic_token(p, .Gt, 0);
+
+		visit_expr(p, v.return_type);
+
+		push_generic_token(p, .Open_Brace, 1);
+		visit_expr(p, v.asm_string);
+		push_generic_token(p, .Comma, 0);
+		visit_expr(p, v.constraints_string);
+		push_generic_token(p, .Close_Brace, 0);
+	case Undef:
+		push_generic_token(p, .Undef, 1);
+	case Auto_Cast:
+		push_generic_token(p, v.op.kind, 1);
+		visit_expr(p, v.expr);
+	case Ternary_Expr:
+		visit_expr(p, v.cond);
+		push_generic_token(p, v.op1.kind, 1);
+		visit_expr(p, v.x);
+		push_generic_token(p, v.op2.kind, 1);
+		visit_expr(p, v.y);
+	case Ternary_If_Expr:
+		visit_expr(p, v.x);
+		push_generic_token(p, v.op1.kind, 1);
+		visit_expr(p, v.cond);
+		push_generic_token(p, v.op2.kind, 1);
+		visit_expr(p, v.y);
+	case Ternary_When_Expr:
+		visit_expr(p, v.x);
+		push_generic_token(p, v.op1.kind, 1);
+		visit_expr(p, v.cond);
+		push_generic_token(p, v.op2.kind, 1);
+		visit_expr(p, v.y);
+	case Selector_Call_Expr:
+		visit_expr(p, v.call.expr);
+		push_generic_token(p, .Open_Paren, 1);
+		visit_exprs(p, v.call.args, {.Add_Comma});
+		push_generic_token(p, .Close_Paren, 0);
+	case Ellipsis:
+		push_generic_token(p, .Ellipsis, 1);
+		visit_expr(p, v.expr);
+	case Relative_Type:
+		visit_expr(p, v.tag);
+		visit_expr(p, v.type);
+	case Slice_Expr:
+		visit_expr(p, v.expr);
+		push_generic_token(p, .Open_Bracket, 0);
+		visit_expr(p, v.low);
+		push_generic_token(p, v.interval.kind, 0);
+		if v.high != nil {
+			merge_next_token(p);
+			visit_expr(p, v.high);
+		}
+		push_generic_token(p, .Close_Bracket, 0);
+	case Ident:
+		if .Enforce_Poly_Names in options {
+			push_generic_token(p, .Dollar, 1);
+			push_ident_token(p, v.name, 0);
+		} else {
+			push_ident_token(p, v.name, 1);
+		}
+	case Deref_Expr:
+		visit_expr(p, v.expr);
+		push_generic_token(p, v.op.kind, 0);
+	case Type_Cast:
+		push_generic_token(p, v.tok.kind, 1);
+		push_generic_token(p, .Open_Paren, 0);
+		visit_expr(p, v.type);
+		push_generic_token(p, .Close_Paren, 0);
+		merge_next_token(p);
+		visit_expr(p, v.expr);
+	case Basic_Directive:
+		push_generic_token(p, v.tok.kind, 1);
+		push_ident_token(p, v.name, 0);
+	case Distinct_Type:
+		push_generic_token(p, .Distinct, 1);
+		visit_expr(p, v.type);
+	case Dynamic_Array_Type:
+		visit_expr(p, v.tag);
+		push_generic_token(p, .Open_Bracket, 1);
+		push_generic_token(p, .Dynamic, 0);
+		push_generic_token(p, .Close_Bracket, 0);
+		merge_next_token(p);
+		visit_expr(p, v.elem);
+	case Bit_Set_Type:
+		push_generic_token(p, .Bit_Set, 1);
+		push_generic_token(p, .Open_Bracket, 0);
+
+		visit_expr(p, v.elem);
+
+		if v.underlying != nil {
+			push_generic_token(p, .Semicolon, 0);
+			visit_expr(p, v.underlying);
+		}
+
+		push_generic_token(p, .Close_Bracket, 0);
+	case Union_Type:
+		push_generic_token(p, .Union, 1);
+
+		push_poly_params(p, v.poly_params);
+
+		if v.is_maybe {
+			push_ident_token(p, "#maybe", 1);
+		}
+
+		push_where_clauses(p, v.where_clauses);
+
+		if v.variants != nil && (len(v.variants) == 0 || v.pos.line == v.end.line) {
+			push_generic_token(p, .Open_Brace, 1);
+			visit_exprs(p, v.variants, {.Add_Comma});
+			push_generic_token(p, .Close_Brace, 0);
+		} else {
+			visit_begin_brace(p, v.pos, .Generic);
+			newline_position(p, 1);
+			set_source_position(p, v.variants[0].pos);
+			visit_exprs(p, v.variants, {.Add_Comma, .Trailing});
+			visit_end_brace(p, v.end);
+		}
+	case Enum_Type:
+		push_generic_token(p, .Enum, 1);
+
+		hint_current_line(p, {.Enum});
+
+		if v.base_type != nil {
+			visit_expr(p, v.base_type);
+		}
+
+		if v.fields != nil && (len(v.fields) == 0 || v.pos.line == v.end.line) {
+			push_generic_token(p, .Open_Brace, 1);
+			visit_exprs(p, v.fields, {.Add_Comma});
+			push_generic_token(p, .Close_Brace, 0);
+		} else {
+			visit_begin_brace(p, v.pos, .Generic, len(v.fields));
+			newline_position(p, 1);
+			set_source_position(p, v.fields[0].pos);
+			visit_exprs(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline});
+			set_source_position(p, v.end);
+			visit_end_brace(p, v.end);
+		}
+
+		set_source_position(p, v.end);
+	case Struct_Type:
+		push_generic_token(p, .Struct, 1);
+
+		hint_current_line(p, {.Struct});
+
+		push_poly_params(p, v.poly_params);
+
+		if v.is_packed {
+			push_ident_token(p, "#packed", 1);
+		}
+
+		if v.is_raw_union {
+			push_ident_token(p, "#raw_union", 1);
+		}
+
+		if v.align != nil {
+			push_ident_token(p, "#align", 1);
+			visit_expr(p, v.align);
+		}
+
+		push_where_clauses(p, v.where_clauses);
+
+		if v.fields != nil && (len(v.fields.list) == 0 || v.pos.line == v.end.line) {
+			push_generic_token(p, .Open_Brace, 1);
+			set_source_position(p, v.fields.pos);
+			visit_field_list(p, v.fields, {.Add_Comma});
+			push_generic_token(p, .Close_Brace, 0);
+		} else if v.fields != nil {
+			visit_begin_brace(p, v.pos, .Generic, len(v.fields.list));
+			set_source_position(p, v.fields.pos);
+			visit_field_list(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline});
+			visit_end_brace(p, v.end);
+		}
+
+		set_source_position(p, v.end);
+	case Proc_Lit:
+		switch v.inlining {
+		case .None:
+		case .Inline:
+			push_ident_token(p, "#force_inline", 0);
+		case .No_Inline:
+			push_ident_token(p, "#force_no_inline", 0);
+		}
+
+		visit_proc_type(p, v.type^, true);
+
+		push_where_clauses(p, v.where_clauses);
+
+		if v.body != nil {
+			set_source_position(p, v.body.pos);
+			visit_stmt(p, v.body, .Proc);
+		} else {
+			push_generic_token(p, .Undef, 1);
+		}
+	case Proc_Type:
+		visit_proc_type(p, v);
+	case Basic_Lit:
+		push_generic_token(p, v.tok.kind, 1, v.tok.text);
+	case Binary_Expr:
+		visit_binary_expr(p, v);
+	case Implicit_Selector_Expr:
+		push_generic_token(p, .Period, 1);
+		push_ident_token(p, v.field.name, 0);
+	case Call_Expr:
+		visit_expr(p, v.expr);
+
+		push_format_token(p,
+			Format_Token {
+				kind = .Open_Paren,
+				type = .Call,
+				text = "(",
+			},
+		);
+
+		hint_current_line(p, {.Call});
+
+		visit_call_exprs(p, v.args, v.ellipsis.kind == .Ellipsis);
+		push_generic_token(p, .Close_Paren, 0);
+	case Typeid_Type:
+		push_generic_token(p, .Typeid, 1);
+
+		if v.specialization != nil {
+			push_generic_token(p, .Quo, 0);
+			visit_expr(p, v.specialization);
+		}
+	case Selector_Expr:
+		visit_expr(p, v.expr);
+		push_generic_token(p, v.op.kind, 0);
+		visit_expr(p, v.field);
+	case Paren_Expr:
+		push_generic_token(p, .Open_Paren, 1);
+		visit_expr(p, v.expr);
+		push_generic_token(p, .Close_Paren, 0);
+	case Index_Expr:
+		visit_expr(p, v.expr);
+		push_generic_token(p, .Open_Bracket, 0);
+		visit_expr(p, v.index);
+		push_generic_token(p, .Close_Bracket, 0);
+	case Proc_Group:
+		push_generic_token(p, v.tok.kind, 1);
+
+		if len(v.args) != 0 && v.pos.line != v.args[len(v.args) - 1].pos.line {
+			visit_begin_brace(p, v.pos, .Generic);
+			newline_position(p, 1);
+			set_source_position(p, v.args[0].pos);
+			visit_exprs(p, v.args, {.Add_Comma, .Trailing});
+			visit_end_brace(p, v.end);
+		} else {
+			push_generic_token(p, .Open_Brace, 0);
+			visit_exprs(p, v.args, {.Add_Comma});
+			push_generic_token(p, .Close_Brace, 0);
+		}
+
+	case Comp_Lit:
+		if v.type != nil {
+			visit_expr(p, v.type);
+		}
+
+		if len(v.elems) != 0 && v.pos.line != v.elems[len(v.elems) - 1].pos.line {
+			visit_begin_brace(p, v.pos, .Comp_Lit, 0);
+			newline_position(p, 1);
+			set_source_position(p, v.elems[0].pos);
+			visit_exprs(p, v.elems, {.Add_Comma, .Trailing});
+			visit_end_brace(p, v.end);
+		} else {
+			push_generic_token(p, .Open_Brace, 0 if v.type != nil else 1);
+			visit_exprs(p, v.elems, {.Add_Comma});
+			push_generic_token(p, .Close_Brace, 0);
+		}
+
+	case Unary_Expr:
+		push_generic_token(p, v.op.kind, 1);
+		merge_next_token(p);
+		visit_expr(p, v.expr);
+	case Field_Value:
+		visit_expr(p, v.field);
+		push_generic_token(p, .Eq, 1);
+		visit_expr(p, v.value);
+	case Type_Assertion:
+		visit_expr(p, v.expr);
+
+		if unary, ok := v.type.derived.(Unary_Expr); ok && unary.op.text == "?" {
+			push_generic_token(p, .Period, 0);
+			visit_expr(p, v.type);
+		} else {
+			push_generic_token(p, .Period, 0);
+			push_generic_token(p, .Open_Paren, 0);
+			visit_expr(p, v.type);
+			push_generic_token(p, .Close_Paren, 0);
+		}
+
+	case Pointer_Type:
+		push_generic_token(p, .Pointer, 1);
+		merge_next_token(p);
+		visit_expr(p, v.elem);
+	case Implicit:
+		push_generic_token(p, v.tok.kind, 1);
+	case Poly_Type:
+		push_generic_token(p, .Dollar, 1);
+		merge_next_token(p);
+		visit_expr(p, v.type);
+
+		if v.specialization != nil {
+			push_generic_token(p, .Quo, 0);
+			merge_next_token(p);
+			visit_expr(p, v.specialization);
+		}
+	case Array_Type:
+		visit_expr(p, v.tag);
+		push_generic_token(p, .Open_Bracket, 1);
+		visit_expr(p, v.len);
+		push_generic_token(p, .Close_Bracket, 0);
+		merge_next_token(p);
+		visit_expr(p, v.elem);
+	case Map_Type:
+		push_generic_token(p, .Map, 1);
+		push_generic_token(p, .Open_Bracket, 0);
+		visit_expr(p, v.key);
+		push_generic_token(p, .Close_Bracket, 0);
+		merge_next_token(p);
+		visit_expr(p, v.value);
+	case Helper_Type:
+		visit_expr(p, v.type);
+	case:
+		panic(fmt.aprint(expr.derived));
+	}
+}
+
+visit_begin_brace :: proc(p: ^Printer, begin: tokenizer.Pos, type: Block_Type, count := 0, same_line_spaces_before := 1) {
+	set_source_position(p, begin);
+
+	newline_braced := p.config.brace_style == .Allman;
+	newline_braced |= p.config.brace_style == .K_And_R && type == .Proc;
+	newline_braced &= p.config.brace_style != ._1TBS;
+
+	format_token := Format_Token {
+		kind = .Open_Brace,
+		parameter_count = count,
+		text = "{",
+	};
+
+	if newline_braced {
+		newline_position(p, 1);
+		push_format_token(p, format_token);
+		indent(p);
+	} else {
+		format_token.spaces_before = same_line_spaces_before;
+		push_format_token(p, format_token);
+		indent(p);
+	}
+}
+
+visit_end_brace :: proc(p: ^Printer, end: tokenizer.Pos) {
+	move_line(p, end);
+	push_generic_token(p, .Close_Brace, 0);
+	unindent(p);
+	p.current_line.depth = p.depth;
+}
+
+visit_block_stmts :: proc(p: ^Printer, stmts: []^ast.Stmt, split := false) {
+	for stmt, i in stmts {
+		visit_stmt(p, stmt, .Generic, false, true);
+
+		if split && i != len(stmts) - 1 && stmt.pos.line == stmts[i + 1].pos.line {
+			newline_position(p, 1);
+		}
+	}
+}
+
+List_Option :: enum u8 {
+	Add_Comma,
+	Trailing,
+	Enforce_Newline,
+	Enforce_Poly_Names,
+}
+
+List_Options :: distinct bit_set[List_Option];
+
+visit_field_list :: proc(p: ^Printer, list: ^ast.Field_List, options := List_Options{}) {
+	if list.list == nil {
+		return;
+	}
+
+	for field, i in list.list {
+		if !move_line_limit(p, field.pos, 1) && .Enforce_Newline in options {
+			newline_position(p, 1);
+		}
+
+		if .Using in field.flags {
+			push_generic_token(p, .Using, 1);
+		}
+
+		name_options := List_Options{.Add_Comma};
+		if .Enforce_Poly_Names in options {
+			name_options += {.Enforce_Poly_Names};
+		}
+
+		visit_exprs(p, field.names, name_options);
+
+		if field.type != nil {
+			if len(field.names) != 0 {
+				push_generic_token(p, .Colon, 0);
+			}
+			visit_expr(p, field.type);
+		} else {
+			push_generic_token(p, .Colon, 1);
+			push_generic_token(p, .Eq, 0);
+			visit_expr(p, field.default_value);
+		}
+
+		if field.tag.text != "" {
+			push_generic_token(p, field.tag.kind, 1, field.tag.text);
+		}
+
+		if (i != len(list.list) - 1 || .Trailing in options) && .Add_Comma in options {
+			push_generic_token(p, .Comma, 0);
+		}
+	}
+}
+
+visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type, is_proc_lit := false) {
+	if is_proc_lit {
+		push_format_token(p, Format_Token {
+			kind = .Proc,
+			type = .Proc_Lit,
+			text = "proc",
+			spaces_before = 1,
+		});
+	} else {
+		push_format_token(p, Format_Token {
+			kind = .Proc,
+			text = "proc",
+			spaces_before = 1,
+		});
+	}
+
+	explicit_calling := false;
+
+	if v, ok := proc_type.calling_convention.(string); ok {
+		explicit_calling = true;
+		push_string_token(p, v, 1);
+	}
+
+	if explicit_calling {
+		push_generic_token(p, .Open_Paren, 1);
+	} else {
+		push_generic_token(p, .Open_Paren, 0);
+	}
+
+	visit_signature_list(p, proc_type.params, false);
+
+	push_generic_token(p, .Close_Paren, 0);
+
+	if proc_type.results != nil {
+		push_generic_token(p, .Sub, 1);
+		push_generic_token(p, .Gt, 0);
+
+		use_parens := false;
+		use_named  := false;
+
+		if len(proc_type.results.list) > 1 {
+			use_parens = true;
+		} else if len(proc_type.results.list) == 1 {
+
+			for name in proc_type.results.list[0].names {
+				if ident, ok := name.derived.(ast.Ident); ok {
+					if ident.name != "_" {
+						use_parens = true;
+					}
+				}
+			}
+		}
+
+		if use_parens {
+			push_generic_token(p, .Open_Paren, 1);
+			visit_signature_list(p, proc_type.results);
+			push_generic_token(p, .Close_Paren, 0);
+		} else {
+			visit_signature_list(p, proc_type.results);
+		}
+	}
+}
+
+visit_binary_expr :: proc(p: ^Printer, binary: ast.Binary_Expr) {
+	move_line(p, binary.left.pos);
+
+	if v, ok := binary.left.derived.(ast.Binary_Expr); ok {
+		visit_binary_expr(p, v);
+	} else {
+		visit_expr(p, binary.left);
+	}
+
+	either_implicit_selector := false;
+	if _, ok := binary.left.derived.(ast.Implicit_Selector_Expr); ok {
+		either_implicit_selector = true;
+	} else if _, ok := binary.right.derived.(ast.Implicit_Selector_Expr); ok {
+		either_implicit_selector = true;
+	}
+
+	#partial switch binary.op.kind {
+	case .Ellipsis:
+		push_generic_token(p, binary.op.kind, 1 if either_implicit_selector else 0,
+		                   tokenizer.tokens[tokenizer.Token_Kind.Range_Full]);
+	case .Range_Half, .Range_Full:
+		push_generic_token(p, binary.op.kind, 1 if either_implicit_selector else 0);
+	case:
+		push_generic_token(p, binary.op.kind, 1);
+	}
+
+	move_line(p, binary.right.pos);
+
+
+	if v, ok := binary.right.derived.(ast.Binary_Expr); ok {
+		visit_binary_expr(p, v);
+	} else {
+		visit_expr(p, binary.right);
+	}
+}
+
+visit_call_exprs :: proc(p: ^Printer, list: []^ast.Expr, ellipsis := false) {
+	if len(list) == 0 {
+		return;
+	}
+
+	// all the expression are on the line
+	if list[0].pos.line == list[len(list) - 1].pos.line {
+		for expr, i in list {
+			if i == len(list) - 1 && ellipsis {
+				push_generic_token(p, .Ellipsis, 0);
+			}
+
+			visit_expr(p, expr);
+
+			if i != len(list) - 1 {
+				push_generic_token(p, .Comma, 0);
+			}
+		}
+	} else {
+		for expr, i in list {
+			// we have to newline the expressions to respect the source
+			move_line_limit(p, expr.pos, 1);
+
+			if i == len(list) - 1 && ellipsis {
+				push_generic_token(p, .Ellipsis, 0);
+			}
+
+			visit_expr(p, expr);
+
+			if i != len(list) - 1 {
+				push_generic_token(p, .Comma, 0);
+			}
+		}
+	}
+}
+
+visit_signature_list :: proc(p: ^Printer, list: ^ast.Field_List, remove_blank := true) {
+	if list.list == nil {
+		return;
+	}
+
+	for field, i in list.list {
+		if i != 0 {
+			move_line_limit(p, field.pos, 1);
+		}
+
+		if .Using in field.flags {
+			push_generic_token(p, .Using, 0);
+		}
+
+		named := false;
+
+		for name in field.names {
+			if ident, ok := name.derived.(ast.Ident); ok {
+				//for some reason the parser uses _ to mean empty
+				if ident.name != "_" || !remove_blank {
+					named = true;
+				}
+			} else {
+				//alternative is poly names
+				named = true;
+			}
+		}
+
+		if named {
+			visit_exprs(p, field.names, {.Add_Comma});
+
+			if len(field.names) != 0 && field.type != nil {
+				push_generic_token(p, .Colon, 0);
+			}
+		}
+
+		if field.type != nil && field.default_value != nil {
+			visit_expr(p, field.type);
+			push_generic_token(p, .Eq, 1);
+			visit_expr(p, field.default_value);
+		} else if field.type != nil {
+			visit_expr(p, field.type);
+		} else {
+			push_generic_token(p, .Colon, 1);
+			push_generic_token(p, .Eq, 0);
+			visit_expr(p, field.default_value);
+		}
+
+		if i != len(list.list) - 1 {
+			push_generic_token(p, .Comma, 0);
+		}
+	}
+}

+ 2 - 0
core/odin/tokenizer/token.odin

@@ -107,6 +107,7 @@ Token_Kind :: enum u32 {
 		Comma,         // ,
 		Ellipsis,      // ..
 		Range_Half,    // ..<
+		Range_Full,    // ..=
 		Back_Slash,    // \
 	B_Operator_End,
 
@@ -233,6 +234,7 @@ tokens := [Token_Kind.COUNT]string {
 	",",
 	"..",
 	"..<",
+	"..=",
 	"\\",
 	"",
 

+ 7 - 4
core/odin/tokenizer/tokenizer.odin

@@ -14,7 +14,7 @@ Flags :: distinct bit_set[Flag; u32];
 Tokenizer :: struct {
 	// Immutable data
 	path: string,
-	src:  []byte,
+	src:  string,
 	err:  Error_Handler,
 
 	flags: Flags,
@@ -31,7 +31,7 @@ Tokenizer :: struct {
 	error_count: int,
 }
 
-init :: proc(t: ^Tokenizer, src: []byte, path: string, err: Error_Handler = default_error_handler) {
+init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = default_error_handler) {
 	t.src = src;
 	t.err = err;
 	t.ch = ' ';
@@ -87,7 +87,7 @@ advance_rune :: proc(using t: ^Tokenizer) {
 		case r == 0:
 			error(t, t.offset, "illegal character NUL");
 		case r >= utf8.RUNE_SELF:
-			r, w = utf8.decode_rune(src[read_offset:]);
+			r, w = utf8.decode_rune_in_string(src[read_offset:]);
 			if r == utf8.RUNE_ERROR && w == 1 {
 				error(t, t.offset, "illegal UTF-8 encoding");
 			} else if r == utf8.RUNE_BOM && offset > 0 {
@@ -608,7 +608,7 @@ scan :: proc(t: ^Tokenizer) -> Token {
 				kind = switch3(t, .And, .And_Eq, '&', .Cmp_And);
 			}
 		case '|': kind = switch3(t, .Or, .Or_Eq, '|', .Cmp_Or);
-		case '~': kind = .Xor;
+		case '~': kind = switch2(t, .Xor, .Xor_Eq);
 		case '<': kind = switch4(t, .Lt, .Lt_Eq, '<', .Shl, .Shl_Eq);
 		case '>': kind = switch4(t, .Gt, .Gt_Eq, '>', .Shr,.Shr_Eq);
 
@@ -623,6 +623,9 @@ scan :: proc(t: ^Tokenizer) -> Token {
 					if t.ch == '<' {
 						advance_rune(t);
 						kind = .Range_Half;
+					} else if t.ch == '=' {
+						advance_rune(t);
+						kind = .Range_Full;
 					}
 				}
 			}

+ 2 - 2
core/os/file_windows.odin

@@ -273,7 +273,7 @@ is_file :: proc(path: string) -> bool {
 	attribs := win32.GetFileAttributesW(wpath);
 
 	if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
-		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == win32.FILE_ATTRIBUTE_DIRECTORY;
+		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0;
 	}
 	return false;
 }
@@ -283,7 +283,7 @@ is_dir :: proc(path: string) -> bool {
 	attribs := win32.GetFileAttributesW(wpath);
 
 	if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
-		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != win32.FILE_ATTRIBUTE_DIRECTORY;
+		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0;
 	}
 	return false;
 }

+ 16 - 45
core/os/os2/errors.odin

@@ -1,11 +1,8 @@
 package os2
 
-Platform_Error_Min_Bits :: 32;
+import "core:io"
 
-Error :: enum u64 {
-	None = 0,
-
-	// General Errors
+General_Error :: enum u32 {
 	Invalid_Argument,
 
 	Permission_Denied,
@@ -13,42 +10,19 @@ Error :: enum u64 {
 	Not_Exist,
 	Closed,
 
-	// Timeout Errors
 	Timeout,
+}
 
-	// I/O Errors
-	// EOF is the error returned by `read` when no more input is available
-	EOF,
-
-	// Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
-	Unexpected_EOF,
-
-	// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
-	Short_Write,
-
-	// Invalid_Write means that a write returned an impossible count
-	Invalid_Write,
-
-	// Short_Buffer means that a read required a longer buffer than was provided
-	Short_Buffer,
-
-	// No_Progress is returned by some implementations of `io.Reader` when many calls
-	// to `read` have failed to return any data or error.
-	// This is usually a signed of a broken `io.Reader` implementation
-	No_Progress,
-
-	Invalid_Whence,
-	Invalid_Offset,
-	Invalid_Unread,
-
-	Negative_Read,
-	Negative_Write,
-	Negative_Count,
-	Buffer_Full,
+Platform_Error :: struct {
+	err: i32,
+}
 
-	// Platform Specific Errors
-	Platform_Minimum = 1<<Platform_Error_Min_Bits,
+Error :: union {
+	General_Error,
+	io.Error,
+	Platform_Error,
 }
+#assert(size_of(Error) == size_of(u64));
 
 Path_Error :: struct {
 	op:   string,
@@ -83,20 +57,17 @@ link_error_delete :: proc(lerr: Maybe(Link_Error)) {
 
 
 is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
-	if ferr >= .Platform_Minimum {
-		err = i32(u64(ferr)>>Platform_Error_Min_Bits);
-		ok = true;
+	v: Platform_Error;
+	if v, ok = ferr.(Platform_Error); ok {
+		err = v.err;
 	}
 	return;
 }
 
-error_from_platform_error :: proc(errno: i32) -> Error {
-	return Error(u64(errno) << Platform_Error_Min_Bits);
-}
 
 error_string :: proc(ferr: Error) -> string {
-	#partial switch ferr {
-	case .None:              return "";
+	switch ferr {
+	case nil:                return "";
 	case .Invalid_Argument:  return "invalid argument";
 	case .Permission_Denied: return "permission denied";
 	case .Exist:             return "file already exists";

+ 7 - 16
core/os/os2/file_stream.odin

@@ -10,23 +10,14 @@ file_to_stream :: proc(fd: Handle) -> (s: io.Stream) {
 
 @(private)
 error_to_io_error :: proc(ferr: Error) -> io.Error {
-	#partial switch ferr {
-	case .None:           return .None;
-	case .EOF:            return .EOF;
-	case .Unexpected_EOF: return .Unexpected_EOF;
-	case .Short_Write:    return .Short_Write;
-	case .Invalid_Write:  return .Invalid_Write;
-	case .Short_Buffer:   return .Short_Buffer;
-	case .No_Progress:    return .No_Progress;
-	case .Invalid_Whence: return .Invalid_Whence;
-	case .Invalid_Offset: return .Invalid_Offset;
-	case .Invalid_Unread: return .Invalid_Unread;
-	case .Negative_Read:  return .Negative_Read;
-	case .Negative_Write: return .Negative_Write;
-	case .Negative_Count: return .Negative_Count;
-	case .Buffer_Full:    return .Buffer_Full;
+	if ferr == nil {
+		return .None;
 	}
-	return .Unknown;
+	err, ok := ferr.(io.Error);
+	if !ok {
+		err = .Unknown;
+	}
+	return err;
 }
 
 

+ 1 - 0
core/os/os2/file_util.odin

@@ -1,6 +1,7 @@
 package os2
 
 import "core:mem"
+import "core:io"
 import "core:strconv"
 import "core:unicode/utf8"
 

+ 10 - 10
core/os/os2/file_windows.odin

@@ -5,19 +5,19 @@ import "core:io"
 import "core:time"
 
 _create :: proc(name: string) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 
 _open :: proc(name: string) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 
 _open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 
 _close :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 
 _name :: proc(fd: Handle, allocator := context.allocator) -> string {
@@ -58,11 +58,11 @@ _file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
 
 
 _sync :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 
 _flush :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 
 _truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
@@ -92,20 +92,20 @@ _read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
 
 
 _chdir :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 
 _chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
-	return .None;
+	return nil;
 }
 
 _chown :: proc(fd: Handle, uid, gid: int) -> Error {
-	return .None;
+	return nil;
 }
 
 
 _lchown :: proc(name: string, uid, gid: int) -> Error {
-	return .None;
+	return nil;
 }
 
 

+ 1 - 1
core/os/os2/pipe_windows.odin

@@ -6,7 +6,7 @@ import win32 "core:sys/windows"
 _pipe :: proc() -> (r, w: Handle, err: Error) {
 	p: [2]win32.HANDLE;
 	if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
-		return 0, 0, error_from_platform_error(i32(win32.GetLastError()));
+		return 0, 0, Platform_Error{i32(win32.GetLastError())};
 	}
 	return Handle(p[0]), Handle(p[1]), nil;
 }

+ 3 - 3
core/os/os2/stat_windows.odin

@@ -40,7 +40,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool {
 
 
 _stat_errno :: proc(errno: win32.DWORD) -> Path_Error {
-	return Path_Error{err = error_from_platform_error(i32(errno))};
+	return Path_Error{err = Platform_Error{i32(errno)}};
 }
 
 
@@ -89,7 +89,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co
 		fd: win32.WIN32_FIND_DATAW;
 		sh := win32.FindFirstFileW(wname, &fd);
 		if sh == win32.INVALID_HANDLE_VALUE {
-			e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
+			e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}};
 			return;
 		}
 		win32.FindClose(sh);
@@ -99,7 +99,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co
 
 	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil);
 	if h == win32.INVALID_HANDLE_VALUE {
-		e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
+		e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}};
 		return;
 	}
 	defer win32.CloseHandle(h);

+ 2 - 2
core/os/os2/temp_file_windows.odin

@@ -4,11 +4,11 @@ package os2
 import win32 "core:sys/windows"
 
 _create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 
 _mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
-	return "", .None;
+	return "", nil;
 }
 
 _temp_dir :: proc(allocator := context.allocator) -> string {

+ 1 - 1
core/os/os_freebsd.odin

@@ -10,7 +10,7 @@ import "core:c"
 Handle :: distinct i32;
 File_Time :: distinct u64;
 Errno :: distinct i32;
-Syscall :: distinct int;
+Syscall :: distinct i32;
 
 INVALID_HANDLE :: ~Handle(0);
 

+ 3 - 3
core/os/os_linux.odin

@@ -11,7 +11,7 @@ import "core:strconv"
 Handle    :: distinct i32;
 File_Time :: distinct u64;
 Errno     :: distinct i32;
-Syscall   :: distinct int;
+Syscall   :: distinct i32;
 
 INVALID_HANDLE :: ~Handle(0);
 
@@ -269,7 +269,7 @@ SYS_GETTID: Syscall : 186;
 
 foreign libc {
 	@(link_name="__errno_location") __errno_location    :: proc() -> ^int ---;
-	@(link_name="syscall")          syscall             :: proc(number: Syscall, #c_vararg args: ..any) -> int ---;
+	@(link_name="syscall")          syscall             :: proc(number: Syscall, #c_vararg args: ..any) -> i32 ---;
 
 	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---;
 	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---;
@@ -595,7 +595,7 @@ exit :: proc "contextless" (code: int) -> ! {
 }
 
 current_thread_id :: proc "contextless" () -> int {
-	return syscall(SYS_GETTID);
+	return cast(int)syscall(SYS_GETTID);
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {

+ 41 - 2
core/runtime/default_allocators.odin

@@ -1,6 +1,6 @@
 package runtime
 
-when ODIN_DEFAULT_TO_NIL_ALLOCATOR || ODIN_OS == "freestanding" {
+when ODIN_DEFAULT_TO_NIL_ALLOCATOR || ODIN_OS == "freestanding" || ODIN_OS == "js" {
 	// mem.nil_allocator reimplementation
 
 	default_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
@@ -15,7 +15,46 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR || ODIN_OS == "freestanding" {
 			data = nil,
 		};
 	}
-} else when ODIN_OS != "windows" {
+
+} else when ODIN_OS == "windows" {
+	default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
+	                                size, alignment: int,
+	                                old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
+		switch mode {
+		case .Alloc:
+			return _windows_default_alloc(size, alignment);
+
+		case .Free:
+			_windows_default_free(old_memory);
+
+		case .Free_All:
+			// NOTE(tetra): Do nothing.
+
+		case .Resize:
+			return _windows_default_resize(old_memory, old_size, size, alignment);
+
+		case .Query_Features:
+			set := (^Allocator_Mode_Set)(old_memory);
+			if set != nil {
+				set^ = {.Alloc, .Free, .Resize, .Query_Features};
+			}
+			return nil, nil;
+
+		case .Query_Info:
+			return nil, nil;
+		}
+
+		return nil, nil;
+	}
+
+	default_allocator :: proc() -> Allocator {
+		return Allocator{
+			procedure = default_allocator_proc,
+			data = nil,
+		};
+	}
+
+} else {
 	// TODO(bill): reimplement these procedures in the os_specific stuff
 	import "core:os"
 

+ 12 - 33
core/runtime/internal.odin

@@ -97,7 +97,7 @@ mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
 	if len < 0 {
 		return data;
 	}
-	memset(data, 0, len);
+	intrinsics.mem_zero(data, len);
 	return data;
 }
 
@@ -105,17 +105,9 @@ mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr {
 	if src == nil {
 		return dst;
 	}
+
 	// NOTE(bill): This _must_ be implemented like C's memmove
-	foreign _ {
-		when size_of(rawptr) == 8 {
-			@(link_name="llvm.memmove.p0i8.p0i8.i64")
-			llvm_memmove :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
-		} else {
-			@(link_name="llvm.memmove.p0i8.p0i8.i32")
-			llvm_memmove :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
-		}
-	}
-	llvm_memmove(dst, src, len);
+	intrinsics.mem_copy(dst, src, len);
 	return dst;
 }
 
@@ -123,17 +115,9 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r
 	if src == nil {
 		return dst;
 	}
+
 	// NOTE(bill): This _must_ be implemented like C's memcpy
-	foreign _ {
-		when size_of(rawptr) == 8 {
-			@(link_name="llvm.memcpy.p0i8.p0i8.i64")
-			llvm_memcpy :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
-		} else {
-			@(link_name="llvm.memcpy.p0i8.p0i8.i32")
-			llvm_memcpy :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
-		}
-	}
-	llvm_memcpy(dst, src, len);
+	intrinsics.mem_copy_non_overlapping(dst, src, len);
 	return dst;
 }
 
@@ -409,11 +393,6 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int
 	return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4;
 }
 
-@(default_calling_convention = "none")
-foreign {
-	@(link_name="llvm.sqrt.f32") _sqrt_f32 :: proc(x: f32) -> f32 ---
-	@(link_name="llvm.sqrt.f64") _sqrt_f64 :: proc(x: f64) -> f64 ---
-}
 abs_f16 :: #force_inline proc "contextless" (x: f16) -> f16 {
 	return -x if x < 0 else x;
 }
@@ -445,27 +424,27 @@ max_f64 :: proc(a, b: f64) -> f64 {
 
 abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 {
 	r, i := real(x), imag(x);
-	return f16(_sqrt_f32(f32(r*r + i*i)));
+	return f16(intrinsics.sqrt(f32(r*r + i*i)));
 }
 abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 {
 	r, i := real(x), imag(x);
-	return _sqrt_f32(r*r + i*i);
+	return intrinsics.sqrt(r*r + i*i);
 }
 abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 {
 	r, i := real(x), imag(x);
-	return _sqrt_f64(r*r + i*i);
+	return intrinsics.sqrt(r*r + i*i);
 }
 abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 {
 	r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
-	return f16(_sqrt_f32(f32(r*r + i*i + j*j + k*k)));
+	return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k)));
 }
 abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 {
 	r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
-	return _sqrt_f32(r*r + i*i + j*j + k*k);
+	return intrinsics.sqrt(r*r + i*i + j*j + k*k);
 }
 abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 {
 	r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
-	return _sqrt_f64(r*r + i*i + j*j + k*k);
+	return intrinsics.sqrt(r*r + i*i + j*j + k*k);
 }
 
 
@@ -644,7 +623,7 @@ truncsfhf2 :: proc "c" (value: f32) -> u16 {
 		}
 
 		if (e > 30) {
-			f := 1e12;
+			f := i64(1e12);
 			for j := 0; j < 10; j += 1 {
 				/* NOTE(bill): Cause overflow */
 				g := intrinsics.volatile_load(&f);

+ 8 - 0
core/runtime/os_specific.odin

@@ -1,3 +1,11 @@
 package runtime
 
 _OS_Errno :: distinct int;
+
+os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	return _os_write(data);
+}
+
+current_thread_id :: proc "contextless" () -> int {
+	return _current_thread_id();
+}

+ 2 - 2
core/runtime/os_specific_any.odin

@@ -6,12 +6,12 @@ import "core:os"
 
 // TODO(bill): reimplement `os.write` so that it does not rely on package os
 // NOTE: Use os_specific_linux.odin, os_specific_darwin.odin, etc
-os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
 	context = default_context();
 	n, err := os.write(os.stderr, data);
 	return int(n), _OS_Errno(err);
 }
 
-current_thread_id :: proc "contextless" () -> int {
+_current_thread_id :: proc "contextless" () -> int {
 	return os.current_thread_id();
 }

+ 2 - 2
core/runtime/os_specific_freestanding.odin

@@ -2,10 +2,10 @@
 package runtime
 
 // TODO(bill): reimplement `os.write`
-os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
 	return 0, -1;
 }
 
-current_thread_id :: proc "contextless" () -> int {
+_current_thread_id :: proc "contextless" () -> int {
 	return 0;
 }

+ 41 - 71
core/runtime/os_specific_windows.odin

@@ -1,3 +1,4 @@
+//+private
 //+build windows
 package runtime
 
@@ -24,7 +25,7 @@ foreign kernel32 {
 	HeapFree       :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 ---
 }
 
-os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) {
+_os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) {
 	if len(data) == 0 {
 		return 0, 0;
 	}
@@ -58,7 +59,7 @@ os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) {
 	return;
 }
 
-current_thread_id :: proc "contextless" () -> int {
+_current_thread_id :: proc "contextless" () -> int {
 	return int(GetCurrentThreadId());
 }
 
@@ -86,89 +87,58 @@ heap_free :: proc "contextless" (ptr: rawptr) {
 	HeapFree(GetProcessHeap(), 0, ptr);
 }
 
-default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
-                               size, alignment: int,
-                               old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
-
-	//
-	// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
-	// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
-	// padding. We also store the original pointer returned by heap_alloc right before
-	// the pointer we return to the user.
-	//
-
-	aligned_alloc :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, Allocator_Error) {
-		a := max(alignment, align_of(rawptr));
-		space := size + a - 1;
-
-		allocated_mem: rawptr;
-		if old_ptr != nil {
-			original_old_ptr := ptr_offset((^rawptr)(old_ptr), -1)^;
-			allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr));
-		} else {
-			allocated_mem = heap_alloc(space+size_of(rawptr));
-		}
-		aligned_mem := rawptr(ptr_offset((^u8)(allocated_mem), size_of(rawptr)));
 
-		ptr := uintptr(aligned_mem);
-		aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a);
-		diff := int(aligned_ptr - ptr);
-		if (size + diff) > space {
-			return nil, .Out_Of_Memory;
-		}
+//
+// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
+// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
+// padding. We also store the original pointer returned by heap_alloc right before
+// the pointer we return to the user.
+//
 
-		aligned_mem = rawptr(aligned_ptr);
-		ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem;
 
-		return byte_slice(aligned_mem, size), nil;
-	}
 
-	aligned_free :: proc "contextless" (p: rawptr) {
-		if p != nil {
-			heap_free(ptr_offset((^rawptr)(p), -1)^);
-		}
+_windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, Allocator_Error) {
+	if size == 0 {
+		_windows_default_free(old_ptr);
+		return nil, nil;
 	}
 
-	aligned_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) {
-		if p == nil {
-			return nil, nil;
-		}
-		return aligned_alloc(new_size, new_alignment, p);
+	a := max(alignment, align_of(rawptr));
+	space := size + a - 1;
+
+	allocated_mem: rawptr;
+	if old_ptr != nil {
+		original_old_ptr := ptr_offset((^rawptr)(old_ptr), -1)^;
+		allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr));
+	} else {
+		allocated_mem = heap_alloc(space+size_of(rawptr));
 	}
+	aligned_mem := rawptr(ptr_offset((^u8)(allocated_mem), size_of(rawptr)));
 
-	switch mode {
-	case .Alloc:
-		return aligned_alloc(size, alignment);
+	ptr := uintptr(aligned_mem);
+	aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a);
+	diff := int(aligned_ptr - ptr);
+	if (size + diff) > space {
+		return nil, .Out_Of_Memory;
+	}
 
-	case .Free:
-		aligned_free(old_memory);
+	aligned_mem = rawptr(aligned_ptr);
+	ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem;
 
-	case .Free_All:
-		// NOTE(tetra): Do nothing.
+	return byte_slice(aligned_mem, size), nil;
+}
 
-	case .Resize:
-		if old_memory == nil {
-			return aligned_alloc(size, alignment);
-		}
-		return aligned_resize(old_memory, old_size, size, alignment);
+_windows_default_alloc :: proc "contextless" (size, alignment: int) -> ([]byte, Allocator_Error) {
+	return _windows_default_alloc_or_resize(size, alignment, nil);
+}
 
-	case .Query_Features:
-		set := (^Allocator_Mode_Set)(old_memory);
-		if set != nil {
-			set^ = {.Alloc, .Free, .Resize, .Query_Features};
-		}
-		return nil, nil;
 
-	case .Query_Info:
-		return nil, nil;
+_windows_default_free :: proc "contextless" (ptr: rawptr) {
+	if ptr != nil {
+		heap_free(ptr_offset((^rawptr)(ptr), -1)^);
 	}
-
-	return nil, nil;
 }
 
-default_allocator :: proc() -> Allocator {
-	return Allocator{
-		procedure = default_allocator_proc,
-		data = nil,
-	};
+_windows_default_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) {
+	return _windows_default_alloc_or_resize(new_size, new_alignment, p);
 }

+ 9 - 5
core/runtime/procs_essence.odin

@@ -1,18 +1,22 @@
 package runtime
 
-import "core:sys/es"
-
 @(link_name="memset")
 memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr {
-	return es.CRTmemset(ptr, val, len);
+	addr := 0x1000 + 196 * size_of(int);
+	fp := (rawptr(((^uintptr)(uintptr(addr)))^));
+	return ((proc "c" (rawptr, i32, int) -> rawptr)(fp))(ptr, val, len);
 }
 
 @(link_name="memmove")
 memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
-	return es.CRTmemmove(dst, src, len);
+	addr := 0x1000 + 195 * size_of(int);
+	fp := (rawptr(((^uintptr)(uintptr(addr)))^));
+	return ((proc "c" (rawptr, rawptr, int) -> rawptr)(fp))(dst, src, len);
 }
 
 @(link_name="memcpy")
 memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
-	return es.CRTmemcpy(dst, src, len);
+	addr := 0x1000 + 194 * size_of(int);
+	fp := (rawptr(((^uintptr)(uintptr(addr)))^));
+	return ((proc "c" (rawptr, rawptr, int) -> rawptr)(fp))(dst, src, len);
 }

+ 1 - 1
core/strings/builder.odin

@@ -221,7 +221,7 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
 }
 
 
-@(private, static)
+@(private)
 DIGITS_LOWER := "0123456789abcdefx";
 
 write_quoted_string :: proc{

+ 8 - 0
core/strings/strings.odin

@@ -541,6 +541,14 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator) ->
 	return;
 }
 
+remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) {
+	return replace(s, key, "", n, allocator);
+}
+
+remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) {
+	return remove(s, key, -1, allocator);
+}
+
 @(private) _ascii_space := [256]u8{'\t' = 1, '\n' = 1, '\v' = 1, '\f' = 1, '\r' = 1, ' ' = 1};
 
 

+ 2 - 0
core/sync/sync2/atomic.odin

@@ -56,6 +56,7 @@ atomic_exchange_release :: intrinsics.atomic_xchg_rel;
 atomic_exchange_acqrel  :: intrinsics.atomic_xchg_acqrel;
 atomic_exchange_relaxed :: intrinsics.atomic_xchg_relaxed;
 
+// Returns value and optional ok boolean
 atomic_compare_exchange_strong                     :: intrinsics.atomic_cxchg;
 atomic_compare_exchange_strong_acquire             :: intrinsics.atomic_cxchg_acq;
 atomic_compare_exchange_strong_release             :: intrinsics.atomic_cxchg_rel;
@@ -66,6 +67,7 @@ atomic_compare_exchange_strong_failacquire         :: intrinsics.atomic_cxchg_fa
 atomic_compare_exchange_strong_acquire_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed;
 atomic_compare_exchange_strong_acqrel_failrelaxed  :: intrinsics.atomic_cxchg_acqrel_failrelaxed;
 
+// Returns value and optional ok boolean
 atomic_compare_exchange_weak                     :: intrinsics.atomic_cxchgweak;
 atomic_compare_exchange_weak_acquire             :: intrinsics.atomic_cxchgweak_acq;
 atomic_compare_exchange_weak_release             :: intrinsics.atomic_cxchgweak_rel;

+ 0 - 886
core/sync/sync2/channel.odin

@@ -1,886 +0,0 @@
-package sync2
-
-// TODO(bill): The Channel implementation needs a complete rewrite for this new package sync design
-// Especially how the `select` things work
-
-import "core:mem"
-import "core:time"
-import "core:math/rand"
-
-_, _ :: time, rand;
-
-Channel_Direction :: enum i8 {
-	Both =  0,
-	Send = +1,
-	Recv = -1,
-}
-
-Channel :: struct(T: typeid, Direction := Channel_Direction.Both) {
-	using _internal: ^Raw_Channel,
-}
-
-channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) {
-	context.allocator = allocator;
-	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
-	return;
-}
-
-channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) {
-	context.allocator = allocator;
-	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
-	return;
-}
-
-channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) {
-	context.allocator = allocator;
-	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
-	return;
-}
-channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) {
-	context.allocator = allocator;
-	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
-	return;
-}
-
-channel_destroy :: proc(ch: $C/Channel($T, $D)) {
-	raw_channel_destroy(ch._internal);
-}
-
-channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) {
-	res._internal = ch._internal;
-	return;
-}
-
-channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) {
-	res._internal = ch._internal;
-	return;
-}
-
-
-channel_len :: proc(ch: $C/Channel($T, $D)) -> int {
-	return ch._internal.len if ch._internal != nil else 0;
-}
-channel_cap :: proc(ch: $C/Channel($T, $D)) -> int {
-	return ch._internal.cap if ch._internal != nil else 0;
-}
-
-
-channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both {
-	msg := msg;
-	_ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc);
-}
-channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both {
-	msg := msg;
-	return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc);
-}
-
-channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both {
-	c := ch._internal;
-	if c == nil {
-		panic(message="cannot recv message; channel is nil", loc=loc);
-	}
-	mutex_lock(&c.mutex);
-	raw_channel_recv_impl(c, &msg, loc);
-	mutex_unlock(&c.mutex);
-	return;
-}
-channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both {
-	c := ch._internal;
-	if c != nil && mutex_try_lock(&c.mutex) {
-		if c.len > 0 {
-			raw_channel_recv_impl(c, &msg, loc);
-			ok = true;
-		}
-		mutex_unlock(&c.mutex);
-	}
-	return;
-}
-channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both {
-	res: T;
-	res, ok = channel_try_recv(ch, loc);
-	if ok && msg != nil {
-		msg^ = res;
-	}
-	return;
-}
-
-
-channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool {
-	return ch._internal == nil;
-}
-channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool {
-	c := ch._internal;
-	return c != nil && !c.closed;
-}
-
-
-channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool {
-	return a._internal == b._internal;
-}
-channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool {
-	return a._internal != b._internal;
-}
-
-
-channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both {
-	return raw_channel_can_send(ch._internal);
-}
-channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both {
-	return raw_channel_can_recv(ch._internal);
-}
-
-
-channel_peek :: proc(ch: $C/Channel($T, $D)) -> int {
-	c := ch._internal;
-	if c == nil {
-		return -1;
-	}
-	if atomic_load(&c.closed) {
-		return -1;
-	}
-	return atomic_load(&c.len);
-}
-
-
-channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) {
-	raw_channel_close(ch._internal, loc);
-}
-
-
-channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both {
-	c := ch._internal;
-	if c == nil {
-		return;
-	}
-
-	if !c.closed || c.len > 0 {
-		msg, ok = channel_recv(ch), true;
-	}
-	return;
-}
-channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both {
-	raw_channel_drain(ch._internal);
-}
-
-
-channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both {
-	for msg in channel_iterator(src) {
-		channel_send(dst, msg);
-	}
-}
-
-
-Raw_Channel_Wait_Queue :: struct {
-	next: ^Raw_Channel_Wait_Queue,
-	state: ^uintptr,
-}
-
-
-Raw_Channel :: struct {
-	closed:      bool,
-	ready:       bool, // ready to recv
-	data_offset: u16,  // data is stored at the end of this data structure
-	elem_size:   u32,
-	len, cap:    int,
-	read, write: int,
-	mutex:       Mutex,
-	cond:        Cond,
-	allocator:   mem.Allocator,
-
-	sendq: ^Raw_Channel_Wait_Queue,
-	recvq: ^Raw_Channel_Wait_Queue,
-}
-
-raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
-	val.next = head^;
-	head^ = val;
-}
-raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
-	p := head;
-	for p^ != nil && p^ != val {
-		p = &p^.next;
-	}
-	if p != nil {
-		p^ = p^.next;
-	}
-}
-
-
-raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel {
-	assert(int(u32(elem_size)) == elem_size);
-
-	s := size_of(Raw_Channel);
-	s = mem.align_forward_int(s, elem_align);
-	data_offset := uintptr(s);
-	s += elem_size * max(cap, 1);
-
-	a := max(elem_align, align_of(Raw_Channel));
-
-	c := (^Raw_Channel)(mem.alloc(s, a));
-	if c == nil {
-		return nil;
-	}
-
-	c.data_offset = u16(data_offset);
-	c.elem_size = u32(elem_size);
-	c.len, c.cap = 0, max(cap, 0);
-	c.read, c.write = 0, 0;
-	c.allocator = context.allocator;
-	c.closed = false;
-
-	return c;
-}
-
-
-raw_channel_destroy :: proc(c: ^Raw_Channel) {
-	if c == nil {
-		return;
-	}
-	context.allocator = c.allocator;
-	atomic_store(&c.closed, true);
-	free(c);
-}
-
-raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) {
-	if c == nil {
-		panic(message="cannot close nil channel", loc=loc);
-	}
-	mutex_lock(&c.mutex);
-	defer mutex_unlock(&c.mutex);
-	atomic_store(&c.closed, true);
-
-	// Release readers and writers
-	raw_channel_wait_queue_broadcast(c.recvq);
-	raw_channel_wait_queue_broadcast(c.sendq);
-	cond_broadcast(&c.cond);
-}
-
-
-
-raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool {
-	send :: proc(c: ^Raw_Channel, src: rawptr) {
-		data := uintptr(c) + uintptr(c.data_offset);
-		dst := data + uintptr(c.write * int(c.elem_size));
-		mem.copy(rawptr(dst), src, int(c.elem_size));
-		c.len += 1;
-		c.write = (c.write + 1) % max(c.cap, 1);
-	}
-
-	switch {
-	case c == nil:
-		panic(message="cannot send message; channel is nil", loc=loc);
-	case c.closed:
-		panic(message="cannot send message; channel is closed", loc=loc);
-	}
-
-	mutex_lock(&c.mutex);
-	defer mutex_unlock(&c.mutex);
-
-	if c.cap > 0 {
-		if !block && c.len >= c.cap {
-			return false;
-		}
-
-		for c.len >= c.cap {
-			cond_wait(&c.cond, &c.mutex);
-		}
-	} else if c.len > 0 { // TODO(bill): determine correct behaviour
-		if !block {
-			return false;
-		}
-		cond_wait(&c.cond, &c.mutex);
-	} else if c.len == 0 && !block {
-		return false;
-	}
-
-	send(c, msg);
-	cond_signal(&c.cond);
-	raw_channel_wait_queue_signal(c.recvq);
-
-	return true;
-}
-
-raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) {
-	recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) {
-		if c.len < 1 {
-			panic(message="cannot recv message; channel is empty", loc=loc);
-		}
-		c.len -= 1;
-
-		data := uintptr(c) + uintptr(c.data_offset);
-		src := data + uintptr(c.read * int(c.elem_size));
-		mem.copy(dst, rawptr(src), int(c.elem_size));
-		c.read = (c.read + 1) % max(c.cap, 1);
-	}
-
-	if c == nil {
-		panic(message="cannot recv message; channel is nil", loc=loc);
-	}
-	atomic_store(&c.ready, true);
-	for c.len < 1 {
-		raw_channel_wait_queue_signal(c.sendq);
-		cond_wait(&c.cond, &c.mutex);
-	}
-	atomic_store(&c.ready, false);
-	recv(c, res, loc);
-	if c.cap > 0 {
-		if c.len == c.cap - 1 {
-			// NOTE(bill): Only signal on the last one
-			cond_signal(&c.cond);
-		}
-	} else {
-		cond_signal(&c.cond);
-	}
-}
-
-
-raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) {
-	if c == nil {
-		return false;
-	}
-	mutex_lock(&c.mutex);
-	switch {
-	case c.closed:
-		ok = false;
-	case c.cap > 0:
-		ok = c.ready && c.len < c.cap;
-	case:
-		ok = c.ready && c.len == 0;
-	}
-	mutex_unlock(&c.mutex);
-	return;
-}
-raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) {
-	if c == nil {
-		return false;
-	}
-	mutex_lock(&c.mutex);
-	ok = c.len > 0;
-	mutex_unlock(&c.mutex);
-	return;
-}
-
-
-raw_channel_drain :: proc(c: ^Raw_Channel) {
-	if c == nil {
-		return;
-	}
-	mutex_lock(&c.mutex);
-	c.len   = 0;
-	c.read  = 0;
-	c.write = 0;
-	mutex_unlock(&c.mutex);
-}
-
-
-
-MAX_SELECT_CHANNELS :: 64;
-SELECT_MAX_TIMEOUT :: max(time.Duration);
-
-Select_Command :: enum {
-	Recv,
-	Send,
-}
-
-Select_Channel :: struct {
-	channel: ^Raw_Channel,
-	command: Select_Command,
-}
-
-
-
-select :: proc(channels: ..Select_Channel) -> (index: int) {
-	return select_timeout(SELECT_MAX_TIMEOUT, ..channels);
-}
-select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) {
-	switch len(channels) {
-	case 0:
-		panic("sync: select with no channels");
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-
-	backing: [MAX_SELECT_CHANNELS]int;
-	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
-	candidates := backing[:];
-	cap := len(channels);
-	candidates = candidates[:cap];
-
-	count := u32(0);
-	for c, i in channels {
-		if c.channel == nil {
-			continue;
-		}
-		switch c.command {
-		case .Recv:
-			if raw_channel_can_recv(c.channel) {
-				candidates[count] = i;
-				count += 1;
-			}
-		case .Send:
-			if raw_channel_can_send(c.channel) {
-				candidates[count] = i;
-				count += 1;
-			}
-		}
-	}
-
-	if count == 0 {
-		wait_state: uintptr = 0;
-		for _, i in channels {
-			q := &queues[i];
-			q.state = &wait_state;
-		}
-
-		for c, i in channels {
-			if c.channel == nil {
-				continue;
-			}
-			q := &queues[i];
-			switch c.command {
-			case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q);
-			case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q);
-			}
-		}
-		raw_channel_wait_queue_wait_on(&wait_state, timeout);
-		for c, i in channels {
-			if c.channel == nil {
-				continue;
-			}
-			q := &queues[i];
-			switch c.command {
-			case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q);
-			case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q);
-			}
-		}
-
-		for c, i in channels {
-			switch c.command {
-			case .Recv:
-				if raw_channel_can_recv(c.channel) {
-					candidates[count] = i;
-					count += 1;
-				}
-			case .Send:
-				if raw_channel_can_send(c.channel) {
-					candidates[count] = i;
-					count += 1;
-				}
-			}
-		}
-		if count == 0 && timeout == SELECT_MAX_TIMEOUT {
-			index = -1;
-			return;
-		}
-
-		assert(count != 0);
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	return;
-}
-
-select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
-	switch len(channels) {
-	case 0:
-		panic("sync: select with no channels");
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-
-	backing: [MAX_SELECT_CHANNELS]int;
-	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
-	candidates := backing[:];
-	cap := len(channels);
-	candidates = candidates[:cap];
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_recv(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		state: uintptr;
-		for c, i in channels {
-			q := &queues[i];
-			q.state = &state;
-			raw_channel_wait_queue_insert(&c.recvq, q);
-		}
-		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
-		for c, i in channels {
-			q := &queues[i];
-			raw_channel_wait_queue_remove(&c.recvq, q);
-		}
-
-		for c, i in channels {
-			if raw_channel_can_recv(c) {
-				candidates[count] = i;
-				count += 1;
-			}
-		}
-		assert(count != 0);
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	return;
-}
-
-select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
-	switch len(channels) {
-	case 0:
-		panic("sync: select with no channels");
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-
-	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
-	candidates: [MAX_SELECT_CHANNELS]int;
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_recv(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		state: uintptr;
-		for c, i in channels {
-			q := &queues[i];
-			q.state = &state;
-			raw_channel_wait_queue_insert(&c.recvq, q);
-		}
-		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
-		for c, i in channels {
-			q := &queues[i];
-			raw_channel_wait_queue_remove(&c.recvq, q);
-		}
-
-		for c, i in channels {
-			if raw_channel_can_recv(c) {
-				candidates[count] = i;
-				count += 1;
-			}
-		}
-		assert(count != 0);
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	msg = channel_recv(channels[index]);
-
-	return;
-}
-
-select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
-	switch len(channels) {
-	case 0:
-		panic("sync: select with no channels");
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-
-	backing: [MAX_SELECT_CHANNELS]int;
-	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
-	candidates := backing[:];
-	cap := len(channels);
-	candidates = candidates[:cap];
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_recv(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		state: uintptr;
-		for c, i in channels {
-			q := &queues[i];
-			q.state = &state;
-			raw_channel_wait_queue_insert(&c.recvq, q);
-		}
-		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
-		for c, i in channels {
-			q := &queues[i];
-			raw_channel_wait_queue_remove(&c.recvq, q);
-		}
-
-		for c, i in channels {
-			if raw_channel_can_recv(c) {
-				candidates[count] = i;
-				count += 1;
-			}
-		}
-		assert(count != 0);
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-
-	if msg != nil {
-		channel_send(channels[index], msg);
-	}
-
-	return;
-}
-
-select_send :: proc(channels: ..^Raw_Channel) -> (index: int) {
-	switch len(channels) {
-	case 0:
-		panic("sync: select with no channels");
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-	candidates: [MAX_SELECT_CHANNELS]int;
-	queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_send(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		state: uintptr;
-		for c, i in channels {
-			q := &queues[i];
-			q.state = &state;
-			raw_channel_wait_queue_insert(&c.sendq, q);
-		}
-		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
-		for c, i in channels {
-			q := &queues[i];
-			raw_channel_wait_queue_remove(&c.sendq, q);
-		}
-
-		for c, i in channels {
-			if raw_channel_can_send(c) {
-				candidates[count] = i;
-				count += 1;
-			}
-		}
-		assert(count != 0);
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	return;
-}
-
-select_try :: proc(channels: ..Select_Channel) -> (index: int) {
-	switch len(channels) {
-	case 0:
-		panic("sync: select with no channels");
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-
-	backing: [MAX_SELECT_CHANNELS]int;
-	candidates := backing[:];
-	cap := len(channels);
-	candidates = candidates[:cap];
-
-	count := u32(0);
-	for c, i in channels {
-		switch c.command {
-		case .Recv:
-			if raw_channel_can_recv(c.channel) {
-				candidates[count] = i;
-				count += 1;
-			}
-		case .Send:
-			if raw_channel_can_send(c.channel) {
-				candidates[count] = i;
-				count += 1;
-			}
-		}
-	}
-
-	if count == 0 {
-		index = -1;
-		return;
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	return;
-}
-
-
-select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
-	switch len(channels) {
-	case 0:
-		index = -1;
-		return;
-	case 1:
-		index = -1;
-		if raw_channel_can_recv(channels[0]) {
-			index = 0;
-		}
-		return;
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-	candidates: [MAX_SELECT_CHANNELS]int;
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_recv(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		index = -1;
-		return;
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	return;
-}
-
-
-select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check {
-	switch len(channels) {
-	case 0:
-		return -1;
-	case 1:
-		if raw_channel_can_send(channels[0]) {
-			return 0;
-		}
-		return -1;
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-	candidates: [MAX_SELECT_CHANNELS]int;
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_send(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		index = -1;
-		return;
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	return;
-}
-
-select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
-	switch len(channels) {
-	case 0:
-		index = -1;
-		return;
-	case 1:
-		ok: bool;
-		if msg, ok = channel_try_recv(channels[0]); ok {
-			index = 0;
-		}
-		return;
-	}
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-	candidates: [MAX_SELECT_CHANNELS]int;
-
-	count := u32(0);
-	for c, i in channels {
-		if channel_can_recv(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		index = -1;
-		return;
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	msg = channel_recv(channels[index]);
-	return;
-}
-
-select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
-	index = -1;
-	switch len(channels) {
-	case 0:
-		return;
-	case 1:
-		if channel_try_send(channels[0], msg) {
-			index = 0;
-		}
-		return;
-	}
-
-
-	assert(len(channels) <= MAX_SELECT_CHANNELS);
-	candidates: [MAX_SELECT_CHANNELS]int;
-
-	count := u32(0);
-	for c, i in channels {
-		if raw_channel_can_send(c) {
-			candidates[count] = i;
-			count += 1;
-		}
-	}
-
-	if count == 0 {
-		index = -1;
-		return;
-	}
-
-	t := time.now();
-	r := rand.create(transmute(u64)t);
-	i := rand.uint32(&r);
-
-	index = candidates[i % count];
-	channel_send(channels[index], msg);
-	return;
-}
-

+ 0 - 17
core/sync/sync2/channel_unix.odin

@@ -1,17 +0,0 @@
-//+build linux, darwin, freebsd
-//+private
-package sync2
-
-import "core:time"
-
-raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
-	// stub
-}
-
-raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
-	// stub
-}
-
-raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
-	// stub
-}

+ 0 - 34
core/sync/sync2/channel_windows.odin

@@ -1,34 +0,0 @@
-//+build windows
-//+private
-package sync2
-
-import win32 "core:sys/windows"
-import "core:time"
-
-raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
-	ms: win32.DWORD = win32.INFINITE;
-	if max(time.Duration) != SELECT_MAX_TIMEOUT {
-		ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000);
-	}
-
-	v := atomic_load(state);
-	for v == 0 {
-		win32.WaitOnAddress(state, &v, size_of(state^), ms);
-		v = atomic_load(state);
-	}
-	atomic_store(state, 0);
-}
-
-raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
-	for x := q; x != nil; x = x.next {
-		atomic_add(x.state, 1);
-		win32.WakeByAddressSingle(x.state);
-	}
-}
-
-raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
-	for x := q; x != nil; x = x.next {
-		atomic_add(x.state, 1);
-		win32.WakeByAddressAll(x.state);
-	}
-}

+ 2 - 2
core/sync/sync2/primitives.odin

@@ -15,7 +15,7 @@ mutex_lock :: proc(m: ^Mutex) {
 	_mutex_lock(m);
 }
 
-// mutex_lock unlocks m
+// mutex_unlock unlocks m
 mutex_unlock :: proc(m: ^Mutex) {
 	_mutex_unlock(m);
 }
@@ -103,7 +103,7 @@ rw_mutex_shared_guard :: proc(m: ^RW_Mutex) -> bool {
 
 
 
-// A Recusrive_Mutex is a recursive mutual exclusion lock
+// A Recursive_Mutex is a recursive mutual exclusion lock
 // The zero value for a Recursive_Mutex is an unlocked mutex
 //
 // A Recursive_Mutex must not be copied after first use

+ 243 - 159
core/sync/sync2/primitives_atomic.odin

@@ -1,159 +1,193 @@
-//+build linux, darwin, freebsd
-//+private
 package sync2
 
-when !#config(ODIN_SYNC_USE_PTHREADS, true) {
-
 import "core:time"
 import "core:runtime"
 
-_Mutex_State :: enum i32 {
+Atomic_Mutex_State :: enum i32 {
 	Unlocked = 0,
 	Locked   = 1,
 	Waiting  = 2,
 }
-_Mutex :: struct {
-	state: _Mutex_State,
-}
 
-_mutex_lock :: proc(m: ^Mutex) {
-	if atomic_xchg_rel(&m.impl.state, .Unlocked) != .Unlocked {
-		_mutex_unlock_slow(m);
-	}
-}
-
-_mutex_unlock :: proc(m: ^Mutex) {
-	switch atomic_xchg_rel(&m.impl.state, .Unlocked) {
-	case .Unlocked:
-		unreachable();
-	case .Locked:
-		// Okay
-	case .Waiting:
-		_mutex_unlock_slow(m);
-	}
-}
 
-_mutex_try_lock :: proc(m: ^Mutex) -> bool {
-	_, ok := atomic_cxchg_acq(&m.impl.state, .Unlocked, .Locked);
-	return ok;
+// An Atomic_Mutex is a mutual exclusion lock
+// The zero value for a Atomic_Mutex is an unlocked mutex
+//
+// An Atomic_Mutex must not be copied after first use
+Atomic_Mutex :: struct {
+	state: Atomic_Mutex_State,
 }
 
-
-@(cold)
-_mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) {
-	new_state := curr_state; // Make a copy of it
-
-	spin_lock: for spin in 0..<i32(100) {
-		state, ok := atomic_cxchgweak_acq(&m.impl.state, .Unlocked, new_state);
-		if ok {
-			return;
+// atomic_mutex_lock locks m
+atomic_mutex_lock :: proc(m: ^Atomic_Mutex) {
+	@(cold)
+	lock_slow :: proc(m: ^Atomic_Mutex, curr_state: Atomic_Mutex_State) {
+		new_state := curr_state; // Make a copy of it
+
+		spin_lock: for spin in 0..<i32(100) {
+			state, ok := atomic_compare_exchange_weak_acquire(&m.state, .Unlocked, new_state);
+			if ok {
+				return;
+			}
+
+			if state == .Waiting {
+				break spin_lock;
+			}
+
+			for i := min(spin+1, 32); i > 0; i -= 1 {
+				cpu_relax();
+			}
 		}
 
-		if state == .Waiting {
-			break spin_lock;
-		}
+		for {
+			if atomic_exchange_acquire(&m.state, .Waiting) == .Unlocked {
+				return;
+			}
 
-		for i := min(spin+1, 32); i > 0; i -= 1 {
+			// TODO(bill): Use a Futex here for Linux to improve performance and error handling
 			cpu_relax();
 		}
 	}
 
-	for {
-		if atomic_xchg_acq(&m.impl.state, .Waiting) == .Unlocked {
-			return;
-		}
 
+	switch v := atomic_exchange_acquire(&m.state, .Locked); v {
+	case .Unlocked:
+		// Okay
+	case: fallthrough;
+	case .Locked, .Waiting:
+		lock_slow(m, v);
+	}
+}
+
+// atomic_mutex_unlock unlocks m
+atomic_mutex_unlock :: proc(m: ^Atomic_Mutex) {
+	@(cold)
+	unlock_slow :: proc(m: ^Atomic_Mutex) {
 		// TODO(bill): Use a Futex here for Linux to improve performance and error handling
-		cpu_relax();
 	}
+
+
+	switch atomic_exchange_release(&m.state, .Unlocked) {
+	case .Unlocked:
+		unreachable();
+	case .Locked:
+		// Okay
+	case .Waiting:
+		unlock_slow(m);
+	}
+}
+
+// atomic_mutex_try_lock tries to lock m, will return true on success, and false on failure
+atomic_mutex_try_lock :: proc(m: ^Atomic_Mutex) -> bool {
+	_, ok := atomic_compare_exchange_strong_acquire(&m.state, .Unlocked, .Locked);
+	return ok;
 }
 
 
-@(cold)
-_mutex_unlock_slow :: proc(m: ^Mutex) {
-	// TODO(bill): Use a Futex here for Linux to improve performance and error handling
+// Example:
+//
+// if atomic_mutex_guard(&m) {
+//         ...
+// }
+//
+@(deferred_in=atomic_mutex_unlock)
+atomic_mutex_guard :: proc(m: ^Atomic_Mutex) -> bool {
+	atomic_mutex_lock(m);
+	return true;
 }
 
 
-RW_Mutex_State :: distinct uint;
-RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2;
-RW_Mutex_State_Is_Writing :: RW_Mutex_State(1);
-RW_Mutex_State_Writer     :: RW_Mutex_State(1)<<1;
-RW_Mutex_State_Reader     :: RW_Mutex_State(1)<<RW_Mutex_State_Half_Width;
+Atomic_RW_Mutex_State :: distinct uint;
+Atomic_RW_Mutex_State_Half_Width :: size_of(Atomic_RW_Mutex_State)*8/2;
+Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1);
+Atomic_RW_Mutex_State_Writer     :: Atomic_RW_Mutex_State(1)<<1;
+Atomic_RW_Mutex_State_Reader     :: Atomic_RW_Mutex_State(1)<<Atomic_RW_Mutex_State_Half_Width;
 
-RW_Mutex_State_Writer_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << 1;
-RW_Mutex_State_Reader_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << RW_Mutex_State_Half_Width;
+Atomic_RW_Mutex_State_Writer_Mask :: Atomic_RW_Mutex_State(1<<(Atomic_RW_Mutex_State_Half_Width-1) - 1) << 1;
+Atomic_RW_Mutex_State_Reader_Mask :: Atomic_RW_Mutex_State(1<<(Atomic_RW_Mutex_State_Half_Width-1) - 1) << Atomic_RW_Mutex_State_Half_Width;
 
 
-_RW_Mutex :: struct {
-	state: RW_Mutex_State,
-	mutex: Mutex,
-	sema:  Sema,
+// An Atomic_RW_Mutex is a reader/writer mutual exclusion lock
+// The lock can be held by any arbitrary number of readers or a single writer
+// The zero value for an Atomic_RW_Mutex is an unlocked mutex
+//
+// An Atomic_RW_Mutex must not be copied after first use
+Atomic_RW_Mutex :: struct {
+	state: Atomic_RW_Mutex_State,
+	mutex: Atomic_Mutex,
+	sema:  Atomic_Sema,
 }
 
-_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
-	_ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer);
-	mutex_lock(&rw.impl.mutex);
+// atomic_rw_mutex_lock locks rw for writing (with a single writer)
+// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available.
+atomic_rw_mutex_lock :: proc(rw: ^Atomic_RW_Mutex) {
+	_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Writer);
+	atomic_mutex_lock(&rw.mutex);
 
-	state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer);
-	if state & RW_Mutex_State_Reader_Mask != 0 {
-		sema_wait(&rw.impl.sema);
+	state := atomic_or(&rw.state, Atomic_RW_Mutex_State_Writer);
+	if state & Atomic_RW_Mutex_State_Reader_Mask != 0 {
+		atomic_sema_wait(&rw.sema);
 	}
 }
 
-_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
-	_ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing);
-	mutex_unlock(&rw.impl.mutex);
+// atomic_rw_mutex_unlock unlocks rw for writing (with a single writer)
+atomic_rw_mutex_unlock :: proc(rw: ^Atomic_RW_Mutex) {
+	_ = atomic_and(&rw.state, ~Atomic_RW_Mutex_State_Is_Writing);
+	atomic_mutex_unlock(&rw.mutex);
 }
 
-_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
-	if mutex_try_lock(&rw.impl.mutex) {
-		state := atomic_load(&rw.impl.state);
-		if state & RW_Mutex_State_Reader_Mask == 0 {
-			_ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing);
+// atomic_rw_mutex_try_lock tries to lock rw for writing (with a single writer)
+atomic_rw_mutex_try_lock :: proc(rw: ^Atomic_RW_Mutex) -> bool {
+	if atomic_mutex_try_lock(&rw.mutex) {
+		state := atomic_load(&rw.state);
+		if state & Atomic_RW_Mutex_State_Reader_Mask == 0 {
+			_ = atomic_or(&rw.state, Atomic_RW_Mutex_State_Is_Writing);
 			return true;
 		}
 
-		mutex_unlock(&rw.impl.mutex);
+		atomic_mutex_unlock(&rw.mutex);
 	}
 	return false;
 }
 
-_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
-	state := atomic_load(&rw.impl.state);
-	for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
+// atomic_rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers)
+atomic_rw_mutex_shared_lock :: proc(rw: ^Atomic_RW_Mutex) {
+	state := atomic_load(&rw.state);
+	for state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 {
 		ok: bool;
-		state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
+		state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader);
 		if ok {
 			return;
 		}
 	}
 
-	mutex_lock(&rw.impl.mutex);
-	_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
-	mutex_unlock(&rw.impl.mutex);
+	atomic_mutex_lock(&rw.mutex);
+	_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader);
+	atomic_mutex_unlock(&rw.mutex);
 }
 
-_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
-	state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader);
+// atomic_rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers)
+atomic_rw_mutex_shared_unlock :: proc(rw: ^Atomic_RW_Mutex) {
+	state := atomic_sub(&rw.state, Atomic_RW_Mutex_State_Reader);
 
-	if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) &&
-	   (state & RW_Mutex_State_Is_Writing != 0) {
-	   	sema_post(&rw.impl.sema);
+	if (state & Atomic_RW_Mutex_State_Reader_Mask == Atomic_RW_Mutex_State_Reader) &&
+	   (state & Atomic_RW_Mutex_State_Is_Writing != 0) {
+	   	atomic_sema_post(&rw.sema);
 	}
 }
 
-_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
-	state := atomic_load(&rw.impl.state);
-	if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
-		_, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader);
+// atomic_rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers)
+atomic_rw_mutex_try_shared_lock :: proc(rw: ^Atomic_RW_Mutex) -> bool {
+	state := atomic_load(&rw.state);
+	if state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 {
+		_, ok := atomic_compare_exchange_strong(&rw.state, state, state + Atomic_RW_Mutex_State_Reader);
 		if ok {
 			return true;
 		}
 	}
-	if mutex_try_lock(&rw.impl.mutex) {
-		_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
-		mutex_unlock(&rw.impl.mutex);
+	if atomic_mutex_try_lock(&rw.mutex) {
+		_ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader);
+		atomic_mutex_unlock(&rw.mutex);
 		return true;
 	}
 
@@ -161,127 +195,177 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
 }
 
 
-_Recursive_Mutex :: struct {
+// Example:
+//
+// if atomic_rw_mutex_guard(&m) {
+//         ...
+// }
+//
+@(deferred_in=atomic_rw_mutex_unlock)
+atomic_rw_mutex_guard :: proc(m: ^Atomic_RW_Mutex) -> bool {
+	atomic_rw_mutex_lock(m);
+	return true;
+}
+
+// Example:
+//
+// if atomic_rw_mutex_shared_guard(&m) {
+//         ...
+// }
+//
+@(deferred_in=atomic_rw_mutex_shared_unlock)
+atomic_rw_mutex_shared_guard :: proc(m: ^Atomic_RW_Mutex) -> bool {
+	atomic_rw_mutex_shared_lock(m);
+	return true;
+}
+
+
+
+
+// An Atomic_Recursive_Mutex is a recursive mutual exclusion lock
+// The zero value for a Recursive_Mutex is an unlocked mutex
+//
+// An Atomic_Recursive_Mutex must not be copied after first use
+Atomic_Recursive_Mutex :: struct {
 	owner:     int,
 	recursion: int,
 	mutex: Mutex,
 }
 
-_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
+atomic_recursive_mutex_lock :: proc(m: ^Atomic_Recursive_Mutex) {
 	tid := runtime.current_thread_id();
-	if tid != m.impl.owner {
-		mutex_lock(&m.impl.mutex);
+	if tid != m.owner {
+		mutex_lock(&m.mutex);
 	}
 	// inside the lock
-	m.impl.owner = tid;
-	m.impl.recursion += 1;
+	m.owner = tid;
+	m.recursion += 1;
 }
 
-_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
+atomic_recursive_mutex_unlock :: proc(m: ^Atomic_Recursive_Mutex) {
 	tid := runtime.current_thread_id();
-	assert(tid == m.impl.owner);
-	m.impl.recursion -= 1;
-	recursion := m.impl.recursion;
+	assert(tid == m.owner);
+	m.recursion -= 1;
+	recursion := m.recursion;
 	if recursion == 0 {
-		m.impl.owner = 0;
+		m.owner = 0;
 	}
 	if recursion == 0 {
-		mutex_unlock(&m.impl.mutex);
+		mutex_unlock(&m.mutex);
 	}
 	// outside the lock
 
 }
 
-_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
+atomic_recursive_mutex_try_lock :: proc(m: ^Atomic_Recursive_Mutex) -> bool {
 	tid := runtime.current_thread_id();
-	if m.impl.owner == tid {
-		return mutex_try_lock(&m.impl.mutex);
+	if m.owner == tid {
+		return mutex_try_lock(&m.mutex);
 	}
-	if !mutex_try_lock(&m.impl.mutex) {
+	if !mutex_try_lock(&m.mutex) {
 		return false;
 	}
 	// inside the lock
-	m.impl.owner = tid;
-	m.impl.recursion += 1;
+	m.owner = tid;
+	m.recursion += 1;
 	return true;
 }
 
 
+// Example:
+//
+// if atomic_recursive_mutex_guard(&m) {
+//         ...
+// }
+//
+@(deferred_in=atomic_recursive_mutex_unlock)
+atomic_recursive_mutex_guard :: proc(m: ^Atomic_Recursive_Mutex) -> bool {
+	atomic_recursive_mutex_lock(m);
+	return true;
+}
 
 
 
+
+@(private="file")
 Queue_Item :: struct {
 	next: ^Queue_Item,
 	futex: i32,
 }
 
+@(private="file")
 queue_item_wait :: proc(item: ^Queue_Item) {
-	for atomic_load_acq(&item.futex) == 0 {
+	for atomic_load_acquire(&item.futex) == 0 {
 		// TODO(bill): Use a Futex here for Linux to improve performance and error handling
 		cpu_relax();
 	}
 }
+@(private="file")
 queue_item_signal :: proc(item: ^Queue_Item) {
-	atomic_store_rel(&item.futex, 1);
+	atomic_store_release(&item.futex, 1);
 	// TODO(bill): Use a Futex here for Linux to improve performance and error handling
 }
 
 
-_Cond :: struct {
-	queue_mutex: Mutex,
+// Atomic_Cond implements a condition variable, a rendezvous point for threads
+// waiting for signalling the occurence of an event
+//
+// An Atomic_Cond must not be copied after first use
+Atomic_Cond :: struct {
+	queue_mutex: Atomic_Mutex,
 	queue_head:  ^Queue_Item,
 	pending:     bool,
 }
 
-_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
+atomic_cond_wait :: proc(c: ^Atomic_Cond, m: ^Atomic_Mutex) {
 	waiter := &Queue_Item{};
 
-	mutex_lock(&c.impl.queue_mutex);
-	waiter.next = c.impl.queue_head;
-	c.impl.queue_head = waiter;
+	atomic_mutex_lock(&c.queue_mutex);
+	waiter.next = c.queue_head;
+	c.queue_head = waiter;
 
-	atomic_store(&c.impl.pending, true);
-	mutex_unlock(&c.impl.queue_mutex);
+	atomic_store(&c.pending, true);
+	atomic_mutex_unlock(&c.queue_mutex);
 
-	mutex_unlock(m);
+	atomic_mutex_unlock(m);
 	queue_item_wait(waiter);
-	mutex_lock(m);
+	atomic_mutex_lock(m);
 }
 
-_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
+atomic_cond_wait_with_timeout :: proc(c: ^Atomic_Cond, m: ^Atomic_Mutex, timeout: time.Duration) -> bool {
 	// TODO(bill): _cond_wait_with_timeout for unix
 	return false;
 }
 
-_cond_signal :: proc(c: ^Cond) {
-	if !atomic_load(&c.impl.pending) {
+atomic_cond_signal :: proc(c: ^Atomic_Cond) {
+	if !atomic_load(&c.pending) {
 		return;
 	}
 
-	mutex_lock(&c.impl.queue_mutex);
-	waiter := c.impl.queue_head;
-	if c.impl.queue_head != nil {
-		c.impl.queue_head = c.impl.queue_head.next;
+	atomic_mutex_lock(&c.queue_mutex);
+	waiter := c.queue_head;
+	if c.queue_head != nil {
+		c.queue_head = c.queue_head.next;
 	}
-	atomic_store(&c.impl.pending, c.impl.queue_head != nil);
-	mutex_unlock(&c.impl.queue_mutex);
+	atomic_store(&c.pending, c.queue_head != nil);
+	atomic_mutex_unlock(&c.queue_mutex);
 
 	if waiter != nil {
 		queue_item_signal(waiter);
 	}
 }
 
-_cond_broadcast :: proc(c: ^Cond) {
-	if !atomic_load(&c.impl.pending) {
+atomic_cond_broadcast :: proc(c: ^Atomic_Cond) {
+	if !atomic_load(&c.pending) {
 		return;
 	}
 
-	atomic_store(&c.impl.pending, false);
+	atomic_store(&c.pending, false);
 
-	mutex_lock(&c.impl.queue_mutex);
-	waiters := c.impl.queue_head;
-	c.impl.queue_head = nil;
-	mutex_unlock(&c.impl.queue_mutex);
+	atomic_mutex_lock(&c.queue_mutex);
+	waiters := c.queue_head;
+	c.queue_head = nil;
+	atomic_mutex_unlock(&c.queue_mutex);
 
 	for waiters != nil {
 		queue_item_signal(waiters);
@@ -289,35 +373,35 @@ _cond_broadcast :: proc(c: ^Cond) {
 	}
 }
 
-_Sema :: struct {
-	mutex: Mutex,
-	cond:  Cond,
+// When waited upon, blocks until the internal count is greater than zero, then subtracts one.
+// Posting to the semaphore increases the count by one, or the provided amount.
+//
+// An Atomic_Sema must not be copied after first use
+Atomic_Sema :: struct {
+	mutex: Atomic_Mutex,
+	cond:  Atomic_Cond,
 	count: int,
 }
 
-_sema_wait :: proc(s: ^Sema) {
-	mutex_lock(&s.impl.mutex);
-	defer mutex_unlock(&s.impl.mutex);
+atomic_sema_wait :: proc(s: ^Atomic_Sema) {
+	atomic_mutex_lock(&s.mutex);
+	defer atomic_mutex_unlock(&s.mutex);
 
-	for s.impl.count == 0 {
-		cond_wait(&s.impl.cond, &s.impl.mutex);
+	for s.count == 0 {
+		atomic_cond_wait(&s.cond, &s.mutex);
 	}
 
-	s.impl.count -= 1;
-	if s.impl.count > 0 {
-		cond_signal(&s.impl.cond);
+	s.count -= 1;
+	if s.count > 0 {
+		atomic_cond_signal(&s.cond);
 	}
 }
 
-_sema_post :: proc(s: ^Sema, count := 1) {
-	mutex_lock(&s.impl.mutex);
-	defer mutex_unlock(&s.impl.mutex);
+atomic_sema_post :: proc(s: ^Atomic_Sema, count := 1) {
+	atomic_mutex_lock(&s.mutex);
+	defer atomic_mutex_unlock(&s.mutex);
 
-	s.impl.count += count;
-	cond_signal(&s.impl.cond);
+	s.count += count;
+	atomic_cond_signal(&s.cond);
 }
 
-
-
-
-} // !ODIN_SYNC_USE_PTHREADS

+ 1 - 1
core/sync/sync2/primitives_pthreads.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, freebsd
 //+private
 package sync2
 

+ 0 - 10
core/unicode/tables.odin

@@ -12,7 +12,6 @@ package unicode
 @(private) pLo    :: pLl | pLu; // a letter that is neither upper nor lower case.
 @(private) pLmask :: pLo;
 
-@(static)
 char_properties := [MAX_LATIN1+1]u8{
 	0x00 = pC,       // '\x00'
 	0x01 = pC,       // '\x01'
@@ -273,7 +272,6 @@ char_properties := [MAX_LATIN1+1]u8{
 };
 
 
-@(static)
 alpha_ranges := [?]i32{
 	0x00d8,  0x00f6,
 	0x00f8,  0x01f5,
@@ -429,7 +427,6 @@ alpha_ranges := [?]i32{
 	0xffda,  0xffdc,
 };
 
-@(static)
 alpha_singlets := [?]i32{
 	0x00aa,
 	0x00b5,
@@ -465,7 +462,6 @@ alpha_singlets := [?]i32{
 	0xfe74,
 };
 
-@(static)
 space_ranges := [?]i32{
 	0x0009,  0x000d, // tab and newline
 	0x0020,  0x0020, // space
@@ -481,7 +477,6 @@ space_ranges := [?]i32{
 	0xfeff,  0xfeff,
 };
 
-@(static)
 unicode_spaces := [?]i32{
 	0x0009, // tab
 	0x000a, // LF
@@ -499,7 +494,6 @@ unicode_spaces := [?]i32{
 	0xfeff, // unknown
 };
 
-@(static)
 to_upper_ranges := [?]i32{
 	0x0061,  0x007a, 468, // a-z A-Z
 	0x00e0,  0x00f6, 468,
@@ -538,7 +532,6 @@ to_upper_ranges := [?]i32{
 	0xff41,  0xff5a, 468,
 };
 
-@(static)
 to_upper_singlets := [?]i32{
 	0x00ff, 621,
 	0x0101, 499,
@@ -882,7 +875,6 @@ to_upper_singlets := [?]i32{
 	0x1ff3, 509,
 };
 
-@(static)
 to_lower_ranges := [?]i32{
 	0x0041,  0x005a, 532, // A-Z a-z
 	0x00c0,  0x00d6, 532, // - -
@@ -922,7 +914,6 @@ to_lower_ranges := [?]i32{
 	0xff21,  0xff3a, 532, // - -
 };
 
-@(static)
 to_lower_singlets := [?]i32{
 	0x0100, 501,
 	0x0102, 501,
@@ -1259,7 +1250,6 @@ to_lower_singlets := [?]i32{
 	0x1ffc, 491,
 };
 
-@(static)
 to_title_singlets := [?]i32{
 	0x01c4, 501,
 	0x01c6, 499,

+ 29 - 7
examples/demo/demo.odin

@@ -1352,8 +1352,8 @@ bit_set_type :: proc() {
 
 		d: Days;
 		d = {Sunday, Monday};
-		e := d | WEEKEND;
-		e |= {Monday};
+		e := d + WEEKEND;
+		e += {Monday};
 		fmt.println(d, e);
 
 		ok := Saturday in e; // `in` is only allowed for `map` and `bit_set` types
@@ -1372,12 +1372,12 @@ bit_set_type :: proc() {
 		fmt.println(typeid_of(type_of(x))); // bit_set[A..Z]
 		fmt.println(typeid_of(type_of(y))); // bit_set[0..8; u16]
 
-		incl(&x, 'F');
+		x += {'F'};
 		assert('F' in x);
-		excl(&x, 'F');
+		x -= {'F'};
 		assert('F' not_in x);
 
-		y |= {1, 4, 2};
+		y += {1, 4, 2};
 		assert(2 in y);
 	}
 	{
@@ -1760,8 +1760,6 @@ range_statements_with_multiple_return_values :: proc() {
 
 
 soa_struct_layout :: proc() {
-	// IMPORTANT NOTE(bill, 2019-11-03): This feature is subject to be changed/removed
-	// NOTE(bill): Most likely #soa [N]T
 	fmt.println("\n#SOA Struct Layout");
 
 	{
@@ -1858,6 +1856,30 @@ soa_struct_layout :: proc() {
 		fmt.println(cap(d));
 		fmt.println(d[:]);
 	}
+	{ // soa_zip and soa_unzip
+		fmt.println("\nsoa_zip and soa_unzip");
+
+		x := []i32{1, 3, 9};
+		y := []f32{2, 4, 16};
+		z := []b32{true, false, true};
+
+		// produce an #soa slice the normal slices passed
+		s := soa_zip(a=x, b=y, c=z);
+
+		// iterate over the #soa slice
+		for v, i in s {
+			fmt.println(v, i); // exactly the same as s[i]
+			// NOTE: 'v' is NOT a temporary value but has a specialized addressing mode
+			// which means that when accessing v.a etc, it does the correct transformation
+			// internally:
+			//         s[i].a === s.a[i]
+			fmt.println(v.a, v.b, v.c);
+		}
+
+		// Recover the slices from the #soa slice
+		a, b, c := soa_unzip(s);
+		fmt.println(a, b, c);
+	}
 }
 
 constant_literal_expressions :: proc() {

+ 5 - 5
examples/demo_insert_semicolon/demo.odin

@@ -1347,8 +1347,8 @@ bit_set_type :: proc() {
 
 		d: Days
 		d = {Sunday, Monday}
-		e := d | WEEKEND
-		e |= {Monday}
+		e := d + WEEKEND
+		e += {Monday}
 		fmt.println(d, e)
 
 		ok := Saturday in e // `in` is only allowed for `map` and `bit_set` types
@@ -1367,12 +1367,12 @@ bit_set_type :: proc() {
 		fmt.println(typeid_of(type_of(x))) // bit_set[A..Z]
 		fmt.println(typeid_of(type_of(y))) // bit_set[0..8; u16]
 
-		incl(&x, 'F')
+		x += {'F'};
 		assert('F' in x)
-		excl(&x, 'F')
+		x -= {'F'};
 		assert('F' not_in x)
 
-		y |= {1, 4, 2}
+		y += {1, 4, 2}
 		assert(2 in y)
 	}
 	{

+ 5 - 0
src/build_settings.cpp

@@ -200,8 +200,10 @@ struct BuildContext {
 	bool   disallow_do;
 	bool   insert_semicolon;
 
+
 	bool   ignore_warnings;
 	bool   warnings_as_errors;
+	bool   show_error_line;
 
 	bool   use_subsystem_windows;
 	bool   ignore_microsoft_magic;
@@ -746,6 +748,9 @@ String get_fullpath_core(gbAllocator a, String path) {
 	return path_to_fullpath(a, res);
 }
 
+bool show_error_line(void) {
+	return build_context.show_error_line;
+}
 
 
 void init_build_context(TargetMetrics *cross_target) {

+ 164 - 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);
@@ -2026,6 +2026,128 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		}
 		break;
 
+	case BuiltinProc_sqrt:
+		{
+			Operand x = {};
+			check_expr(c, &x, ce->args[0]);
+			if (x.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (!is_type_float(x.type)) {
+				gbString xts = type_to_string(x.type);
+				error(x.expr, "Expected a floating point value for '%.*s', got %s", LIT(builtin_procs[id].name), xts);
+				gb_string_free(xts);
+				return false;
+			}
+
+			if (x.mode == Addressing_Constant) {
+				f64 v = exact_value_to_f64(x.value);
+
+				operand->mode = Addressing_Constant;
+				operand->type = x.type;
+				operand->value = exact_value_float(gb_sqrt(v));
+				break;
+			}
+			operand->mode = Addressing_Value;
+			operand->type = default_type(x.type);
+		}
+		break;
+
+	case BuiltinProc_mem_copy:
+	case BuiltinProc_mem_copy_non_overlapping:
+		{
+			operand->mode = Addressing_NoValue;
+			operand->type = t_invalid;
+
+			Operand dst = {};
+			Operand src = {};
+			Operand len = {};
+			check_expr(c, &dst, ce->args[0]);
+			check_expr(c, &src, ce->args[1]);
+			check_expr(c, &len, ce->args[2]);
+			if (dst.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (src.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (len.mode == Addressing_Invalid) {
+				return false;
+			}
+
+
+			if (!is_type_pointer(dst.type)) {
+				gbString str = type_to_string(dst.type);
+				error(dst.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+				gb_string_free(str);
+				return false;
+			}
+			if (!is_type_pointer(src.type)) {
+				gbString str = type_to_string(src.type);
+				error(src.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+				gb_string_free(str);
+				return false;
+			}
+			if (!is_type_integer(len.type)) {
+				gbString str = type_to_string(len.type);
+				error(len.expr, "Expected an integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+				gb_string_free(str);
+				return false;
+			}
+
+			if (len.mode == Addressing_Constant) {
+				i64 n = exact_value_to_i64(len.value);
+				if (n < 0) {
+					gbString str = expr_to_string(len.expr);
+					error(len.expr, "Expected a non-negative integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+					gb_string_free(str);
+				}
+			}
+		}
+		break;
+
+	case BuiltinProc_mem_zero:
+		{
+			operand->mode = Addressing_NoValue;
+			operand->type = t_invalid;
+
+			Operand ptr = {};
+			Operand len = {};
+			check_expr(c, &ptr, ce->args[0]);
+			check_expr(c, &len, ce->args[1]);
+			if (ptr.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (len.mode == Addressing_Invalid) {
+				return false;
+			}
+
+
+			if (!is_type_pointer(ptr.type)) {
+				gbString str = type_to_string(ptr.type);
+				error(ptr.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+				gb_string_free(str);
+				return false;
+			}
+			if (!is_type_integer(len.type)) {
+				gbString str = type_to_string(len.type);
+				error(len.expr, "Expected an integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+				gb_string_free(str);
+				return false;
+			}
+
+			if (len.mode == Addressing_Constant) {
+				i64 n = exact_value_to_i64(len.value);
+				if (n < 0) {
+					gbString str = expr_to_string(len.expr);
+					error(len.expr, "Expected a non-negative integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str);
+					gb_string_free(str);
+				}
+			}
+		}
+		break;
+
+
 	case BuiltinProc_atomic_fence:
 	case BuiltinProc_atomic_fence_acq:
 	case BuiltinProc_atomic_fence_rel:
@@ -2429,6 +2551,46 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		}
 		break;
 
+	case BuiltinProc_type_is_variant_of:
+		{
+			if (operand->mode != Addressing_Type) {
+				error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
+				operand->mode = Addressing_Invalid;
+				operand->type = t_invalid;
+				return false;
+			}
+
+
+			Type *u = operand->type;
+
+			if (!is_type_union(u)) {
+				error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name));
+				operand->mode = Addressing_Invalid;
+				operand->type = t_invalid;
+				return false;
+			}
+
+			Type *v = check_type(c, ce->args[1]);
+
+			u = base_type(u);
+			GB_ASSERT(u->kind == Type_Union);
+
+			bool is_variant = false;
+
+			for_array(i, u->Union.variants) {
+				Type *vt = u->Union.variants[i];
+				if (are_types_identical(v, vt)) {
+					is_variant = true;
+					break;
+				}
+			}
+
+			operand->mode = Addressing_Constant;
+			operand->type = t_untyped_bool;
+			operand->value = exact_value_bool(is_variant);
+		}
+		break;
+
 	case BuiltinProc_type_struct_field_count:
 		operand->value = exact_value_i64(0);
 		if (operand->mode != Addressing_Type) {

+ 18 - 14
src/check_decl.cpp

@@ -289,17 +289,6 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def)
 	if (decl != nullptr) {
 		AttributeContext ac = {};
 		check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
-		if (ac.atom_op_table != nullptr) {
-			Type *bt = base_type(e->type);
-			switch (bt->kind) {
-			case Type_Struct:
-				bt->Struct.atom_op_table = ac.atom_op_table;
-				break;
-			default:
-				error(e->token, "Only struct types can have custom atom operations");
-				break;
-			}
-		}
 	}
 
 
@@ -376,6 +365,7 @@ void override_entity_in_scope(Entity *original_entity, Entity *new_entity) {
 void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) {
 	GB_ASSERT(e->type == nullptr);
 	GB_ASSERT(e->kind == Entity_Constant);
+	init = unparen_expr(init);
 
 	if (e->flags & EntityFlag_Visited) {
 		e->type = t_invalid;
@@ -409,6 +399,18 @@ void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init,
 			e->kind = Entity_TypeName;
 			e->type = nullptr;
 
+			if (entity != nullptr && entity->type != nullptr &&
+			    is_type_polymorphic_record_unspecialized(entity->type)) {
+				DeclInfo *decl = decl_info_of_entity(e);
+				if (decl != nullptr) {
+					if (decl->attributes.count > 0) {
+						error(decl->attributes[0], "Constant alias declarations cannot have attributes");
+					}
+				}
+
+				override_entity_in_scope(e, entity);
+				return;
+			}
 			check_type_decl(ctx, e, ctx->decl->init_expr, named_type);
 			return;
 		}
@@ -897,10 +899,9 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr,
 
 	e->Variable.thread_local_model = ac.thread_local_model;
 	e->Variable.is_export = ac.is_export;
+	e->flags &= ~EntityFlag_Static;
 	if (ac.is_static) {
-		e->flags |= EntityFlag_Static;
-	} else {
-		e->flags &= ~EntityFlag_Static;
+		error(e->token, "@(static) is not supported for global variables, nor required");
 	}
 	ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix);
 
@@ -933,6 +934,9 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr,
 	if (ac.link_name.len > 0) {
 		e->Variable.link_name = ac.link_name;
 	}
+	if (ac.link_section.len > 0) {
+		e->Variable.link_section = ac.link_section;
+	}
 
 	if (e->Variable.is_foreign || e->Variable.is_export) {
 		String name = e->token.string;

+ 96 - 96
src/check_expr.cpp

@@ -621,7 +621,9 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type
 		}
 		PolyProcData poly_proc_data = {};
 		if (check_polymorphic_procedure_assignment(c, operand, type, operand->expr, &poly_proc_data)) {
-			add_entity_use(c, operand->expr, poly_proc_data.gen_entity);
+			Entity *e = poly_proc_data.gen_entity;
+			add_type_and_value(c->info, operand->expr, Addressing_Value, e->type, {});
+			add_entity_use(c, operand->expr, e);
 			return 4;
 		}
 	}
@@ -1113,6 +1115,7 @@ bool check_cycle(CheckerContext *c, Entity *curr, bool report) {
 					error(curr->token, "\t%.*s refers to", LIT(curr->token.string));
 				}
 				error(curr->token, "\t%.*s", LIT(curr->token.string));
+				curr->type = t_invalid;
 			}
 			return true;
 		}
@@ -1130,7 +1133,7 @@ Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Typ
 	Entity *e = scope_lookup(c->scope, name);
 	if (e == nullptr) {
 		if (is_blank_ident(name)) {
-			error(n, "'_' cannot be used as a value type");
+			error(n, "'_' cannot be used as a value");
 		} else {
 			error(n, "Undeclared name: %.*s", LIT(name));
 		}
@@ -2213,6 +2216,10 @@ void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *node, Type *typ
 		return;
 	}
 
+	if (is_type_untyped(y->type)) {
+		convert_to_typed(c, y, t_uint);
+	}
+
 	x->mode = Addressing_Value;
 }
 
@@ -2380,9 +2387,15 @@ bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) {
 		if (core_type(bt)->kind == Type_Basic) {
 			if (check_representable_as_constant(c, x->value, bt, &x->value)) {
 				return true;
-			} else if (is_type_pointer(type) && check_is_castable_to(c, x, type)) {
-				return true;
+			} else if (check_is_castable_to(c, x, type)) {
+				if (is_type_pointer(type)) {
+					return true;
+				}
 			}
+		} else if (check_is_castable_to(c, x, type)) {
+			x->value = {};
+			x->mode = Addressing_Value;
+			return true;
 		}
 	} else if (check_is_castable_to(c, x, type)) {
 		if (x->mode != Addressing_Constant) {
@@ -2392,6 +2405,9 @@ bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) {
 		} else if (is_type_union(type)) {
 			x->mode = Addressing_Value;
 		}
+		if (x->mode == Addressing_Value) {
+			x->value = {};
+		}
 		return true;
 	}
 	return false;
@@ -2874,7 +2890,7 @@ void update_expr_type(CheckerContext *c, Ast *e, Type *type, bool final) {
 		if (token_is_comparison(be->op.kind)) {
 			// NOTE(bill): Do nothing as the types are fine
 		} else if (token_is_shift(be->op.kind)) {
-			update_expr_type(c, be->left,  type, final);
+			update_expr_type(c, be->left, type, final);
 		} else {
 			update_expr_type(c, be->left,  type, final);
 			update_expr_type(c, be->right, type, final);
@@ -3188,8 +3204,8 @@ void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) {
 		break;
 	}
 
-	operand->type = target_type;
 	update_expr_type(c, operand->expr, target_type, true);
+	operand->type = target_type;
 }
 
 bool check_index_value(CheckerContext *c, bool open_range, Ast *index_value, i64 max_count, i64 *value, Type *type_hint=nullptr) {
@@ -3376,7 +3392,7 @@ ExactValue get_constant_field_single(CheckerContext *c, ExactValue value, i32 in
 							GB_ASSERT(bt->kind == Type_EnumeratedArray);
 							corrected_index = index + exact_value_to_i64(bt->EnumeratedArray.min_value);
 						}
-						if (op == Token_Ellipsis) {
+						if (op != Token_RangeHalf) {
 							if (lo <= corrected_index && corrected_index <= hi) {
 								TypeAndValue tav = fv->value->tav;
 								if (success_) *success_ = true;
@@ -3938,6 +3954,16 @@ bool check_assignment_arguments(CheckerContext *ctx, Array<Operand> const &lhs,
 					add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value);
 				}
 
+				if (o.mode == Addressing_OptionalOk && expr->kind == Ast_TypeAssertion) {
+					// NOTE(bill): Used only for optimizations in the backend
+					if (is_blank_ident(lhs[0].expr)) {
+						expr->TypeAssertion.ignores[0] = true;
+					}
+					if (is_blank_ident(lhs[1].expr)) {
+						expr->TypeAssertion.ignores[1] = true;
+					}
+				}
+
 				array_add(operands, val0);
 				array_add(operands, val1);
 				optional_ok = true;
@@ -4052,6 +4078,16 @@ bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count,
 					add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value);
 				}
 
+				if (o.mode == Addressing_OptionalOk && expr->kind == Ast_TypeAssertion) {
+					// NOTE(bill): Used only for optimizations in the backend
+					if (is_blank_ident(lhs[0]->token)) {
+						expr->TypeAssertion.ignores[0] = true;
+					}
+					if (is_blank_ident(lhs[1]->token)) {
+						expr->TypeAssertion.ignores[1] = true;
+					}
+				}
+
 				array_add(operands, val0);
 				array_add(operands, val1);
 				optional_ok = true;
@@ -4076,6 +4112,16 @@ bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count,
 }
 
 
+bool is_expr_constant_zero(Ast *expr) {
+	GB_ASSERT(expr != nullptr);
+	auto v = exact_value_to_integer(expr->tav.value);
+	if (v.kind == ExactValue_Integer) {
+		return big_int_cmp_zero(&v.value_integer) == 0;
+	}
+	return false;
+}
+
+
 CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 	ast_node(ce, CallExpr, call);
 	GB_ASSERT(is_type_proc(proc_type));
@@ -4250,6 +4296,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 							err = CallArgumentError_WrongTypes;
 						}
 					}
+				} else if (show_error) {
+					check_assignment(c, &o, t, str_lit("argument"));
 				}
 				score += s;
 
@@ -4265,7 +4313,10 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 				if (o.mode == Addressing_Type && is_type_typeid(e->type)) {
 					add_type_info_type(c, o.type);
 					add_type_and_value(c->info, o.expr, Addressing_Value, e->type, exact_value_typeid(o.type));
+				} else if (show_error && is_type_untyped(o.type)) {
+					update_expr_type(c, o.expr, t, true);
 				}
+
 			}
 
 			if (variadic) {
@@ -4303,6 +4354,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 							check_assignment(c, &o, t, str_lit("argument"));
 						}
 						err = CallArgumentError_WrongTypes;
+					} else if (show_error) {
+						check_assignment(c, &o, t, str_lit("argument"));
 					}
 					score += s;
 					if (is_type_any(elem)) {
@@ -4311,6 +4364,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 					if (o.mode == Addressing_Type && is_type_typeid(t)) {
 						add_type_info_type(c, o.type);
 						add_type_and_value(c->info, o.expr, Addressing_Value, t, exact_value_typeid(o.type));
+					} else if (show_error && is_type_untyped(o.type)) {
+						update_expr_type(c, o.expr, t, true);
 					}
 				}
 			}
@@ -4525,6 +4580,8 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) {
 						err = CallArgumentError_NoneConstantParameter;
 					}
 				}
+			} else if (show_error) {
+				check_assignment(c, o, e->type, str_lit("procedure argument"));
 			}
 			score += s;
 		}
@@ -5459,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;
@@ -5563,9 +5620,15 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 				}
 				check_expr(c, operand, arg);
 				if (operand->mode != Addressing_Invalid) {
-					check_cast(c, operand, t);
+					if (is_type_polymorphic(t)) {
+						error(call, "A polymorphic type cannot be used in a type conversion");
+					} else {
+						// NOTE(bill): Otherwise the compiler can override the polymorphic type
+						// as it assumes it is determining the type
+						check_cast(c, operand, t);
+					}
 				}
-
+				operand->type = t;
 				break;
 			}
 			}
@@ -5715,7 +5778,7 @@ void check_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *t) {
 		break;
 	case Addressing_Type:
 		if (t == nullptr || !is_type_typeid(t)) {
-			err_str = "is not an expression but a type";
+			err_str = "is not an expression but a type, in this context it is ambiguous";
 		}
 		break;
 	case Addressing_Builtin:
@@ -5903,8 +5966,9 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu
 
 		TokenKind op = Token_Lt;
 		switch (ie->op.kind) {
-		case Token_Ellipsis:  op = Token_LtEq; break;
-		case Token_RangeHalf: op = Token_Lt; break;
+		case Token_Ellipsis:  op = Token_LtEq; break; // ..
+		case Token_RangeFull: op = Token_LtEq; break; // ..=
+		case Token_RangeHalf: op = Token_Lt;   break; // ..<
 		default: error(ie->op, "Invalid range operator"); break;
 		}
 		bool ok = compare_exact_values(op, a, b);
@@ -5915,7 +5979,7 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu
 		}
 
 		ExactValue inline_for_depth = exact_value_sub(b, a);
-		if (ie->op.kind == Token_Ellipsis) {
+		if (ie->op.kind != Token_RangeHalf) {
 			inline_for_depth = exact_value_increment_one(inline_for_depth);
 		}
 
@@ -6127,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;
@@ -6142,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;
@@ -6309,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;
@@ -6321,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;
@@ -7536,47 +7601,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			return Expr_Expr;
 		}
 
-		if (t->kind == Type_Struct) {
-			TypeAtomOpTable *atom_op_table = t->Struct.atom_op_table;
-			if (atom_op_table != nullptr) {
-				if (atom_op_table->op[TypeAtomOp_index_set]) {
-					if (c->assignment_lhs_hint == node) {
-						o->mode = Addressing_AtomOpAssign;
-						o->type = o->type;
-						o->expr = node;
-						return kind;
-					}
-				}
-				if (atom_op_table->op[TypeAtomOp_index_get]) {
-					Entity *e = atom_op_table->op[TypeAtomOp_index_get];
-					if (ie->index == nullptr) {
-						gbString str = expr_to_string(o->expr);
-						error(o->expr, "Missing index for '%s'", str);
-						gb_string_free(str);
-						o->mode = Addressing_Invalid;
-						o->expr = node;
-						return kind;
-					}
-
-					GB_ASSERT(e->identifier != nullptr);
-					Ast *proc_ident = clone_ast(e->identifier);
-
-					auto args = array_make<Ast *>(heap_allocator(), 2);
-					args[0] = ie->expr;
-					args[1] = ie->index;
-
-					GB_ASSERT(c->file != nullptr);
-					Ast *fake_call = ast_call_expr(c->file, proc_ident, args, ie->open, ie->close, {});
-					check_expr_base(c, o, fake_call, type_hint);
-					AtomOpMapEntry entry = {TypeAtomOp_index_get, fake_call};
-					map_set(&c->info->atom_op_map, hash_pointer(node), entry);
-					o->expr = node;
-					return kind;
-				}
-			}
-		}
-
-
 		i64 max_count = -1;
 		bool valid = check_set_index_data(o, t, is_ptr, &max_count, o->type);
 
@@ -7715,37 +7739,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			if (is_type_soa_struct(t)) {
 				valid = true;
 				o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem);
-			} else {
-				TypeAtomOpTable *atom_op_table = t->Struct.atom_op_table;
-				if (atom_op_table != nullptr && atom_op_table->op[TypeAtomOp_slice]) {
-					Entity *e = atom_op_table->op[TypeAtomOp_slice];
-					GB_ASSERT(e->identifier != nullptr);
-					Ast *proc_ident = clone_ast(e->identifier);
-
-					Ast *expr = se->expr;
-					if (o->mode == Addressing_Variable) {
-						expr = ast_unary_expr(c->file, {Token_And, STR_LIT("&")}, expr);
-					} else if (is_type_pointer(o->type)) {
-						// Okay
-					} else {
-						gbString str = expr_to_string(node);
-						error(node, "Cannot slice '%s', value is not addressable", str);
-						gb_string_free(str);
-						o->mode = Addressing_Invalid;
-						o->expr = node;
-						return kind;
-					}
-					auto args = array_make<Ast *>(heap_allocator(), 1);
-					args[0] = expr;
-
-
-					GB_ASSERT(c->file != nullptr);
-					Ast *fake_call = ast_call_expr(c->file, proc_ident, args, se->open, se->close, {});
-					check_expr_base(c, o, fake_call, type_hint);
-					AtomOpMapEntry entry = {TypeAtomOp_slice, fake_call};
-					map_set(&c->info->atom_op_map, hash_pointer(node), entry);
-					valid = true;
-				}
 			}
 			break;
 
@@ -7774,10 +7767,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			return kind;
 		}
 
-		o->mode = Addressing_Value;
-
 		if (se->low == nullptr && se->high != nullptr) {
-			// error(se->interval0, "1st index is required if a 2nd index is specified");
 			// It is okay to continue as it will assume the 1st index is zero
 		}
 
@@ -7812,6 +7802,16 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			}
 		}
 
+		if (max_count < 0)  {
+			if (o->mode == Addressing_Constant) {
+				gbString s = expr_to_string(se->expr);
+				error(se->expr, "Cannot slice constant value '%s'", s);
+				gb_string_free(s);
+			}
+		}
+
+		o->mode = Addressing_Value;
+
 		if (is_type_string(t) && max_count >= 0) {
 			bool all_constant = true;
 			for (isize i = 0; i < gb_count_of(nodes); i++) {
@@ -8152,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);

+ 70 - 55
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);
@@ -939,6 +939,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 				TokenKind upper_op = Token_Invalid;
 				switch (be->op.kind) {
 				case Token_Ellipsis:  upper_op = Token_GtEq; break;
+				case Token_RangeFull: upper_op = Token_GtEq; break;
 				case Token_RangeHalf: upper_op = Token_Gt;   break;
 				default: GB_PANIC("Invalid range operator"); break;
 				}
@@ -960,9 +961,44 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 				Operand b1 = rhs;
 				check_comparison(ctx, &a1, &b1, Token_LtEq);
 
-				add_constant_switch_case(ctx, &seen, lhs);
-				if (upper_op == Token_GtEq) {
-					add_constant_switch_case(ctx, &seen, rhs);
+				if (is_type_enum(x.type)) {
+					// TODO(bill): Fix this logic so it's fast!!!
+
+					i64 v0 = exact_value_to_i64(lhs.value);
+					i64 v1 = exact_value_to_i64(rhs.value);
+					Operand v = {};
+					v.mode = Addressing_Constant;
+					v.type = x.type;
+					v.expr = x.expr;
+
+					Type *bt = base_type(x.type);
+					GB_ASSERT(bt->kind == Type_Enum);
+					for (i64 vi = v0; vi <= v1; vi++) {
+						if (upper_op != Token_GtEq && vi == v1) {
+							break;
+						}
+
+						bool found = false;
+						for_array(j, bt->Enum.fields) {
+							Entity *f = bt->Enum.fields[j];
+							GB_ASSERT(f->kind == Entity_Constant);
+
+							i64 fv = exact_value_to_i64(f->Constant.value);
+							if (fv == vi) {
+								found = true;
+								break;
+							}
+						}
+						if (found) {
+							v.value = exact_value_i64(vi);
+							add_constant_switch_case(ctx, &seen, v);
+						}
+					}
+				} else {
+					add_constant_switch_case(ctx, &seen, lhs);
+					if (upper_op == Token_GtEq) {
+						add_constant_switch_case(ctx, &seen, rhs);
+					}
 				}
 
 				if (is_type_string(x.type)) {
@@ -1400,6 +1436,28 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 			gbString expr_str = expr_to_string(operand.expr);
 			error(node, "Expression is not used: '%s'", expr_str);
 			gb_string_free(expr_str);
+			if (operand.expr->kind == Ast_BinaryExpr) {
+				ast_node(be, BinaryExpr, operand.expr);
+				if (be->op.kind != Token_CmpEq) {
+					break;
+				}
+
+				switch (be->left->tav.mode) {
+				case Addressing_Context:
+				case Addressing_Variable:
+				case Addressing_MapIndex:
+				case Addressing_SoaVariable:
+					{
+						gbString lhs = expr_to_string(be->left);
+						gbString rhs = expr_to_string(be->right);
+						error_line("\tSuggestion: Did you mean to do an assignment?\n", lhs, rhs);
+						error_line("\t            '%s = %s;'\n", lhs, rhs);
+						gb_string_free(rhs);
+						gb_string_free(lhs);
+					}
+					break;
+				}
+			}
 
 			break;
 		}
@@ -1454,53 +1512,6 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 			auto lhs_to_ignore = array_make<bool>(temporary_allocator(), lhs_count);
 
 			isize max = gb_min(lhs_count, rhs_count);
-			// NOTE(bill, 2020-05-02): This is an utter hack to get these custom atom operations working
-			// correctly for assignments
-			for (isize i = 0; i < max; i++) {
-				if (lhs_operands[i].mode == Addressing_AtomOpAssign) {
-					Operand lhs = lhs_operands[i];
-
-					Type *t = base_type(lhs.type);
-					GB_ASSERT(t->kind == Type_Struct);
-					ast_node(ie, IndexExpr, unparen_expr(lhs.expr));
-
-					TypeAtomOpTable *atom_op_table = t->Struct.atom_op_table;
-					GB_ASSERT(atom_op_table->op[TypeAtomOp_index_set] != nullptr);
-					Entity *e = atom_op_table->op[TypeAtomOp_index_set];
-
-					GB_ASSERT(e->identifier != nullptr);
-					Ast *proc_ident = clone_ast(e->identifier);
-					GB_ASSERT(ctx->file != nullptr);
-
-
-					TypeAndValue tv = type_and_value_of_expr(ie->expr);
-					Ast *expr = ie->expr;
-					if (is_type_pointer(tv.type)) {
-						// Okay
-					} else if (tv.mode == Addressing_Variable) {
-						// NOTE(bill): Hack it to take the address instead
-						expr = ast_unary_expr(ctx->file, {Token_And, STR_LIT("&")}, ie->expr);
-					} else {
-						continue;
-					}
-
-					auto args = array_make<Ast *>(heap_allocator(), 3);
-					args[0] = expr;
-					args[1] = ie->index;
-					args[2] = rhs_operands[i].expr;
-
-					Ast *fake_call = ast_call_expr(ctx->file, proc_ident, args, ie->open, ie->close, {});
-					Operand fake_operand = {};
-					fake_operand.expr = lhs.expr;
-					check_expr_base(ctx, &fake_operand, fake_call, nullptr);
-					AtomOpMapEntry entry = {TypeAtomOp_index_set, fake_call};
-					map_set(&ctx->info->atom_op_map, hash_pointer(lhs.expr), entry);
-
-					lhs_to_ignore[i] = true;
-
-				}
-			}
-
 			for (isize i = 0; i < max; i++) {
 				if (lhs_to_ignore[i]) {
 					continue;
@@ -1526,8 +1537,8 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 			}
 			Operand lhs = {Addressing_Invalid};
 			Operand rhs = {Addressing_Invalid};
-			Ast binary_expr = {Ast_BinaryExpr};
-			ast_node(be, BinaryExpr, &binary_expr);
+			Ast *binary_expr = alloc_ast_node(node->file, Ast_BinaryExpr);
+			ast_node(be, BinaryExpr, binary_expr);
 			be->op = op;
 			be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
 			 // NOTE(bill): Only use the first one will be used
@@ -1535,7 +1546,7 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 			be->right = as->rhs[0];
 
 			check_expr(ctx, &lhs, as->lhs[0]);
-			check_binary_expr(ctx, &rhs, &binary_expr, nullptr, true);
+			check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
 			if (rhs.mode == Addressing_Invalid) {
 				return;
 			}
@@ -1632,7 +1643,11 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 		} else {
 			for (isize i = 0; i < result_count; i++) {
 				Entity *e = pt->results->Tuple.variables[i];
-				check_assignment(ctx, &operands[i], e->type, str_lit("return statement"));
+				Operand *o = &operands[i];
+				check_assignment(ctx, o, e->type, str_lit("return statement"));
+				if (is_type_untyped(o->type)) {
+					update_expr_type(ctx, o->expr, e->type, true);
+				}
 			}
 		}
 	case_end;

+ 45 - 43
src/check_type.cpp

@@ -322,19 +322,6 @@ void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_t
 		array_add(&array, e);
 		map_set(&ctx->checker->info.gen_types, hash_pointer(original_type), array);
 	}
-
-	{
-		Type *dst_bt = base_type(named_type);
-		Type *src_bt = base_type(original_type);
-		if ((dst_bt != nullptr && src_bt != nullptr) &&
-		    (dst_bt->kind == src_bt->kind)){
-			if (dst_bt->kind == Type_Struct) {
-				if (dst_bt->Struct.atom_op_table == nullptr) {
-					dst_bt->Struct.atom_op_table = src_bt->Struct.atom_op_table;
-				}
-			}
-		}
-	}
 }
 
 Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *polymorphic_params,
@@ -944,6 +931,7 @@ void check_bit_set_type(CheckerContext *c, Type *type, Type *named_type, Ast *no
 
 		switch (be->op.kind) {
 		case Token_Ellipsis:
+		case Token_RangeFull:
 			if (upper - lower >= bits) {
 				error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, (upper-lower+1));
 			}
@@ -1203,10 +1191,15 @@ 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;
+
+		if (in_type) {
+			check_assignment(ctx, &o, in_type, str_lit("parameter value"));
+		}
+
 	} else {
 		if (in_type) {
 			check_expr_with_type_hint(ctx, &o, expr, in_type);
@@ -1214,6 +1207,11 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 			check_expr(ctx, &o, expr);
 		}
 
+		if (in_type) {
+			check_assignment(ctx, &o, in_type, str_lit("parameter value"));
+		}
+
+
 		if (is_operand_nil(o)) {
 			param_value.kind = ParameterValue_Nil;
 		} else if (o.mode != Addressing_Constant) {
@@ -1221,16 +1219,7 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 				param_value.kind = ParameterValue_Constant;
 				param_value.value = exact_value_procedure(expr);
 			} else {
-				Entity *e = nullptr;
-				// if (o.mode == Addressing_Value && is_type_proc(o.type)) {
-				if (o.mode == Addressing_Value || o.mode == Addressing_Variable) {
-					Operand x = {};
-					if (expr->kind == Ast_Ident) {
-						e = check_ident(ctx, &x, expr, nullptr, nullptr, false);
-					} else if (expr->kind == Ast_SelectorExpr) {
-						e = check_selector(ctx, &x, expr, nullptr);
-					}
-				}
+				Entity *e = entity_from_expr(o.expr);
 
 				if (e != nullptr) {
 					if (e->kind == Entity_Procedure) {
@@ -1253,8 +1242,11 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 				} else if (allow_caller_location && o.mode == Addressing_Context) {
 					param_value.kind = ParameterValue_Value;
 					param_value.ast_value = expr;
+				} else if (o.value.kind != ExactValue_Invalid) {
+					param_value.kind = ParameterValue_Constant;
+					param_value.value = o.value;
 				} else {
-					error(expr, "Default parameter must be a constant");
+					error(expr, "Default parameter must be a constant, %d", o.mode);
 				}
 			}
 		} else {
@@ -1267,12 +1259,14 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 		}
 	}
 
-	if (in_type) {
-		check_assignment(ctx, &o, in_type, str_lit("parameter value"));
+	if (out_type_) {
+		if (in_type != nullptr) {
+			*out_type_ = in_type;
+		} else {
+			*out_type_ = default_type(o.type);
+		}
 	}
 
-	if (out_type_) *out_type_ = default_type(o.type);
-
 	return param_value;
 }
 
@@ -1389,6 +1383,9 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
 				}
 			}
 		}
+
+
+
 		if (type == nullptr) {
 			error(param, "Invalid parameter type");
 			type = t_invalid;
@@ -1408,6 +1405,21 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
 			type = t_invalid;
 		}
 
+		if (is_type_polymorphic(type)) {
+			switch (param_value.kind) {
+			case ParameterValue_Invalid:
+			case ParameterValue_Constant:
+			case ParameterValue_Nil:
+				break;
+			case ParameterValue_Location:
+			case ParameterValue_Value:
+				gbString str = type_to_string(type);
+				error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str);
+				gb_string_free(str);
+				break;
+			}
+		}
+
 
 		if (p->flags&FieldFlag_c_vararg) {
 			if (p->type == nullptr ||
@@ -2517,16 +2529,6 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 					return true;
 				}
 			}
-
-			// if (ctx->type_level == 0 && entity->state == EntityState_InProgress) {
-			// 	error(entity->token, "Illegal declaration cycle of `%.*s`", LIT(entity->token.string));
-			// 	for_array(j, *ctx->type_path) {
-			// 		Entity *k = (*ctx->type_path)[j];
-			// 		error(k->token, "\t%.*s refers to", LIT(k->token.string));
-			// 	}
-			// 	error(entity->token, "\t%.*s", LIT(entity->token.string));
-			// 	*type = t_invalid;
-			// }
 			return true;
 		}
 
@@ -2709,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 {
@@ -2743,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") {
@@ -2768,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 {
@@ -2788,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 {

+ 10 - 200
src/checker.cpp

@@ -2650,6 +2650,16 @@ DECL_ATTRIBUTE_PROC(var_decl_attribute) {
 			error(elem, "Expected a string value for '%.*s'", LIT(name));
 		}
 		return true;
+	} else if (name == "link_section") {
+		if (ev.kind == ExactValue_String) {
+			ac->link_section = ev.value_string;
+			if (!is_foreign_name_valid(ac->link_section)) {
+				error(elem, "Invalid link section: %.*s", LIT(ac->link_section));
+			}
+		} else {
+			error(elem, "Expected a string value for '%.*s'", LIT(name));
+		}
+		return true;
 	}
 	return false;
 }
@@ -2666,206 +2676,6 @@ DECL_ATTRIBUTE_PROC(type_decl_attribute) {
 	if (name == "private") {
 		// NOTE(bill): Handled elsewhere `check_collect_value_decl`
 		return true;
-	} else if (name == "index_get") {
-		if (value != nullptr) {
-			Operand o = {};
-			check_expr_or_type(c, &o, value);
-			Entity *e = entity_of_node(value);
-			if (e != nullptr && e->kind == Entity_Procedure) {
-				if (ac->deferred_procedure.entity != nullptr) {
-					error(elem, "Previous usage of the '%.*s' attribute", LIT(name));
-				}
-
-				bool valid = true;
-
-				{
-					Type *pt = base_type(e->type);
-					GB_ASSERT(pt->kind == Type_Proc);
-
-					if (pt->Proc.result_count == 0) {
-						error(value, "'%s' attribute must return something", LIT(name));
-						valid = false;
-					}
-
-					if (pt->Proc.param_count < 2) {
-						error(value, "'%s' attribute must allow for 2 parameters", LIT(name));
-						valid = false;
-					} else {
-						isize minimum_param_count = 0;
-						for_array(i, pt->Proc.params->Tuple.variables) {
-							Entity *param = pt->Proc.params->Tuple.variables[i];
-							if (param->kind == Entity_Variable) {
-								if (param->Variable.param_value.kind == ParameterValue_Invalid) {
-									minimum_param_count += 1;
-								} else {
-									break;
-								}
-							} else if (param->kind == Entity_Constant) {
-								minimum_param_count += 1;
-							} else {
-								break;
-							}
-						}
-
-						if (minimum_param_count > 2) {
-							error(value, "'%s' attribute must allow for at a minimum 2 parameters", LIT(name));
-							valid = false;
-						}
-					}
-				}
-
-				if (valid) {
-					if (ac->atom_op_table == nullptr) {
-						ac->atom_op_table = gb_alloc_item(permanent_allocator(), TypeAtomOpTable);
-					}
-					ac->atom_op_table->op[TypeAtomOp_index_get] = e;
-				}
-				return true;
-			}
-		}
-		error(elem, "Expected a procedure entity for '%.*s'", LIT(name));
-		return false;
-	} else if (name == "index_set") {
-		if (value != nullptr) {
-			Operand o = {};
-			check_expr_or_type(c, &o, value);
-			Entity *e = entity_of_node(value);
-			if (e != nullptr && e->kind == Entity_Procedure) {
-				if (ac->deferred_procedure.entity != nullptr) {
-					error(elem, "Previous usage of the '%.*s' attribute", LIT(name));
-				}
-
-				bool valid = true;
-
-				{
-					Type *pt = base_type(e->type);
-					GB_ASSERT(pt->kind == Type_Proc);
-
-					if (pt->Proc.param_count < 3) {
-						error(value, "'%s' attribute must allow for 3 parameters", LIT(name));
-						valid = false;
-					} else {
-						isize minimum_param_count = 0;
-						for_array(i, pt->Proc.params->Tuple.variables) {
-							Entity *param = pt->Proc.params->Tuple.variables[i];
-							if (param->kind == Entity_Variable) {
-								if (param->Variable.param_value.kind == ParameterValue_Invalid) {
-									minimum_param_count += 1;
-								} else {
-									break;
-								}
-							} else if (param->kind == Entity_Constant) {
-								minimum_param_count += 1;
-							} else {
-								break;
-							}
-						}
-
-						if (minimum_param_count > 3) {
-							error(value, "'%s' attribute must allow for at a minimum 3 parameters", LIT(name));
-							valid = false;
-						}
-					}
-
-					if (pt->Proc.variadic || pt->Proc.c_vararg) {
-						error(value, "'%s' attribute does not allow variadic procedures", LIT(name));
-						valid = false;
-					}
-				}
-
-				if (valid) {
-					if (ac->atom_op_table == nullptr) {
-						ac->atom_op_table = gb_alloc_item(permanent_allocator(), TypeAtomOpTable);
-					}
-					ac->atom_op_table->op[TypeAtomOp_index_set] = e;
-				}
-				return true;
-			}
-		}
-		error(elem, "Expected a procedure entity for '%.*s'", LIT(name));
-		return false;
-	} else if (name == "slice") {
-		if (value != nullptr) {
-			Operand o = {};
-			check_expr_or_type(c, &o, value);
-			Entity *e = entity_of_node(value);
-			if (e != nullptr && e->kind == Entity_Procedure) {
-				if (ac->deferred_procedure.entity != nullptr) {
-					error(elem, "Previous usage of the '%.*s' attribute", LIT(name));
-				}
-
-				bool valid = true;
-
-				{
-					Type *pt = base_type(e->type);
-					GB_ASSERT(pt->kind == Type_Proc);
-
-					if (pt->Proc.param_count < 1) {
-						error(value, "'%s' attribute must allow for 1 parameter", LIT(name));
-						valid = false;
-					} else {
-						isize minimum_param_count = 0;
-						for_array(i, pt->Proc.params->Tuple.variables) {
-							Entity *param = pt->Proc.params->Tuple.variables[i];
-							if (param->kind == Entity_Variable) {
-								if (param->Variable.param_value.kind == ParameterValue_Invalid) {
-									minimum_param_count += 1;
-								} else {
-									break;
-								}
-							} else if (param->kind == Entity_Constant) {
-								minimum_param_count += 1;
-							} else {
-								break;
-							}
-						}
-
-						if (minimum_param_count > 1) {
-							error(value, "'%s' attribute must allow for at a minimum 1 parameter", LIT(name));
-							valid = false;
-						}
-						{
-							Entity *param = pt->Proc.params->Tuple.variables[0];
-							Type *param_type = base_type(param->type);
-							if (is_type_pointer(param_type) && !is_type_rawptr(param_type)) {
-								// okay
-							} else {
-								error(value, "'%s' attribute's first parameter must be a pointer", LIT(name));
-								valid = false;
-							}
-
-						}
-					}
-
-					if (pt->Proc.variadic || pt->Proc.c_vararg) {
-						error(value, "'%s' attribute does not allow variadic procedures", LIT(name));
-						valid = false;
-					}
-
-					if (pt->Proc.result_count != 1) {
-						error(value, "'%s' attribute must return 1 result", LIT(name));
-						valid = false;
-					} else {
-						Type *rt = pt->Proc.results->Tuple.variables[0]->type;
-						rt = base_type(rt);
-						if (!is_type_slice(rt)) {
-							error(value, "'%s' attribute must return a slice", LIT(name));
-							valid = false;
-						}
-					}
-				}
-
-				if (valid) {
-					if (ac->atom_op_table == nullptr) {
-						ac->atom_op_table = gb_alloc_item(permanent_allocator(), TypeAtomOpTable);
-					}
-					ac->atom_op_table->op[TypeAtomOp_slice] = e;
-				}
-				return true;
-			}
-		}
-		error(elem, "Expected a procedure entity for '%.*s'", LIT(name));
-		return false;
 	}
 	return false;
 }

+ 1 - 1
src/checker.hpp

@@ -109,12 +109,12 @@ struct AttributeContext {
 	bool    set_cold;
 	String  link_name;
 	String  link_prefix;
+	String  link_section;
 	isize   init_expr_list_count;
 	String  thread_local_model;
 	String  deprecated_message;
 	DeferredProcedure deferred_procedure;
 	u32 optimization_mode; // ProcedureOptimizationMode
-	struct TypeAtomOpTable *atom_op_table;
 };
 
 AttributeContext make_attribute_context(String link_prefix) {

+ 16 - 0
src/checker_builtin_procs.hpp

@@ -56,6 +56,12 @@ enum BuiltinProcId {
 	BuiltinProc_overflow_sub,
 	BuiltinProc_overflow_mul,
 
+	BuiltinProc_sqrt,
+
+	BuiltinProc_mem_copy,
+	BuiltinProc_mem_copy_non_overlapping,
+	BuiltinProc_mem_zero,
+
 	BuiltinProc_volatile_store,
 	BuiltinProc_volatile_load,
 
@@ -197,6 +203,8 @@ BuiltinProc__type_simple_boolean_end,
 
 	BuiltinProc_type_is_specialization_of,
 
+	BuiltinProc_type_is_variant_of,
+
 	BuiltinProc_type_struct_field_count,
 
 	BuiltinProc_type_proc_parameter_count,
@@ -276,6 +284,12 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
+	{STR_LIT("mem_copy"),                 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+	{STR_LIT("mem_copy_non_overlapping"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+	{STR_LIT("mem_zero"),                 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT("volatile_store"),  2, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT("volatile_load"),   1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
@@ -415,6 +429,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 
 	{STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("type_is_variant_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT("type_struct_field_count"),   1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT("type_proc_parameter_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},

+ 1 - 0
src/entity.cpp

@@ -158,6 +158,7 @@ struct Entity {
 			Ast *      foreign_library_ident;
 			String     link_name;
 			String     link_prefix;
+			String     link_section;
 			bool       is_foreign;
 			bool       is_export;
 		} Variable;

+ 77 - 54
src/llvm_abi.cpp

@@ -10,21 +10,37 @@ struct lbArgType {
 	LLVMTypeRef cast_type;      // Optional
 	LLVMTypeRef pad_type;       // Optional
 	LLVMAttributeRef attribute; // Optional
+	LLVMAttributeRef align_attribute; // Optional
+	i64 byval_alignment;
+	bool is_byval;
 };
 
+
+i64 lb_sizeof(LLVMTypeRef type);
+i64 lb_alignof(LLVMTypeRef type);
+
 lbArgType lb_arg_type_direct(LLVMTypeRef type, LLVMTypeRef cast_type, LLVMTypeRef pad_type, LLVMAttributeRef attr) {
-	return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr};
+	return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr, nullptr, 0, false};
 }
 lbArgType lb_arg_type_direct(LLVMTypeRef type) {
 	return lb_arg_type_direct(type, nullptr, nullptr, nullptr);
 }
 
 lbArgType lb_arg_type_indirect(LLVMTypeRef type, LLVMAttributeRef attr) {
-	return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr};
+	return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr, nullptr, 0, false};
+}
+
+lbArgType lb_arg_type_indirect_byval(LLVMContextRef c, LLVMTypeRef type) {
+	i64 alignment = lb_alignof(type);
+	alignment = gb_max(alignment, 8);
+
+	LLVMAttributeRef byval_attr = lb_create_enum_attribute_with_type(c, "byval", type);
+	LLVMAttributeRef align_attr = lb_create_enum_attribute(c, "align", alignment);
+	return lbArgType{lbArg_Indirect, type, nullptr, nullptr, byval_attr, align_attr, alignment, true};
 }
 
 lbArgType lb_arg_type_ignore(LLVMTypeRef type) {
-	return lbArgType{lbArg_Ignore, type, nullptr, nullptr, nullptr};
+	return lbArgType{lbArg_Ignore, type, nullptr, nullptr, nullptr, nullptr, 0, false};
 }
 
 struct lbFunctionType {
@@ -121,6 +137,9 @@ void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType *ft, ProcCa
 		if (arg->attribute) {
 			LLVMAddAttributeAtIndex(fn, arg_index+1, arg->attribute);
 		}
+		if (arg->align_attribute) {
+			LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute);
+		}
 
 		arg_index++;
 	}
@@ -145,8 +164,6 @@ void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType *ft, ProcCa
 
 }
 
-i64 lb_sizeof(LLVMTypeRef type);
-i64 lb_alignof(LLVMTypeRef type);
 
 i64 lb_sizeof(LLVMTypeRef type) {
 	LLVMTypeKind kind = LLVMGetTypeKind(type);
@@ -328,7 +345,7 @@ namespace lbAbi386 {
 				if (sz == 0) {
 					args[i] = lb_arg_type_ignore(t);
 				} else {
-					args[i] = lb_arg_type_indirect(t, lb_create_enum_attribute(c, "byval"));
+					args[i] = lb_arg_type_indirect(t, nullptr);
 				}
 			} else {
 				args[i] = non_struct(c, t, false);
@@ -348,7 +365,7 @@ namespace lbAbi386 {
 			case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr);
 			case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr);
 			}
-			LLVMAttributeRef attr = lb_create_enum_attribute(c, "sret");
+			LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
 			return lb_arg_type_indirect(return_type, attr);
 		}
 		return non_struct(c, return_type, true);
@@ -419,8 +436,14 @@ namespace lbAbiAmd64SysV {
 		switch (reg_class) {
 		case RegClass_SSEFs:
 		case RegClass_SSEFv:
+		case RegClass_SSEDs:
 		case RegClass_SSEDv:
 			return true;
+		case RegClass_SSEInt8:
+		case RegClass_SSEInt16:
+		case RegClass_SSEInt32:
+		case RegClass_SSEInt64:
+			return true;
 		}
 		return false;
 	}
@@ -437,11 +460,10 @@ namespace lbAbiAmd64SysV {
 		Amd64TypeAttribute_StructRect,
 	};
 
-	Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count);
 	lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined);
 	void classify_with(LLVMTypeRef t, Array<RegClass> *cls, i64 ix, i64 off);
 	void fixup(LLVMTypeRef t, Array<RegClass> *cls);
-	lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind);
+	lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention);
 	Array<RegClass> classify(LLVMTypeRef t);
 	LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const &reg_classes);
 
@@ -452,11 +474,11 @@ namespace lbAbiAmd64SysV {
 
 		ft->args = array_make<lbArgType>(heap_allocator(), arg_count);
 		for (unsigned i = 0; i < arg_count; i++) {
-			ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal);
+			ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention);
 		}
 
 		if (return_is_defined) {
-			ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect);
+			ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention);
 		} else {
 			ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c));
 		}
@@ -493,7 +515,7 @@ namespace lbAbiAmd64SysV {
 		return false;
 	}
 
-	lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind) {
+	lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention) {
 		if (is_register(type)) {
 			LLVMAttributeRef attribute = nullptr;
 			if (type == LLVMInt1TypeInContext(c)) {
@@ -506,9 +528,12 @@ namespace lbAbiAmd64SysV {
 		if (is_mem_cls(cls, attribute_kind)) {
 			LLVMAttributeRef attribute = nullptr;
 			if (attribute_kind == Amd64TypeAttribute_ByVal) {
-				attribute = lb_create_enum_attribute(c, "byval");
+				if (!is_calling_convention_odin(calling_convention)) {
+					return lb_arg_type_indirect_byval(c, type);
+				}
+				attribute = nullptr;
 			} else if (attribute_kind == Amd64TypeAttribute_StructRect) {
-				attribute = lb_create_enum_attribute(c, "sret");
+				attribute = lb_create_enum_attribute_with_type(c, "sret", type);
 			}
 			return lb_arg_type_indirect(type, attribute);
 		} else {
@@ -538,30 +563,48 @@ namespace lbAbiAmd64SysV {
 		return reg_classes;
 	}
 
-	void unify(Array<RegClass> *cls, i64 i, RegClass newv) {
-		RegClass &oldv = (*cls)[i];
+	void unify(Array<RegClass> *cls, i64 i, RegClass const newv) {
+		RegClass const oldv = (*cls)[i];
 		if (oldv == newv) {
 			return;
-		} else if (oldv == RegClass_NoClass) {
-			oldv = newv;
+		}
+
+		RegClass to_write = newv;
+		if (oldv == RegClass_NoClass) {
+			to_write = newv;
 		} else if (newv == RegClass_NoClass) {
 			return;
 		} else if (oldv == RegClass_Memory || newv == RegClass_Memory) {
-			return;
-		} else if (oldv == RegClass_Int || newv	== RegClass_Int) {
-			return;
-		} else if (oldv == RegClass_X87 || oldv == RegClass_X87Up || oldv == RegClass_ComplexX87 ||
-		           newv == RegClass_X87 || newv == RegClass_X87Up || newv == RegClass_ComplexX87) {
-			oldv = RegClass_Memory;
-		} else {
-			oldv = newv;
+			to_write = RegClass_Memory;
+		} else if (oldv == RegClass_Int || newv == RegClass_Int) {
+			to_write = RegClass_Int;
+		} else if (oldv == RegClass_X87 || oldv == RegClass_X87Up || oldv == RegClass_ComplexX87) {
+			to_write = RegClass_Memory;
+		} else if (newv == RegClass_X87 || newv == RegClass_X87Up || newv == RegClass_ComplexX87) {
+			to_write = RegClass_Memory;
+		} else if (newv == RegClass_SSEUp) {
+			switch (oldv) {
+			case RegClass_SSEFv:
+			case RegClass_SSEFs:
+			case RegClass_SSEDv:
+			case RegClass_SSEDs:
+			case RegClass_SSEInt8:
+			case RegClass_SSEInt16:
+			case RegClass_SSEInt32:
+			case RegClass_SSEInt64:
+				return;
+			}
 		}
+
+		(*cls)[i] = to_write;
 	}
 
 	void fixup(LLVMTypeRef t, Array<RegClass> *cls) {
 		i64 i = 0;
 		i64 e = cls->count;
-		if (e > 2 && (lb_is_type_kind(t, LLVMStructTypeKind) || lb_is_type_kind(t, LLVMArrayTypeKind))) {
+		if (e > 2 && (lb_is_type_kind(t, LLVMStructTypeKind) ||
+		              lb_is_type_kind(t, LLVMArrayTypeKind) ||
+		              lb_is_type_kind(t, LLVMVectorTypeKind))) {
 			RegClass &oldv = (*cls)[i];
 			if (is_sse(oldv)) {
 				for (i++; i < e; i++) {
@@ -605,8 +648,8 @@ namespace lbAbiAmd64SysV {
 
 	unsigned llvec_len(Array<RegClass> const &reg_classes, isize offset) {
 		unsigned len = 1;
-		for (isize i = offset+1; i < reg_classes.count; i++) {
-			if (reg_classes[offset] != RegClass_SSEFv && reg_classes[i] != RegClass_SSEUp) {
+		for (isize i = offset; i < reg_classes.count; i++) {
+			if (reg_classes[i] != RegClass_SSEUp) {
 				break;
 			}
 			len++;
@@ -617,7 +660,7 @@ namespace lbAbiAmd64SysV {
 
 	LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const &reg_classes) {
 		auto types = array_make<LLVMTypeRef>(heap_allocator(), 0, reg_classes.count);
-		for_array(i, reg_classes) {
+		for (isize i = 0; i < reg_classes.count; /**/) {
 			RegClass reg_class = reg_classes[i];
 			switch (reg_class) {
 			case RegClass_Int:
@@ -659,7 +702,7 @@ namespace lbAbiAmd64SysV {
 						break;
 					}
 
-					unsigned vec_len = llvec_len(reg_classes, i);
+					unsigned vec_len = llvec_len(reg_classes, i+1);
 					LLVMTypeRef vec_type = LLVMVectorType(elem_type, vec_len * elems_per_word);
 					array_add(&types, vec_type);
 					i += vec_len;
@@ -675,9 +718,9 @@ namespace lbAbiAmd64SysV {
 			default:
 				GB_PANIC("Unhandled RegClass");
 			}
+			i += 1;
 		}
 
-		GB_ASSERT(types.count != 0);
 		if (types.count == 1) {
 			return types[0];
 		}
@@ -778,26 +821,6 @@ namespace lbAbiAmd64SysV {
 		}
 	}
 
-	Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) {
-		auto args = array_make<lbArgType>(heap_allocator(), arg_count);
-
-		for (unsigned i = 0; i < arg_count; i++) {
-			LLVMTypeRef t = arg_types[i];
-			LLVMTypeKind kind = LLVMGetTypeKind(t);
-			if (kind == LLVMStructTypeKind) {
-				i64 sz = lb_sizeof(t);
-				if (sz == 0) {
-					args[i] = lb_arg_type_ignore(t);
-				} else {
-					args[i] = lb_arg_type_indirect(t, lb_create_enum_attribute(c, "byval"));
-				}
-			} else {
-				args[i] = non_struct(c, t);
-			}
-		}
-		return args;
-	}
-
 	lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) {
 		if (!return_is_defined) {
 			return lb_arg_type_direct(LLVMVoidTypeInContext(c));
@@ -809,7 +832,7 @@ namespace lbAbiAmd64SysV {
 			case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr);
 			case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr);
 			}
-			LLVMAttributeRef attr = lb_create_enum_attribute(c, "sret");
+			LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
 			return lb_arg_type_indirect(return_type, attr);
 		} else if (build_context.metrics.os == TargetOs_windows && lb_is_type_kind(return_type, LLVMIntegerTypeKind) && lb_sizeof(return_type) == 16) {
 			return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 128), nullptr, nullptr);
@@ -959,7 +982,7 @@ namespace lbAbiArm64 {
 				}
 				return lb_arg_type_direct(type, cast_type, nullptr, nullptr);
 			} else {
-				LLVMAttributeRef attr = lb_create_enum_attribute(c, "sret");
+				LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", type);
 				return lb_arg_type_indirect(type, attr);
 			}
 		}

Datei-Diff unterdrückt, da er zu groß ist
+ 579 - 302
src/llvm_backend.cpp


+ 9 - 3
src/llvm_backend.hpp

@@ -215,6 +215,12 @@ enum lbProcedureFlag : u32 {
 	lbProcedureFlag_WithoutMemcpyPass = 1<<0,
 };
 
+struct lbCopyElisionHint {
+	lbValue ptr;
+	Ast *   ast;
+	bool    used;
+};
+
 struct lbProcedure {
 	u32 flags;
 	u16 state_flags;
@@ -260,9 +266,7 @@ struct lbProcedure {
 
 	LLVMMetadataRef debug_info;
 
-	lbValue  return_ptr_hint_value;
-	Ast *    return_ptr_hint_ast;
-	bool     return_ptr_hint_used;
+	lbCopyElisionHint copy_elision_hint;
 };
 
 
@@ -276,6 +280,7 @@ String lb_mangle_name(lbModule *m, Entity *e);
 String lb_get_entity_name(lbModule *m, Entity *e, String name = {});
 
 LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char const *name, u64 value=0);
+LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef ctx, char const *name, LLVMTypeRef type);
 void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value);
 void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name);
 lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false);
@@ -412,6 +417,7 @@ lbValue lb_emit_reverse_bits(lbProcedure *p, lbValue x, Type *type);
 
 lbValue lb_emit_bit_set_card(lbProcedure *p, lbValue x);
 
+void lb_mem_zero_addr(lbProcedure *p, LLVMValueRef ptr, Type *type);
 
 
 #define LB_STARTUP_RUNTIME_PROC_NAME   "__$startup_runtime"

+ 127 - 4
src/llvm_backend_opt.cpp

@@ -1,7 +1,25 @@
-void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level);
+void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level);
 void lb_add_function_simplifcation_passes(LLVMPassManagerRef mpm, i32 optimization_level);
 void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPassManagerRef mpm, i32 optimization_level);
-void lb_populate_function_pass_manager_specific(LLVMPassManagerRef fpm, i32 optimization_level);
+void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level);
+
+LLVMBool lb_must_preserve_predicate_callback(LLVMValueRef value, void *user_data) {
+	lbModule *m = cast(lbModule *)user_data;
+	if (m == nullptr) {
+		return false;
+	}
+	if (value == nullptr) {
+		return false;
+	}
+	return LLVMIsAAllocaInst(value) != nullptr;
+}
+
+void lb_add_must_preserve_predicate_pass(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) {
+	if (false && optimization_level == 0 && m->debug_builder) {
+		// LLVMAddInternalizePassWithMustPreservePredicate(fpm, m, lb_must_preserve_predicate_callback);
+	}
+}
+
 
 void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) {
 	LLVMAddPromoteMemoryToRegisterPass(fpm);
@@ -15,11 +33,13 @@ void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) {
 	LLVMAddCFGSimplificationPass(fpm);
 }
 
-void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) {
+void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) {
 	// NOTE(bill): Treat -opt:3 as if it was -opt:2
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	optimization_level = gb_clamp(optimization_level, 0, 2);
 
+	lb_add_must_preserve_predicate_pass(m, fpm, optimization_level);
+
 	if (ignore_memcpy_pass) {
 		lb_basic_populate_function_pass_manager(fpm);
 		return;
@@ -57,11 +77,13 @@ void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcp
 #endif
 }
 
-void lb_populate_function_pass_manager_specific(LLVMPassManagerRef fpm, i32 optimization_level) {
+void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) {
 	// NOTE(bill): Treat -opt:3 as if it was -opt:2
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	optimization_level = gb_clamp(optimization_level, 0, 2);
 
+	lb_add_must_preserve_predicate_pass(m, fpm, optimization_level);
+
 	if (optimization_level == 0) {
 		LLVMAddMemCpyOptPass(fpm);
 		lb_basic_populate_function_pass_manager(fpm);
@@ -226,3 +248,104 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa
 
 	LLVMAddCFGSimplificationPass(mpm);
 }
+
+void lb_run_remove_dead_instruction_pass(lbProcedure *p) {
+	isize removal_count = 0;
+	isize pass_count = 0;
+	isize const max_pass_count = 10;
+	isize original_instruction_count = 0;
+	// Custom remove dead instruction pass
+	for (; pass_count < max_pass_count; pass_count++) {
+		bool was_dead_instructions = false;
+
+		// NOTE(bill): Iterate backwards
+		// reduces the number of passes as things later on will depend on things previously
+		for (LLVMBasicBlockRef block = LLVMGetLastBasicBlock(p->value);
+		     block != nullptr;
+		     block = LLVMGetPreviousBasicBlock(block)) {
+			// NOTE(bill): Iterate backwards
+			// reduces the number of passes as things later on will depend on things previously
+			for (LLVMValueRef instr = LLVMGetLastInstruction(block);
+			     instr != nullptr;
+			     /**/)  {
+			     	if (pass_count == 0) {
+			     		original_instruction_count += 1;
+			     	}
+
+				LLVMValueRef curr_instr = instr;
+				instr = LLVMGetPreviousInstruction(instr);
+
+				LLVMUseRef first_use = LLVMGetFirstUse(curr_instr);
+				if (first_use != nullptr)  {
+					continue;
+				}
+				if (LLVMTypeOf(curr_instr) == nullptr) {
+					continue;
+				}
+
+				// NOTE(bill): Explicit instructions are set here because some instructions could have side effects
+				switch (LLVMGetInstructionOpcode(curr_instr)) {
+				case LLVMFNeg:
+				case LLVMAdd:
+				case LLVMFAdd:
+				case LLVMSub:
+				case LLVMFSub:
+				case LLVMMul:
+				case LLVMFMul:
+				case LLVMUDiv:
+				case LLVMSDiv:
+				case LLVMFDiv:
+				case LLVMURem:
+				case LLVMSRem:
+				case LLVMFRem:
+				case LLVMShl:
+				case LLVMLShr:
+				case LLVMAShr:
+				case LLVMAnd:
+				case LLVMOr:
+				case LLVMXor:
+				case LLVMAlloca:
+				case LLVMLoad:
+				case LLVMGetElementPtr:
+				case LLVMTrunc:
+				case LLVMZExt:
+				case LLVMSExt:
+				case LLVMFPToUI:
+				case LLVMFPToSI:
+				case LLVMUIToFP:
+				case LLVMSIToFP:
+				case LLVMFPTrunc:
+				case LLVMFPExt:
+				case LLVMPtrToInt:
+				case LLVMIntToPtr:
+				case LLVMBitCast:
+				case LLVMAddrSpaceCast:
+				case LLVMICmp:
+				case LLVMFCmp:
+				case LLVMSelect:
+				case LLVMExtractElement:
+				case LLVMShuffleVector:
+				case LLVMExtractValue:
+					removal_count += 1;
+					LLVMInstructionEraseFromParent(curr_instr);
+					was_dead_instructions = true;
+					break;
+				}
+			}
+		}
+
+		if (!was_dead_instructions) {
+			break;
+		}
+	}
+}
+
+
+void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedure *p) {
+	LLVMRunFunctionPassManager(fpm, p->value);
+	// NOTE(bill): LLVMAddDCEPass doesn't seem to be exported in the official DLL's for LLVM
+	// which means we cannot rely upon it
+	// This is also useful for read the .ll for debug purposes because a lot of instructions
+	// are not removed
+	lb_run_remove_dead_instruction_pass(p);
+}

+ 7 - 1
src/main.cpp

@@ -621,6 +621,7 @@ enum BuildFlagKind {
 
 	BuildFlag_IgnoreWarnings,
 	BuildFlag_WarningsAsErrors,
+	BuildFlag_VerboseErrors,
 
 #if defined(GB_SYSTEM_WINDOWS)
 	BuildFlag_IgnoreVsSearch,
@@ -741,6 +742,7 @@ bool parse_build_flags(Array<String> args) {
 
 	add_flag(&build_flags, BuildFlag_IgnoreWarnings,   str_lit("ignore-warnings"),    BuildFlagParam_None, Command_all);
 	add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
+	add_flag(&build_flags, BuildFlag_VerboseErrors,    str_lit("verbose-errors"),     BuildFlagParam_None, Command_all);
 
 #if defined(GB_SYSTEM_WINDOWS)
 	add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"),  BuildFlagParam_None, Command__does_build);
@@ -1320,6 +1322,10 @@ bool parse_build_flags(Array<String> args) {
 							}
 							break;
 
+						case BuildFlag_VerboseErrors:
+							build_context.show_error_line = true;
+							break;
+
 					#if defined(GB_SYSTEM_WINDOWS)
 						case BuildFlag_IgnoreVsSearch:
 							GB_ASSERT(value.kind == ExactValue_Invalid);
@@ -1719,7 +1725,7 @@ void print_show_help(String const arg0, String const &command) {
 		print_usage_line(2, "Disables automatic linking with the C Run Time");
 		print_usage_line(0, "");
 
-		print_usage_line(1, "-use-lld");
+		print_usage_line(1, "-lld");
 		print_usage_line(2, "Use the LLD linker rather than the default");
 		print_usage_line(0, "");
 

+ 126 - 143
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;
@@ -115,6 +10,48 @@ Token token_end_of_line(AstFile *f, Token tok) {
 	return tok;
 }
 
+gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) {
+	AstFile *file = get_ast_file_from_id(pos.file_id);
+	if (file == nullptr) {
+		return nullptr;
+	}
+	isize offset = pos.offset;
+
+	u8 *start = file->tokenizer.start;
+	u8 *end = file->tokenizer.end;
+	isize len = end-start;
+	if (len < offset) {
+		return nullptr;
+	}
+
+	u8 *pos_offset = start+offset;
+
+	u8 *line_start = pos_offset;
+	u8 *line_end  = pos_offset;
+	while (line_start >= start) {
+		if (*line_start == '\n') {
+			line_start += 1;
+			break;
+		}
+		line_start -= 1;
+	}
+
+	while (line_end < end) {
+		if (*line_end == '\n') {
+			line_end -= 1;
+			break;
+		}
+		line_end += 1;
+	}
+	String the_line = make_string(line_start, line_end-line_start);
+	the_line = string_trim_whitespace(the_line);
+
+	if (offset_) *offset_ = cast(i32)(pos_offset - the_line.text);
+
+	return gb_string_make_length(heap_allocator(), the_line.text, the_line.len);
+}
+
+
 
 isize ast_node_size(AstKind kind) {
 	return align_formula_isize(gb_size_of(AstCommonStuff) + ast_variant_sizes[kind], gb_align_of(void *));
@@ -432,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, fmt, va);
+	error_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
@@ -451,7 +391,7 @@ void error_no_newline(Ast *node, char const *fmt, ...) {
 	}
 	va_list va;
 	va_start(va, fmt);
-	error_no_newline_va(token, fmt, va);
+	error_no_newline_va(token.pos, fmt, va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
@@ -459,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), 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), 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;
@@ -640,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;
@@ -1344,6 +1296,7 @@ Token expect_token_after(AstFile *f, TokenKind kind, char const *msg) {
 bool is_token_range(TokenKind kind) {
 	switch (kind) {
 	case Token_Ellipsis:
+	case Token_RangeFull:
 	case Token_RangeHalf:
 		return true;
 	}
@@ -1574,6 +1527,10 @@ void expect_semicolon(AstFile *f, Ast *s) {
 		return;
 	}
 
+	if (f->curr_token.kind == Token_EOF) {
+		return;
+	}
+
 	if (s != nullptr) {
 		bool insert_semi = (f->tokenizer.flags & TokenizerFlag_InsertSemicolon) != 0;
 		if (insert_semi) {
@@ -1994,35 +1951,28 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 		Token name = expect_token(f, Token_Ident);
 		if (name.string == "type") {
 			return ast_helper_type(f, token, parse_type(f));
-		} /* else if (name.string == "no_deferred") {
-			operand = parse_expr(f, false);
-			if (unparen_expr(operand)->kind != Ast_CallExpr) {
-				syntax_error(operand, "#no_deferred can only be applied to procedure calls");
-				operand = ast_bad_expr(f, token, f->curr_token);
-			}
-			operand->state_flags |= StateFlag_no_deferred;
-		} */ 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);
+		} else if (name.string == "file") {
+			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) {
@@ -2034,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) {
@@ -2046,6 +1996,10 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			return original_type;
 		} else if (name.string == "bounds_check") {
 			Ast *operand = parse_expr(f, lhs);
+			if (operand == nullptr) {
+				syntax_error(token, "Invalid expresssion for #%.*s", LIT(name.string));
+				return nullptr;
+			}
 			operand->state_flags |= StateFlag_bounds_check;
 			if ((operand->state_flags & StateFlag_no_bounds_check) != 0) {
 				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
@@ -2053,13 +2007,17 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			return operand;
 		} else if (name.string == "no_bounds_check") {
 			Ast *operand = parse_expr(f, lhs);
+			if (operand == nullptr) {
+				syntax_error(token, "Invalid expresssion for #%.*s", LIT(name.string));
+				return nullptr;
+			}
 			operand->state_flags |= StateFlag_no_bounds_check;
 			if ((operand->state_flags & StateFlag_bounds_check) != 0) {
 				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
 			}
 			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);
@@ -2314,7 +2272,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			f->expr_level = prev_level;
 		}
 
-
+		skip_possible_newline_for_literal(f);
 		Token open = expect_token_after(f, Token_OpenBrace, "struct");
 
 		isize name_count = 0;
@@ -2674,6 +2632,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 
 			switch (f->curr_token.kind) {
 			case Token_Ellipsis:
+			case Token_RangeFull:
 			case Token_RangeHalf:
 				// NOTE(bill): Do not err yet
 			case Token_Colon:
@@ -2685,6 +2644,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 
 			switch (f->curr_token.kind) {
 			case Token_Ellipsis:
+			case Token_RangeFull:
 			case Token_RangeHalf:
 				syntax_error(f->curr_token, "Expected a colon, not a range");
 				/* fallthrough */
@@ -2723,6 +2683,16 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 			}
 			break;
 
+		case Token_Increment:
+		case Token_Decrement:
+			if (!lhs) {
+				Token token = advance_token(f);
+				syntax_error(token, "Postfix '%.*s' operator is not supported", LIT(token.string));
+			} else {
+				loop = false;
+			}
+			break;
+
 		default:
 			loop = false;
 			break;
@@ -2753,16 +2723,26 @@ Ast *parse_unary_expr(AstFile *f, bool lhs) {
 		return ast_auto_cast(f, token, expr);
 	}
 
+
 	case Token_Add:
 	case Token_Sub:
-	case Token_Not:
 	case Token_Xor:
-	case Token_And: {
+	case Token_And:
+	case Token_Not: {
+		Token token = advance_token(f);
+		Ast *expr = parse_unary_expr(f, lhs);
+		return ast_unary_expr(f, token, expr);
+	}
+
+	case Token_Increment:
+	case Token_Decrement: {
 		Token token = advance_token(f);
+		syntax_error(token, "Unary '%.*s' operator is not supported", LIT(token.string));
 		Ast *expr = parse_unary_expr(f, lhs);
 		return ast_unary_expr(f, token, expr);
 	}
 
+
 	case Token_Period: {
 		Token token = expect_token(f, Token_Period);
 		Ast *ident = parse_ident(f);
@@ -2791,6 +2771,7 @@ i32 token_precedence(AstFile *f, TokenKind t) {
 	case Token_when:
 		return 1;
 	case Token_Ellipsis:
+	case Token_RangeFull:
 	case Token_RangeHalf:
 		if (!f->allow_range) {
 			return 0;
@@ -3152,6 +3133,13 @@ Ast *parse_simple_stmt(AstFile *f, u32 flags) {
 		return ast_bad_stmt(f, token, f->curr_token);
 	}
 
+	switch (token.kind) {
+	case Token_Increment:
+	case Token_Decrement:
+		advance_token(f);
+		syntax_error(token, "Postfix '%.*s' statement is not supported", LIT(token.string));
+		break;
+	}
 
 
 	#if 0
@@ -3898,12 +3886,6 @@ Ast *parse_return_stmt(AstFile *f) {
 
 	while (f->curr_token.kind != Token_Semicolon) {
 		Ast *arg = parse_expr(f, false);
-		// if (f->curr_token.kind == Token_Eq) {
-		// 	Token eq = expect_token(f, Token_Eq);
-		// 	Ast *value = parse_value(f);
-		// 	arg = ast_field_value(f, arg, value, eq);
-		// }
-
 		array_add(&results, arg);
 		if (f->curr_token.kind != Token_Comma ||
 		    f->curr_token.kind == Token_EOF) {
@@ -4024,7 +4006,7 @@ Ast *parse_case_clause(AstFile *f, bool is_type) {
 	}
 	f->allow_range = prev_allow_range;
 	f->allow_in_expr = prev_allow_in_expr;
-	expect_token(f, Token_Colon); // TODO(bill): Is this the best syntax?
+	expect_token(f, Token_Colon);
 	Array<Ast *> stmts = parse_stmt_list(f);
 
 	return ast_case_clause(f, token, list, stmts);
@@ -4482,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") {
@@ -4572,6 +4554,7 @@ ParseFileError init_ast_file(AstFile *f, String fullpath, TokenPos *err_pos) {
 	GB_ASSERT(f != nullptr);
 	f->fullpath = string_trim_whitespace(fullpath); // Just in case
 	set_file_path_string(f->id, fullpath);
+	set_ast_file_from_id(f->id, f);
 	if (!string_ends_with(f->fullpath, str_lit(".odin"))) {
 		return ParseFile_WrongExtension;
 	}

+ 25 - 20
src/parser.hpp

@@ -7,22 +7,21 @@ struct AstFile;
 struct AstPackage;
 
 enum AddressingMode {
-	Addressing_Invalid,       // invalid addressing mode
-	Addressing_NoValue,       // no value (void in C)
-	Addressing_Value,         // computed value (rvalue)
-	Addressing_Context,       // context value
-	Addressing_Variable,      // addressable variable (lvalue)
-	Addressing_Constant,      // constant
-	Addressing_Type,          // type
-	Addressing_Builtin,       // built-in procedure
-	Addressing_ProcGroup,     // procedure group (overloaded procedure)
-	Addressing_MapIndex,      // map index expression -
-	                          // 	lhs: acts like a Variable
-	                          // 	rhs: acts like OptionalOk
-	Addressing_OptionalOk,    // rhs: acts like a value with an optional boolean part (for existence check)
-	Addressing_SoaVariable,   // Struct-Of-Arrays indexed variable
-
-	Addressing_AtomOpAssign,  // Specialized for custom atom operations for assignments
+	Addressing_Invalid   = 0,    // invalid addressing mode
+	Addressing_NoValue   = 1,    // no value (void in C)
+	Addressing_Value     = 2,    // computed value (rvalue)
+	Addressing_Context   = 3,    // context value
+	Addressing_Variable  = 4,    // addressable variable (lvalue)
+	Addressing_Constant  = 5,    // constant
+	Addressing_Type      = 6,    // type
+	Addressing_Builtin   = 7,    // built-in procedure
+	Addressing_ProcGroup = 8,    // procedure group (overloaded procedure)
+	Addressing_MapIndex  = 9,    // map index expression -
+	                             // 	lhs: acts like a Variable
+	                             // 	rhs: acts like OptionalOk
+	Addressing_OptionalOk  = 10, // rhs: acts like a value with an optional boolean part (for existence check)
+	Addressing_SoaVariable = 11, // Struct-Of-Arrays indexed variable
+
 };
 
 struct TypeAndValue {
@@ -287,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; \
@@ -325,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; \
@@ -345,7 +344,13 @@ AST_KIND(_ExprBegin,  "",  bool) \
 	AST_KIND(FieldValue,      "field value",              struct { Token eq; Ast *field, *value; }) \
 	AST_KIND(TernaryIfExpr,   "ternary if expression",    struct { Ast *x, *cond, *y; }) \
 	AST_KIND(TernaryWhenExpr, "ternary when expression",  struct { Ast *x, *cond, *y; }) \
-	AST_KIND(TypeAssertion, "type assertion",      struct { Ast *expr; Token dot; Ast *type; Type *type_hint; }) \
+	AST_KIND(TypeAssertion, "type assertion", struct { \
+		Ast *expr; \
+		Token dot; \
+		Ast *type; \
+		Type *type_hint; \
+		bool ignores[2]; \
+	}) \
 	AST_KIND(TypeCast,      "type cast",           struct { Token token; Ast *type, *expr; }) \
 	AST_KIND(AutoCast,      "auto_cast",           struct { Token token; Ast *expr; }) \
 	AST_KIND(InlineAsmExpr, "inline asm expression", struct { \

+ 340 - 0
src/parser_pos.cpp

@@ -0,0 +1,340 @@
+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_Invalid:
+		return empty_token;
+	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:
+		if (node->TagExpr.expr) {
+			return ast_end_token(node->TagExpr.expr);
+		}
+		return node->TagExpr.name;
+	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:
+		if (node->ImplicitSelectorExpr.selector) {
+			return ast_end_token(node->ImplicitSelectorExpr.selector);
+		}
+		return node->ImplicitSelectorExpr.token;
+	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));
+}

+ 170 - 51
src/tokenizer.cpp

@@ -51,8 +51,10 @@ TOKEN_KIND(Token__AssignOpBegin, ""), \
 	TOKEN_KIND(Token_CmpAndEq, "&&="), \
 	TOKEN_KIND(Token_CmpOrEq,  "||="), \
 TOKEN_KIND(Token__AssignOpEnd, ""), \
-	TOKEN_KIND(Token_ArrowRight,       "->"), \
-	TOKEN_KIND(Token_Undef,            "---"), \
+	TOKEN_KIND(Token_Increment, "++"), \
+	TOKEN_KIND(Token_Decrement, "--"), \
+	TOKEN_KIND(Token_ArrowRight,"->"), \
+	TOKEN_KIND(Token_Undef,     "---"), \
 \
 TOKEN_KIND(Token__ComparisonBegin, ""), \
 	TOKEN_KIND(Token_CmpEq, "=="), \
@@ -74,6 +76,7 @@ TOKEN_KIND(Token__ComparisonEnd, ""), \
 	TOKEN_KIND(Token_Period,        "."),   \
 	TOKEN_KIND(Token_Comma,         ","),   \
 	TOKEN_KIND(Token_Ellipsis,      ".."),  \
+	TOKEN_KIND(Token_RangeFull,     "..="), \
 	TOKEN_KIND(Token_RangeHalf,     "..<"), \
 	TOKEN_KIND(Token_BackSlash,     "\\"),  \
 TOKEN_KIND(Token__OperatorEnd, ""), \
@@ -185,9 +188,11 @@ void init_keyword_hash_table(void) {
 	GB_ASSERT(max_keyword_size < 16);
 }
 
-gb_global Array<String> global_file_path_strings; // index is file id
+gb_global Array<String>           global_file_path_strings; // index is file id
+gb_global Array<struct AstFile *> global_files; // index is file id
 
-String get_file_path_string(i32 index);
+String   get_file_path_string(i32 index);
+struct AstFile *get_ast_file_from_id(i32 index);
 
 struct TokenPos {
 	i32 file_id;
@@ -281,6 +286,7 @@ void init_global_error_collector(void) {
 	array_init(&global_error_collector.errors, heap_allocator());
 	array_init(&global_error_collector.error_buffer, heap_allocator());
 	array_init(&global_file_path_strings, heap_allocator(), 4096);
+	array_init(&global_files, heap_allocator(), 4096);
 }
 
 
@@ -302,6 +308,24 @@ bool set_file_path_string(i32 index, String const &path) {
 	return ok;
 }
 
+bool set_ast_file_from_id(i32 index, AstFile *file) {
+	bool ok = false;
+	GB_ASSERT(index >= 0);
+	gb_mutex_lock(&global_error_collector.string_mutex);
+
+	if (index >= global_files.count) {
+		array_resize(&global_files, index);
+	}
+	AstFile *prev = global_files[index];
+	if (prev == nullptr) {
+		global_files[index] = file;
+		ok = true;
+	}
+
+	gb_mutex_unlock(&global_error_collector.string_mutex);
+	return ok;
+}
+
 String get_file_path_string(i32 index) {
 	GB_ASSERT(index >= 0);
 	gb_mutex_lock(&global_error_collector.string_mutex);
@@ -315,6 +339,20 @@ String get_file_path_string(i32 index) {
 	return path;
 }
 
+AstFile *get_ast_file_from_id(i32 index) {
+	GB_ASSERT(index >= 0);
+	gb_mutex_lock(&global_error_collector.string_mutex);
+
+	AstFile *file = nullptr;
+	if (index < global_files.count) {
+		file = global_files[index];
+	}
+
+	gb_mutex_unlock(&global_error_collector.string_mutex);
+	return file;
+}
+
+
 void begin_error_block(void) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.in_block = true;
@@ -374,6 +412,8 @@ ErrorOutProc *error_out_va = default_error_out_va;
 // NOTE: defined in build_settings.cpp
 bool global_warnings_as_errors(void);
 bool global_ignore_warnings(void);
+bool show_error_line(void);
+gbString get_file_line_as_string(TokenPos const &pos, i32 *offset);
 
 void error_out(char const *fmt, ...) {
 	va_list va;
@@ -383,17 +423,85 @@ void error_out(char const *fmt, ...) {
 }
 
 
-void error_va(Token token, char const *fmt, va_list va) {
+bool show_error_on_line(TokenPos const &pos, TokenPos end) {
+	if (!show_error_line()) {
+		return false;
+	}
+
+	i32 offset = 0;
+	gbString the_line = get_file_line_as_string(pos, &offset);
+	defer (gb_string_free(the_line));
+
+	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,
+			ELLIPSIS_PADDING = 8
+		};
+
+		error_out("\n\t");
+		if (line.len+MAX_TAB_WIDTH+ELLIPSIS_PADDING > MAX_LINE_LENGTH) {
+			i32 const half_width = MAX_LINE_LENGTH/2;
+			i32 left  = cast(i32)(offset);
+			i32 right = cast(i32)(line.len - offset);
+			left  = gb_min(left, half_width);
+			right = gb_min(right, half_width);
+
+			line.text += offset-left;
+			line.len  -= offset+right-left;
+
+			line = string_trim_whitespace(line);
+
+			offset = left + ELLIPSIS_PADDING/2;
+
+			error_out("... %.*s ...", LIT(line));
+		} else {
+			error_out("%.*s", LIT(line));
+		}
+		error_out("\n\t");
+
+		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 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
-	if (token.pos.line == 0) {
+	if (pos.line == 0) {
 		error_out("Error: %s\n", gb_bprintf_va(fmt, va));
-	} else if (global_error_collector.prev != token.pos) {
-		global_error_collector.prev = token.pos;
+	} else if (global_error_collector.prev != pos) {
+		global_error_collector.prev = pos;
 		error_out("%s %s\n",
-		          token_pos_to_string(token.pos),
+		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
+		show_error_on_line(pos, end);
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
@@ -401,22 +509,23 @@ void error_va(Token token, char const *fmt, va_list va) {
 	}
 }
 
-void warning_va(Token token, 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(token, fmt, va);
+		error_va(pos, end, fmt, va);
 		return;
 	}
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.warning_count++;
 	if (!global_ignore_warnings()) {
 		// NOTE(bill): Duplicate error, skip it
-		if (token.pos.line == 0) {
+		if (pos.line == 0) {
 			error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
-		} else if (global_error_collector.prev != token.pos) {
-			global_error_collector.prev = token.pos;
+		} else if (global_error_collector.prev != pos) {
+			global_error_collector.prev = pos;
 			error_out("%s Warning: %s\n",
-			          token_pos_to_string(token.pos),
+			          token_pos_to_string(pos),
 			          gb_bprintf_va(fmt, va));
+			show_error_on_line(pos, end);
 		}
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
@@ -429,16 +538,16 @@ void error_line_va(char const *fmt, va_list va) {
 	gb_mutex_unlock(&global_error_collector.mutex);
 }
 
-void error_no_newline_va(Token token, char const *fmt, va_list va) {
+void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
-	if (token.pos.line == 0) {
+	if (pos.line == 0) {
 		error_out("Error: %s", gb_bprintf_va(fmt, va));
-	} else if (global_error_collector.prev != token.pos) {
-		global_error_collector.prev = token.pos;
+	} else if (global_error_collector.prev != pos) {
+		global_error_collector.prev = pos;
 		error_out("%s %s",
-		          token_pos_to_string(token.pos),
+		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
@@ -448,16 +557,17 @@ void error_no_newline_va(Token token, char const *fmt, va_list va) {
 }
 
 
-void syntax_error_va(Token token, 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
-	if (global_error_collector.prev != token.pos) {
-		global_error_collector.prev = token.pos;
+	if (global_error_collector.prev != pos) {
+		global_error_collector.prev = pos;
 		error_out("%s Syntax Error: %s\n",
-		          token_pos_to_string(token.pos),
+		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
-	} else if (token.pos.line == 0) {
+		show_error_on_line(pos, end);
+	} else if (pos.line == 0) {
 		error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va));
 	}
 
@@ -467,21 +577,22 @@ void syntax_error_va(Token token, char const *fmt, va_list va) {
 	}
 }
 
-void syntax_warning_va(Token token, 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(token, fmt, va);
+		syntax_error_va(pos, end, fmt, va);
 		return;
 	}
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.warning_count++;
 	if (!global_ignore_warnings()) {
 		// NOTE(bill): Duplicate error, skip it
-		if (global_error_collector.prev != token.pos) {
-			global_error_collector.prev = token.pos;
+		if (global_error_collector.prev != pos) {
+			global_error_collector.prev = pos;
 			error_out("%s Syntax Warning: %s\n",
-			          token_pos_to_string(token.pos),
+			          token_pos_to_string(pos),
 			          gb_bprintf_va(fmt, va));
-		} else if (token.pos.line == 0) {
+			show_error_on_line(pos, end);
+		} else if (pos.line == 0) {
 			error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
 		}
 	}
@@ -490,17 +601,17 @@ void syntax_warning_va(Token token, char const *fmt, va_list va) {
 
 
 
-void warning(Token token, char const *fmt, ...) {
+void warning(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	warning_va(token, fmt, va);
+	warning_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
-void error(Token token, char const *fmt, ...) {
+void error(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	error_va(token, fmt, va);
+	error_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
@@ -509,7 +620,7 @@ void error(TokenPos pos, char const *fmt, ...) {
 	va_start(va, fmt);
 	Token token = {};
 	token.pos = pos;
-	error_va(token, fmt, va);
+	error_va(pos, {}, fmt, va);
 	va_end(va);
 }
 
@@ -521,26 +632,24 @@ void error_line(char const *fmt, ...) {
 }
 
 
-void syntax_error(Token token, char const *fmt, ...) {
+void syntax_error(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	syntax_error_va(token, 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);
-	Token token = {};
-	token.pos = pos;
-	syntax_error_va(token, fmt, va);
+	syntax_error_va(pos, {}, fmt, va);
 	va_end(va);
 }
 
-void syntax_warning(Token token, char const *fmt, ...) {
+void syntax_warning(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
-	syntax_warning_va(token, fmt, va);
+	syntax_warning_va(token.pos, {}, fmt, va);
 	va_end(va);
 }
 
@@ -652,13 +761,14 @@ void tokenizer_err(Tokenizer *t, char const *msg, ...) {
 	if (column < 1) {
 		column = 1;
 	}
-	Token token = {};
-	token.pos.file_id = t->curr_file_id;
-	token.pos.line = t->line_count;
-	token.pos.column = cast(i32)column;
+	TokenPos pos = {};
+	pos.file_id = t->curr_file_id;
+	pos.line = t->line_count;
+	pos.column = cast(i32)column;
+	pos.offset = cast(i32)(t->read_curr - t->start);
 
 	va_start(va, msg);
-	syntax_error_va(token, msg, va);
+	syntax_error_va(pos, {}, msg, va);
 	va_end(va);
 
 	t->error_count++;
@@ -670,11 +780,9 @@ void tokenizer_err(Tokenizer *t, TokenPos const &pos, char const *msg, ...) {
 	if (column < 1) {
 		column = 1;
 	}
-	Token token = {};
-	token.pos = pos;
 
 	va_start(va, msg);
-	syntax_error_va(token, msg, va);
+	syntax_error_va(pos, {}, msg, va);
 	va_end(va);
 
 	t->error_count++;
@@ -1202,6 +1310,9 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
 				if (t->curr_rune == '<') {
 					advance_to_next_rune(t);
 					token->kind = Token_RangeHalf;
+				} else if (t->curr_rune == '=') {
+					advance_to_next_rune(t);
+					token->kind = Token_RangeFull;
 				}
 			} else if ('0' <= t->curr_rune && t->curr_rune <= '9') {
 				scan_number_to_token(t, token, true);
@@ -1287,6 +1398,10 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
 			if (t->curr_rune == '=') {
 				advance_to_next_rune(t);
 				token->kind = Token_AddEq;
+			} else if (t->curr_rune == '+') {
+				advance_to_next_rune(t);
+				token->kind = Token_Increment;
+				insert_semicolon = true;
 			}
 			break;
 		case '-':
@@ -1298,6 +1413,10 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
 				advance_to_next_rune(t);
 				advance_to_next_rune(t);
 				token->kind = Token_Undef;
+			} else if (t->curr_rune == '-') {
+				advance_to_next_rune(t);
+				token->kind = Token_Decrement;
+				insert_semicolon = true;
 			} else if (t->curr_rune == '>') {
 				advance_to_next_rune(t);
 				token->kind = Token_ArrowRight;

+ 0 - 19
src/types.cpp

@@ -128,21 +128,6 @@ enum StructSoaKind {
 	StructSoa_Dynamic = 3,
 };
 
-enum TypeAtomOpKind {
-	TypeAtomOp_Invalid,
-
-	TypeAtomOp_index_get,
-	TypeAtomOp_index_set,
-	TypeAtomOp_slice,
-	TypeAtomOp_index_get_ptr,
-
-	TypeAtomOp_COUNT,
-};
-
-struct TypeAtomOpTable {
-	Entity *op[TypeAtomOp_COUNT];
-};
-
 struct TypeStruct {
 	Array<Entity *> fields;
 	Array<String>   tags;
@@ -156,8 +141,6 @@ struct TypeStruct {
 	i64      custom_align;
 	Entity * names;
 
-	TypeAtomOpTable *atom_op_table;
-
 	Type *        soa_elem;
 	i64           soa_count;
 	StructSoaKind soa_kind;
@@ -180,8 +163,6 @@ struct TypeUnion {
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_parent;
 
-	TypeAtomOpTable *atom_op_table;
-
 	bool          no_nil;
 	bool          maybe;
 	bool          is_polymorphic;

+ 216 - 0
tools/odinfmt/flag/flag.odin

@@ -0,0 +1,216 @@
+package flag
+
+import "core:runtime"
+import "core:strings"
+import "core:reflect"
+import "core:fmt"
+import "core:mem"
+import "core:strconv"
+
+Flag_Error :: enum {
+	None,
+	No_Base_Struct,
+	Arg_Error,
+	Arg_Unsupported_Field_Type,
+	Arg_Not_Defined,
+	Arg_Non_Optional,
+	Value_Parse_Error,
+	Tag_Error,
+}
+
+Flag :: struct {
+	optional: bool,
+	type:     ^runtime.Type_Info,
+	data:     rawptr,
+	tag_ptr:  rawptr,
+	parsed:   bool,
+}
+
+Flag_Context :: struct {
+	seen_flags: map[string]Flag,
+}
+
+parse_args :: proc(ctx: ^Flag_Context, args: []string) -> Flag_Error {
+
+	using runtime;
+
+	args := args;
+
+	for true {
+
+		if len(args) == 0 {
+			return .None;
+		}
+
+		arg := args[0];
+
+		if len(arg) < 2 || arg[0] != '-' {
+			return .Arg_Error;
+		}
+
+		minus_count := 1;
+
+		if arg[1] == '-' {
+			minus_count += 1;
+
+			if len(arg) == 2 {
+				return .Arg_Error;
+			}
+		}
+
+		name := arg[minus_count:];
+
+		if len(name) == 0 {
+			return .Arg_Error;
+		}
+
+		args = args[1:];
+
+		assign_index := strings.index(name, "=");
+
+		value := "";
+
+		if assign_index > 0 {
+			value = name[assign_index + 1:];
+			name = name[0:assign_index];
+		}
+
+		flag := &ctx.seen_flags[name];
+
+		if flag == nil {
+			return .Arg_Not_Defined;
+		}
+
+		if reflect.is_boolean(flag.type) {
+			tmp := true;
+			mem.copy(flag.data, &tmp, flag.type.size);
+			flag.parsed = true;
+			continue;
+		} else
+
+		//must be in the next argument
+		if value == "" {
+
+			if len(args) == 0 {
+				return .Arg_Error;
+			}
+
+			value = args[0];
+			args = args[1:];
+		}
+
+		#partial switch _ in flag.type.variant {
+		case Type_Info_Integer:
+			if v, ok := strconv.parse_int(value); ok {
+				mem.copy(flag.data, &v, flag.type.size);
+			} else {
+				return .Value_Parse_Error;
+			}
+		case Type_Info_String:
+			raw_string := cast(^mem.Raw_String)flag.data;
+			raw_string.data = strings.ptr_from_string(value);
+			raw_string.len = len(value);
+		case Type_Info_Float:
+			switch flag.type.size {
+			case 32:
+				if v, ok := strconv.parse_f32(value); ok {
+					mem.copy(flag.data, &v, flag.type.size);
+				} else {
+					return .Value_Parse_Error;
+				}
+			case 64:
+				if v, ok := strconv.parse_f64(value); ok {
+					mem.copy(flag.data, &v, flag.type.size);
+				} else {
+					return .Value_Parse_Error;
+				}
+			}
+		}
+
+		flag.parsed = true;
+	}
+
+	return .None;
+}
+
+reflect_args_structure :: proc(ctx: ^Flag_Context, v: any) -> Flag_Error {
+	using runtime;
+
+	if !reflect.is_struct(type_info_of(v.id)) {
+		return .No_Base_Struct;
+	}
+
+	names := reflect.struct_field_names(v.id);
+	types := reflect.struct_field_types(v.id);
+	offsets := reflect.struct_field_offsets(v.id);
+	tags := reflect.struct_field_tags(v.id);
+
+	for name, i in names {
+		flag: Flag;
+
+		type := types[i];
+
+		if named_type, ok := type.variant.(Type_Info_Named); ok {
+			if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && union_type.maybe && len(union_type.variants) == 1 {
+				flag.optional = true;
+				flag.tag_ptr = rawptr(uintptr(union_type.tag_offset) + uintptr(v.data) + uintptr(offsets[i]));
+				type = union_type.variants[0];
+			} else {
+				return .Arg_Unsupported_Field_Type;
+			}
+		}
+
+		#partial switch _ in type.variant {
+		case Type_Info_Integer, Type_Info_String, Type_Info_Boolean, Type_Info_Float:
+			flag.type = type;
+			flag.data = rawptr(uintptr(v.data) + uintptr(offsets[i]));
+		case:
+			return .Arg_Unsupported_Field_Type;
+		}
+
+		flag_name: string;
+
+		if value, ok := reflect.struct_tag_lookup(tags[i], "flag"); ok {
+			flag_name = cast(string)value;
+		} else {
+			return .Tag_Error;
+		}
+
+		ctx.seen_flags[flag_name] = flag;
+	}
+
+	return .None;
+}
+
+parse :: proc(v: any, args: []string) -> Flag_Error {
+
+	if v == nil {
+		return .None;
+	}
+
+	ctx: Flag_Context;
+
+	if res := reflect_args_structure(&ctx, v); res != .None {
+		return res;
+	}
+
+	if res := parse_args(&ctx, args); res != .None {
+		return res;
+	}
+
+	//validate that the required flags were actually set
+	for k, v in ctx.seen_flags {
+		if v.optional && v.parsed {
+			tag_value: i32 = 1;
+			mem.copy(v.tag_ptr, &tag_value, 4); //4 constant is probably not portable, but it works for me currently
+		} else if !v.parsed && !v.optional {
+			return .Arg_Non_Optional;
+		}
+	}
+
+	return .None;
+}
+
+usage :: proc(v: any) -> string {
+	return "failed";
+}

+ 125 - 0
tools/odinfmt/main.odin

@@ -0,0 +1,125 @@
+package odinfmt
+
+import "core:os"
+import "core:odin/format"
+import "core:fmt"
+import "core:strings"
+import "core:path/filepath"
+import "core:time"
+import "core:mem"
+
+import "flag"
+
+Args :: struct {
+	write: Maybe(bool) `flag:"w" usage:"write the new format to file"`,
+}
+
+print_help :: proc(args: []string) {
+	if len(args) == 0 {
+		fmt.eprint("odinfmt ");
+	} else {
+		fmt.eprintf("%s ", args[0]);
+	}
+	fmt.eprintln();
+}
+
+print_arg_error :: proc(error: flag.Flag_Error) {
+	fmt.println(error);
+}
+
+format_file :: proc(filepath: string) -> (string, bool) {
+
+	if data, ok := os.read_entire_file(filepath); ok {
+		return format.format(string(data), format.default_style);
+	} else {
+		return "", false;
+	}
+}
+
+files: [dynamic]string;
+
+walk_files :: proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip_dir: bool) {
+	if info.is_dir {
+		return 0, false;
+	}
+
+	if filepath.ext(info.name) != ".odin" {
+		return 0, false;
+	}
+
+	append(&files, strings.clone(info.fullpath));
+
+	return 0, false;
+}
+
+main :: proc() {
+	init_global_temporary_allocator(mem.megabytes(100));
+
+	args: Args;
+
+	if len(os.args) < 2 {
+		print_help(os.args);
+		os.exit(1);
+	}
+
+	if res := flag.parse(args, os.args[1:len(os.args) - 1]); res != .None {
+		print_arg_error(res);
+		os.exit(1);
+	}
+
+	path := os.args[len(os.args) - 1];
+
+	tick_time := time.tick_now();
+
+	if os.is_file(path) {
+		if _, ok := args.write.(bool); ok {
+			backup_path := strings.concatenate({path, "_bk"});
+			defer delete(backup_path);
+
+			if data, ok := format_file(path); ok {
+				os.rename(path, backup_path);
+
+				if os.write_entire_file(path, transmute([]byte)data) {
+					os.remove(backup_path);
+				}
+			} else {
+				fmt.eprintf("failed to write %v", path);
+			}
+		} else {
+			if data, ok := format_file(path); ok {
+				fmt.println(data);
+			}
+		}
+	} else if os.is_dir(path) {
+		filepath.walk(path, walk_files);
+
+		for file in files {
+			fmt.println(file);
+
+			backup_path := strings.concatenate({file, "_bk"});
+			defer delete(backup_path);
+
+			if data, ok := format_file(file); ok {
+
+				if _, ok := args.write.(bool); ok {
+					os.rename(file, backup_path);
+
+					if os.write_entire_file(file, transmute([]byte)data) {
+						os.remove(backup_path);
+					}
+				} else {
+					fmt.println(data);
+				}
+			} else {
+				fmt.eprintf("failed to format %v", file);
+			}
+		}
+
+		fmt.printf("formatted %v files in %vms", len(files), time.duration_milliseconds(time.tick_lap_time(&tick_time)));
+	} else {
+		fmt.eprintf("%v is neither a directory nor a file \n", path);
+		os.exit(1);
+	}
+
+	os.exit(0);
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.