瀏覽代碼

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

jockus 4 年之前
父節點
當前提交
4455ba5b65
共有 66 個文件被更改,包括 5327 次插入2418 次删除
  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.**
 **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 [...]
 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
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 modification, are permitted provided that the following conditions are met:

+ 23 - 18
Makefile

@@ -8,26 +8,31 @@ CC=clang
 OS=$(shell uname)
 OS=$(shell uname)
 
 
 ifeq ($(OS), Darwin)
 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
 endif
 ifeq ($(OS), Linux)
 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
 endif
 
 
 all: debug demo
 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;
 	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};
 @(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;
 		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 {
 	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_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
 overflow_mul :: 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_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_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) ---
 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_has_nil :: proc($T: typeid) -> bool ---
 
 
 type_is_specialization_of :: proc($T, $S: 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 ---
 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;
 _GLOBAL_SEED_DATA := 1234567890;
-@(private, static)
+@(private)
 global_rand := create(u64(uintptr(&_GLOBAL_SEED_DATA)));
 global_rand := create(u64(uintptr(&_GLOBAL_SEED_DATA)));
 
 
 set_global_seed :: proc(seed: u64) {
 set_global_seed :: proc(seed: u64) {

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

@@ -16,16 +16,12 @@ Proc_Inlining :: enum u32 {
 	No_Inline = 2,
 	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 {
 Node_State_Flag :: enum {
@@ -69,7 +65,7 @@ File :: struct {
 	pkg: ^Package,
 	pkg: ^Package,
 
 
 	fullpath: string,
 	fullpath: string,
-	src:      []byte,
+	src:      string,
 
 
 	docs: ^Comment_Group,
 	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 := ast.new(ast.File, NO_POS, NO_POS);
 		file.pkg = pkg;
 		file.pkg = pkg;
-		file.src = src;
+		file.src = string(src);
 		file.fullpath = fullpath;
 		file.fullpath = fullpath;
 		pkg.files[fullpath] = file;
 		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);
 Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any);
 Error_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 {
 Parser :: struct {
 	file: ^ast.File,
 	file: ^ast.File,
 	tok: tokenizer.Tokenizer,
 	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,
 	warn: Warning_Handler,
 	err:  Error_Handler,
 	err:  Error_Handler,
 
 
@@ -100,8 +111,9 @@ end_pos :: proc(tok: tokenizer.Token) -> tokenizer.Pos {
 	return pos;
 	return pos;
 }
 }
 
 
-default_parser :: proc() -> Parser {
+default_parser :: proc(flags := Flags{}) -> Parser {
 	return Parser {
 	return Parser {
+		flags = flags,
 		err  = default_error_handler,
 		err  = default_error_handler,
 		warn = default_warning_handler,
 		warn = default_warning_handler,
 	};
 	};
@@ -128,6 +140,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
 		p.line_comment     = nil;
 		p.line_comment     = nil;
 	}
 	}
 
 
+	if .Optional_Semicolons in p.flags {
+		p.tok.flags += {.Insert_Semicolon};
+	}
+
 	p.file = file;
 	p.file = file;
 	tokenizer.init(&p.tok, file.src, file.fullpath, p.err);
 	tokenizer.init(&p.tok, file.src, file.fullpath, p.err);
 	if p.tok.ch <= 0 {
 	if p.tok.ch <= 0 {
@@ -400,6 +416,11 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	if node == nil {
 	if node == nil {
 		return false;
 		return false;
 	}
 	}
+
+	if .Optional_Semicolons in p.flags {
+		return true;
+	}
+
 	switch n in node.derived {
 	switch n in node.derived {
 	case ast.Empty_Stmt, ast.Block_Stmt:
 	case ast.Empty_Stmt, ast.Block_Stmt:
 		return true;
 		return true;
@@ -439,14 +460,34 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	return false;
 	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 {
 expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	if allow_token(p, .Semicolon) {
 	if allow_token(p, .Semicolon) {
+		expect_semicolon_newline_error(p, p.prev_tok, node);
 		return true;
 		return true;
 	}
 	}
 
 
 	prev := p.prev_tok;
 	prev := p.prev_tok;
 	if prev.kind == .Semicolon {
 	if prev.kind == .Semicolon {
+		expect_semicolon_newline_error(p, p.prev_tok, node);
 		return true;
 		return true;
 	}
 	}
 
 
@@ -615,7 +656,7 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
 		cond = parse_expr(p, false);
 		cond = parse_expr(p, false);
 	} else {
 	} else {
 		init = parse_simple_stmt(p, nil);
 		init = parse_simple_stmt(p, nil);
-		if allow_token(p, .Semicolon) {
+		if parse_control_statement_semicolon_separator(p) {
 			cond = parse_expr(p, false);
 			cond = parse_expr(p, false);
 		} else {
 		} else {
 			cond = convert_stmt_to_expr(p, init, "boolean expression");
 			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;
 	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 {
 parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 	if p.curr_proc == nil {
 	if p.curr_proc == nil {
 		error(p, p.curr_tok.pos, "you cannot use a for statement in the file scope");
 		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;
 			init = cond;
 			cond = nil;
 			cond = nil;
 			if p.curr_tok.kind != .Semicolon {
 			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});
 			tag = parse_simple_stmt(p, {Stmt_Allow_Flag.In});
 			if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In {
 			if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In {
 				is_type_switch = true;
 				is_type_switch = true;
-			} else if allow_token(p, .Semicolon) {
+			} else if parse_control_statement_semicolon_separator(p) {
 				init = tag;
 				init = tag;
 				tag = nil;
 				tag = nil;
 				if p.curr_tok.kind != .Open_Brace {
 				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);
 	open := expect_token(p, .Open_Brace);
 
 
 	for p.curr_tok.kind == .Case {
 	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;
 	defer p.in_foreign_block = prev_in_foreign_block;
 	p.in_foreign_block = true;
 	p.in_foreign_block = true;
 
 
+	skip_possible_newline_for_literal(p);
 	open := expect_token(p, .Open_Brace);
 	open := expect_token(p, .Open_Brace);
 	for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
 	for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
 		decl := parse_foreign_block_decl(p);
 		decl := parse_foreign_block_decl(p);
@@ -1287,7 +1342,7 @@ token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int {
 	#partial switch kind {
 	#partial switch kind {
 	case .Question, .If, .When:
 	case .Question, .If, .When:
 		return 1;
 		return 1;
-	case .Ellipsis, .Range_Half:
+	case .Ellipsis, .Range_Half, .Range_Full:
 		if !p.allow_range {
 		if !p.allow_range {
 			return 0;
 			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 {
 string_to_calling_convention :: proc(s: string) -> ast.Proc_Calling_Convention {
 	if s[0] != '"' && s[0] != '`' {
 	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) {
 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 {
 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 {
 	if p.curr_tok.kind == .String {
 		str := expect_token(p, .String);
 		str := expect_token(p, .String);
 		cc = string_to_calling_convention(str.text);
 		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);
 			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);
 	expect_token(p, .Open_Paren);
@@ -1976,23 +2015,6 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
 	return pt;
 	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 {
 parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr {
 	expr := parse_unary_expr(p, lhs);
 	expr := parse_unary_expr(p, lhs);
 
 
@@ -2224,6 +2246,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			p.expr_level = -1;
 			p.expr_level = -1;
 			where_clauses = parse_rhs_expr_list(p);
 			where_clauses = parse_rhs_expr_list(p);
 			p.expr_level = prev_level;
 			p.expr_level = prev_level;
+			tags = parse_proc_tags(p);
 		}
 		}
 		if p.allow_type && p.expr_level < 0 {
 		if p.allow_type && p.expr_level < 0 {
 			if where_token.kind != .Invalid {
 			if where_token.kind != .Invalid {
@@ -2233,6 +2256,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		}
 		}
 		body: ^ast.Stmt;
 		body: ^ast.Stmt;
 
 
+		skip_possible_newline_for_literal(p);
+
 		if allow_token(p, .Undef) {
 		if allow_token(p, .Undef) {
 			body = nil;
 			body = nil;
 			if where_token.kind != .Invalid {
 			if where_token.kind != .Invalid {
@@ -2358,7 +2383,6 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 				poly_params = nil;
 				poly_params = nil;
 			}
 			}
 			expect_token_after(p, .Close_Paren, "parameter list");
 			expect_token_after(p, .Close_Paren, "parameter list");
-			check_poly_params_for_type(p, poly_params, tok);
 		}
 		}
 
 
 		prev_level := p.expr_level;
 		prev_level := p.expr_level;
@@ -2405,6 +2429,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			p.expr_level = where_prev_level;
 			p.expr_level = where_prev_level;
 		}
 		}
 
 
+		skip_possible_newline_for_literal(p);
 		expect_token(p, .Open_Brace);
 		expect_token(p, .Open_Brace);
 		fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct);
 		fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct);
 		close := expect_token(p, .Close_Brace);
 		close := expect_token(p, .Close_Brace);
@@ -2434,7 +2459,6 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 				poly_params = nil;
 				poly_params = nil;
 			}
 			}
 			expect_token_after(p, .Close_Paren, "parameter list");
 			expect_token_after(p, .Close_Paren, "parameter list");
-			check_poly_params_for_type(p, poly_params, tok);
 		}
 		}
 
 
 		prev_level := p.expr_level;
 		prev_level := p.expr_level;
@@ -2473,6 +2497,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 
 
 		variants: [dynamic]^ast.Expr;
 		variants: [dynamic]^ast.Expr;
 
 
+		skip_possible_newline_for_literal(p);
 		expect_token_after(p, .Open_Brace, "union");
 		expect_token_after(p, .Open_Brace, "union");
 
 
 		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
 		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 {
 		if p.curr_tok.kind != .Open_Brace {
 			base_type = parse_type(p);
 			base_type = parse_type(p);
 		}
 		}
+
+		skip_possible_newline_for_literal(p);
 		open := expect_token(p, .Open_Brace);
 		open := expect_token(p, .Open_Brace);
 		fields := parse_elem_list(p);
 		fields := parse_elem_list(p);
 		close := expect_token(p, .Close_Brace);
 		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);
 		open := expect_token(p, .Open_Brace);
 		asm_string := parse_expr(p, false);
 		asm_string := parse_expr(p, false);
 		expect_token(p, .Comma);
 		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);
 			open := expect_token(p, .Open_Bracket);
 
 
 			#partial switch p.curr_tok.kind {
 			#partial switch p.curr_tok.kind {
-			case .Colon, .Ellipsis, .Range_Half:
+			case .Colon, .Ellipsis, .Range_Half, .Range_Full:
 				// NOTE(bill): Do not err yet
 				// NOTE(bill): Do not err yet
 				break;
 				break;
 			case:
 			case:
@@ -2819,7 +2847,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
 			}
 			}
 
 
 			#partial switch p.curr_tok.kind {
 			#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");
 				error(p, p.curr_tok.pos, "expected a colon, not a range");
 				fallthrough;
 				fallthrough;
 			case .Colon:
 			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.For_Stmt:         n.label = label;
 					case ast.Switch_Stmt:      n.label = label;
 					case ast.Switch_Stmt:      n.label = label;
 					case ast.Type_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,         // ,
 		Comma,         // ,
 		Ellipsis,      // ..
 		Ellipsis,      // ..
 		Range_Half,    // ..<
 		Range_Half,    // ..<
+		Range_Full,    // ..=
 		Back_Slash,    // \
 		Back_Slash,    // \
 	B_Operator_End,
 	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 {
 Tokenizer :: struct {
 	// Immutable data
 	// Immutable data
 	path: string,
 	path: string,
-	src:  []byte,
+	src:  string,
 	err:  Error_Handler,
 	err:  Error_Handler,
 
 
 	flags: Flags,
 	flags: Flags,
@@ -31,7 +31,7 @@ Tokenizer :: struct {
 	error_count: int,
 	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.src = src;
 	t.err = err;
 	t.err = err;
 	t.ch = ' ';
 	t.ch = ' ';
@@ -87,7 +87,7 @@ advance_rune :: proc(using t: ^Tokenizer) {
 		case r == 0:
 		case r == 0:
 			error(t, t.offset, "illegal character NUL");
 			error(t, t.offset, "illegal character NUL");
 		case r >= utf8.RUNE_SELF:
 		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 {
 			if r == utf8.RUNE_ERROR && w == 1 {
 				error(t, t.offset, "illegal UTF-8 encoding");
 				error(t, t.offset, "illegal UTF-8 encoding");
 			} else if r == utf8.RUNE_BOM && offset > 0 {
 			} 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);
 				kind = switch3(t, .And, .And_Eq, '&', .Cmp_And);
 			}
 			}
 		case '|': kind = switch3(t, .Or, .Or_Eq, '|', .Cmp_Or);
 		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, .Lt, .Lt_Eq, '<', .Shl, .Shl_Eq);
 		case '>': kind = switch4(t, .Gt, .Gt_Eq, '>', .Shr,.Shr_Eq);
 		case '>': kind = switch4(t, .Gt, .Gt_Eq, '>', .Shr,.Shr_Eq);
 
 
@@ -623,6 +623,9 @@ scan :: proc(t: ^Tokenizer) -> Token {
 					if t.ch == '<' {
 					if t.ch == '<' {
 						advance_rune(t);
 						advance_rune(t);
 						kind = .Range_Half;
 						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);
 	attribs := win32.GetFileAttributesW(wpath);
 
 
 	if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
 	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;
 	return false;
 }
 }
@@ -283,7 +283,7 @@ is_dir :: proc(path: string) -> bool {
 	attribs := win32.GetFileAttributesW(wpath);
 	attribs := win32.GetFileAttributesW(wpath);
 
 
 	if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
 	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;
 	return false;
 }
 }

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

@@ -1,11 +1,8 @@
 package os2
 package os2
 
 
-Platform_Error_Min_Bits :: 32;
+import "core:io"
 
 
-Error :: enum u64 {
-	None = 0,
-
-	// General Errors
+General_Error :: enum u32 {
 	Invalid_Argument,
 	Invalid_Argument,
 
 
 	Permission_Denied,
 	Permission_Denied,
@@ -13,42 +10,19 @@ Error :: enum u64 {
 	Not_Exist,
 	Not_Exist,
 	Closed,
 	Closed,
 
 
-	// Timeout Errors
 	Timeout,
 	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 {
 Path_Error :: struct {
 	op:   string,
 	op:   string,
@@ -83,20 +57,17 @@ link_error_delete :: proc(lerr: Maybe(Link_Error)) {
 
 
 
 
 is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
 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;
 	return;
 }
 }
 
 
-error_from_platform_error :: proc(errno: i32) -> Error {
-	return Error(u64(errno) << Platform_Error_Min_Bits);
-}
 
 
 error_string :: proc(ferr: Error) -> string {
 error_string :: proc(ferr: Error) -> string {
-	#partial switch ferr {
-	case .None:              return "";
+	switch ferr {
+	case nil:                return "";
 	case .Invalid_Argument:  return "invalid argument";
 	case .Invalid_Argument:  return "invalid argument";
 	case .Permission_Denied: return "permission denied";
 	case .Permission_Denied: return "permission denied";
 	case .Exist:             return "file already exists";
 	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)
 @(private)
 error_to_io_error :: proc(ferr: Error) -> io.Error {
 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
 package os2
 
 
 import "core:mem"
 import "core:mem"
+import "core:io"
 import "core:strconv"
 import "core:strconv"
 import "core:unicode/utf8"
 import "core:unicode/utf8"
 
 

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

@@ -5,19 +5,19 @@ import "core:io"
 import "core:time"
 import "core:time"
 
 
 _create :: proc(name: string) -> (Handle, Error) {
 _create :: proc(name: string) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 }
 
 
 _open :: proc(name: string) -> (Handle, Error) {
 _open :: proc(name: string) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 }
 
 
 _open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
 _open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
-	return 0, .None;
+	return 0, nil;
 }
 }
 
 
 _close :: proc(fd: Handle) -> Error {
 _close :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 }
 
 
 _name :: proc(fd: Handle, allocator := context.allocator) -> string {
 _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 {
 _sync :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 }
 
 
 _flush :: proc(fd: Handle) -> Error {
 _flush :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 }
 
 
 _truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
 _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 {
 _chdir :: proc(fd: Handle) -> Error {
-	return .None;
+	return nil;
 }
 }
 
 
 _chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
 _chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
-	return .None;
+	return nil;
 }
 }
 
 
 _chown :: proc(fd: Handle, uid, gid: int) -> Error {
 _chown :: proc(fd: Handle, uid, gid: int) -> Error {
-	return .None;
+	return nil;
 }
 }
 
 
 
 
 _lchown :: proc(name: string, uid, gid: int) -> Error {
 _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) {
 _pipe :: proc() -> (r, w: Handle, err: Error) {
 	p: [2]win32.HANDLE;
 	p: [2]win32.HANDLE;
 	if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
 	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;
 	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 {
 _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;
 		fd: win32.WIN32_FIND_DATAW;
 		sh := win32.FindFirstFileW(wname, &fd);
 		sh := win32.FindFirstFileW(wname, &fd);
 		if sh == win32.INVALID_HANDLE_VALUE {
 		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;
 			return;
 		}
 		}
 		win32.FindClose(sh);
 		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);
 	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil);
 	if h == win32.INVALID_HANDLE_VALUE {
 	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;
 		return;
 	}
 	}
 	defer win32.CloseHandle(h);
 	defer win32.CloseHandle(h);

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

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

+ 1 - 1
core/os/os_freebsd.odin

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

+ 3 - 3
core/os/os_linux.odin

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

+ 41 - 2
core/runtime/default_allocators.odin

@@ -1,6 +1,6 @@
 package runtime
 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
 	// mem.nil_allocator reimplementation
 
 
 	default_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 	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,
 			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
 	// TODO(bill): reimplement these procedures in the os_specific stuff
 	import "core:os"
 	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 {
 	if len < 0 {
 		return data;
 		return data;
 	}
 	}
-	memset(data, 0, len);
+	intrinsics.mem_zero(data, len);
 	return data;
 	return data;
 }
 }
 
 
@@ -105,17 +105,9 @@ mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr {
 	if src == nil {
 	if src == nil {
 		return dst;
 		return dst;
 	}
 	}
+
 	// NOTE(bill): This _must_ be implemented like C's memmove
 	// 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;
 	return dst;
 }
 }
 
 
@@ -123,17 +115,9 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r
 	if src == nil {
 	if src == nil {
 		return dst;
 		return dst;
 	}
 	}
+
 	// NOTE(bill): This _must_ be implemented like C's memcpy
 	// 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;
 	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;
 	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 {
 abs_f16 :: #force_inline proc "contextless" (x: f16) -> f16 {
 	return -x if x < 0 else x;
 	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 {
 abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 {
 	r, i := real(x), imag(x);
 	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 {
 abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 {
 	r, i := real(x), imag(x);
 	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 {
 abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 {
 	r, i := real(x), imag(x);
 	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 {
 abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 {
 	r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
 	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 {
 abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 {
 	r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
 	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 {
 abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 {
 	r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
 	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) {
 		if (e > 30) {
-			f := 1e12;
+			f := i64(1e12);
 			for j := 0; j < 10; j += 1 {
 			for j := 0; j < 10; j += 1 {
 				/* NOTE(bill): Cause overflow */
 				/* NOTE(bill): Cause overflow */
 				g := intrinsics.volatile_load(&f);
 				g := intrinsics.volatile_load(&f);

+ 8 - 0
core/runtime/os_specific.odin

@@ -1,3 +1,11 @@
 package runtime
 package runtime
 
 
 _OS_Errno :: distinct int;
 _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
 // 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
 // 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();
 	context = default_context();
 	n, err := os.write(os.stderr, data);
 	n, err := os.write(os.stderr, data);
 	return int(n), _OS_Errno(err);
 	return int(n), _OS_Errno(err);
 }
 }
 
 
-current_thread_id :: proc "contextless" () -> int {
+_current_thread_id :: proc "contextless" () -> int {
 	return os.current_thread_id();
 	return os.current_thread_id();
 }
 }

+ 2 - 2
core/runtime/os_specific_freestanding.odin

@@ -2,10 +2,10 @@
 package runtime
 package runtime
 
 
 // TODO(bill): reimplement `os.write`
 // 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;
 	return 0, -1;
 }
 }
 
 
-current_thread_id :: proc "contextless" () -> int {
+_current_thread_id :: proc "contextless" () -> int {
 	return 0;
 	return 0;
 }
 }

+ 41 - 71
core/runtime/os_specific_windows.odin

@@ -1,3 +1,4 @@
+//+private
 //+build windows
 //+build windows
 package runtime
 package runtime
 
 
@@ -24,7 +25,7 @@ foreign kernel32 {
 	HeapFree       :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 ---
 	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 {
 	if len(data) == 0 {
 		return 0, 0;
 		return 0, 0;
 	}
 	}
@@ -58,7 +59,7 @@ os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) {
 	return;
 	return;
 }
 }
 
 
-current_thread_id :: proc "contextless" () -> int {
+_current_thread_id :: proc "contextless" () -> int {
 	return int(GetCurrentThreadId());
 	return int(GetCurrentThreadId());
 }
 }
 
 
@@ -86,89 +87,58 @@ heap_free :: proc "contextless" (ptr: rawptr) {
 	HeapFree(GetProcessHeap(), 0, ptr);
 	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
 package runtime
 
 
-import "core:sys/es"
-
 @(link_name="memset")
 @(link_name="memset")
 memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr {
 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")
 @(link_name="memmove")
 memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
 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")
 @(link_name="memcpy")
 memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
 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";
 DIGITS_LOWER := "0123456789abcdefx";
 
 
 write_quoted_string :: proc{
 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;
 	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};
 @(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_acqrel  :: intrinsics.atomic_xchg_acqrel;
 atomic_exchange_relaxed :: intrinsics.atomic_xchg_relaxed;
 atomic_exchange_relaxed :: intrinsics.atomic_xchg_relaxed;
 
 
+// Returns value and optional ok boolean
 atomic_compare_exchange_strong                     :: intrinsics.atomic_cxchg;
 atomic_compare_exchange_strong                     :: intrinsics.atomic_cxchg;
 atomic_compare_exchange_strong_acquire             :: intrinsics.atomic_cxchg_acq;
 atomic_compare_exchange_strong_acquire             :: intrinsics.atomic_cxchg_acq;
 atomic_compare_exchange_strong_release             :: intrinsics.atomic_cxchg_rel;
 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_acquire_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed;
 atomic_compare_exchange_strong_acqrel_failrelaxed  :: intrinsics.atomic_cxchg_acqrel_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                     :: intrinsics.atomic_cxchgweak;
 atomic_compare_exchange_weak_acquire             :: intrinsics.atomic_cxchgweak_acq;
 atomic_compare_exchange_weak_acquire             :: intrinsics.atomic_cxchgweak_acq;
 atomic_compare_exchange_weak_release             :: intrinsics.atomic_cxchgweak_rel;
 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(m);
 }
 }
 
 
-// mutex_lock unlocks m
+// mutex_unlock unlocks m
 mutex_unlock :: proc(m: ^Mutex) {
 mutex_unlock :: proc(m: ^Mutex) {
 	_mutex_unlock(m);
 	_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
 // The zero value for a Recursive_Mutex is an unlocked mutex
 //
 //
 // A Recursive_Mutex must not be copied after first use
 // 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
 package sync2
 
 
-when !#config(ODIN_SYNC_USE_PTHREADS, true) {
-
 import "core:time"
 import "core:time"
 import "core:runtime"
 import "core:runtime"
 
 
-_Mutex_State :: enum i32 {
+Atomic_Mutex_State :: enum i32 {
 	Unlocked = 0,
 	Unlocked = 0,
 	Locked   = 1,
 	Locked   = 1,
 	Waiting  = 2,
 	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();
 			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
 		// 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;
 			return true;
 		}
 		}
 
 
-		mutex_unlock(&rw.impl.mutex);
+		atomic_mutex_unlock(&rw.mutex);
 	}
 	}
 	return false;
 	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;
 		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 {
 		if ok {
 			return;
 			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 {
 		if ok {
 			return true;
 			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;
 		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,
 	owner:     int,
 	recursion: int,
 	recursion: int,
 	mutex: Mutex,
 	mutex: Mutex,
 }
 }
 
 
-_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
+atomic_recursive_mutex_lock :: proc(m: ^Atomic_Recursive_Mutex) {
 	tid := runtime.current_thread_id();
 	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
 	// 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();
 	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 {
 	if recursion == 0 {
-		m.impl.owner = 0;
+		m.owner = 0;
 	}
 	}
 	if recursion == 0 {
 	if recursion == 0 {
-		mutex_unlock(&m.impl.mutex);
+		mutex_unlock(&m.mutex);
 	}
 	}
 	// outside the lock
 	// 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();
 	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;
 		return false;
 	}
 	}
 	// inside the lock
 	// inside the lock
-	m.impl.owner = tid;
-	m.impl.recursion += 1;
+	m.owner = tid;
+	m.recursion += 1;
 	return true;
 	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 {
 Queue_Item :: struct {
 	next: ^Queue_Item,
 	next: ^Queue_Item,
 	futex: i32,
 	futex: i32,
 }
 }
 
 
+@(private="file")
 queue_item_wait :: proc(item: ^Queue_Item) {
 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
 		// TODO(bill): Use a Futex here for Linux to improve performance and error handling
 		cpu_relax();
 		cpu_relax();
 	}
 	}
 }
 }
+@(private="file")
 queue_item_signal :: proc(item: ^Queue_Item) {
 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
 	// 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,
 	queue_head:  ^Queue_Item,
 	pending:     bool,
 	pending:     bool,
 }
 }
 
 
-_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
+atomic_cond_wait :: proc(c: ^Atomic_Cond, m: ^Atomic_Mutex) {
 	waiter := &Queue_Item{};
 	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);
 	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
 	// TODO(bill): _cond_wait_with_timeout for unix
 	return false;
 	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;
 		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 {
 	if waiter != nil {
 		queue_item_signal(waiter);
 		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;
 		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 {
 	for waiters != nil {
 		queue_item_signal(waiters);
 		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,
 	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
 //+private
 package sync2
 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) pLo    :: pLl | pLu; // a letter that is neither upper nor lower case.
 @(private) pLmask :: pLo;
 @(private) pLmask :: pLo;
 
 
-@(static)
 char_properties := [MAX_LATIN1+1]u8{
 char_properties := [MAX_LATIN1+1]u8{
 	0x00 = pC,       // '\x00'
 	0x00 = pC,       // '\x00'
 	0x01 = pC,       // '\x01'
 	0x01 = pC,       // '\x01'
@@ -273,7 +272,6 @@ char_properties := [MAX_LATIN1+1]u8{
 };
 };
 
 
 
 
-@(static)
 alpha_ranges := [?]i32{
 alpha_ranges := [?]i32{
 	0x00d8,  0x00f6,
 	0x00d8,  0x00f6,
 	0x00f8,  0x01f5,
 	0x00f8,  0x01f5,
@@ -429,7 +427,6 @@ alpha_ranges := [?]i32{
 	0xffda,  0xffdc,
 	0xffda,  0xffdc,
 };
 };
 
 
-@(static)
 alpha_singlets := [?]i32{
 alpha_singlets := [?]i32{
 	0x00aa,
 	0x00aa,
 	0x00b5,
 	0x00b5,
@@ -465,7 +462,6 @@ alpha_singlets := [?]i32{
 	0xfe74,
 	0xfe74,
 };
 };
 
 
-@(static)
 space_ranges := [?]i32{
 space_ranges := [?]i32{
 	0x0009,  0x000d, // tab and newline
 	0x0009,  0x000d, // tab and newline
 	0x0020,  0x0020, // space
 	0x0020,  0x0020, // space
@@ -481,7 +477,6 @@ space_ranges := [?]i32{
 	0xfeff,  0xfeff,
 	0xfeff,  0xfeff,
 };
 };
 
 
-@(static)
 unicode_spaces := [?]i32{
 unicode_spaces := [?]i32{
 	0x0009, // tab
 	0x0009, // tab
 	0x000a, // LF
 	0x000a, // LF
@@ -499,7 +494,6 @@ unicode_spaces := [?]i32{
 	0xfeff, // unknown
 	0xfeff, // unknown
 };
 };
 
 
-@(static)
 to_upper_ranges := [?]i32{
 to_upper_ranges := [?]i32{
 	0x0061,  0x007a, 468, // a-z A-Z
 	0x0061,  0x007a, 468, // a-z A-Z
 	0x00e0,  0x00f6, 468,
 	0x00e0,  0x00f6, 468,
@@ -538,7 +532,6 @@ to_upper_ranges := [?]i32{
 	0xff41,  0xff5a, 468,
 	0xff41,  0xff5a, 468,
 };
 };
 
 
-@(static)
 to_upper_singlets := [?]i32{
 to_upper_singlets := [?]i32{
 	0x00ff, 621,
 	0x00ff, 621,
 	0x0101, 499,
 	0x0101, 499,
@@ -882,7 +875,6 @@ to_upper_singlets := [?]i32{
 	0x1ff3, 509,
 	0x1ff3, 509,
 };
 };
 
 
-@(static)
 to_lower_ranges := [?]i32{
 to_lower_ranges := [?]i32{
 	0x0041,  0x005a, 532, // A-Z a-z
 	0x0041,  0x005a, 532, // A-Z a-z
 	0x00c0,  0x00d6, 532, // - -
 	0x00c0,  0x00d6, 532, // - -
@@ -922,7 +914,6 @@ to_lower_ranges := [?]i32{
 	0xff21,  0xff3a, 532, // - -
 	0xff21,  0xff3a, 532, // - -
 };
 };
 
 
-@(static)
 to_lower_singlets := [?]i32{
 to_lower_singlets := [?]i32{
 	0x0100, 501,
 	0x0100, 501,
 	0x0102, 501,
 	0x0102, 501,
@@ -1259,7 +1250,6 @@ to_lower_singlets := [?]i32{
 	0x1ffc, 491,
 	0x1ffc, 491,
 };
 };
 
 
-@(static)
 to_title_singlets := [?]i32{
 to_title_singlets := [?]i32{
 	0x01c4, 501,
 	0x01c4, 501,
 	0x01c6, 499,
 	0x01c6, 499,

+ 29 - 7
examples/demo/demo.odin

@@ -1352,8 +1352,8 @@ bit_set_type :: proc() {
 
 
 		d: Days;
 		d: Days;
 		d = {Sunday, Monday};
 		d = {Sunday, Monday};
-		e := d | WEEKEND;
-		e |= {Monday};
+		e := d + WEEKEND;
+		e += {Monday};
 		fmt.println(d, e);
 		fmt.println(d, e);
 
 
 		ok := Saturday in e; // `in` is only allowed for `map` and `bit_set` types
 		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(x))); // bit_set[A..Z]
 		fmt.println(typeid_of(type_of(y))); // bit_set[0..8; u16]
 		fmt.println(typeid_of(type_of(y))); // bit_set[0..8; u16]
 
 
-		incl(&x, 'F');
+		x += {'F'};
 		assert('F' in x);
 		assert('F' in x);
-		excl(&x, 'F');
+		x -= {'F'};
 		assert('F' not_in x);
 		assert('F' not_in x);
 
 
-		y |= {1, 4, 2};
+		y += {1, 4, 2};
 		assert(2 in y);
 		assert(2 in y);
 	}
 	}
 	{
 	{
@@ -1760,8 +1760,6 @@ range_statements_with_multiple_return_values :: proc() {
 
 
 
 
 soa_struct_layout :: 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");
 	fmt.println("\n#SOA Struct Layout");
 
 
 	{
 	{
@@ -1858,6 +1856,30 @@ soa_struct_layout :: proc() {
 		fmt.println(cap(d));
 		fmt.println(cap(d));
 		fmt.println(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() {
 constant_literal_expressions :: proc() {

+ 5 - 5
examples/demo_insert_semicolon/demo.odin

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

+ 5 - 0
src/build_settings.cpp

@@ -200,8 +200,10 @@ struct BuildContext {
 	bool   disallow_do;
 	bool   disallow_do;
 	bool   insert_semicolon;
 	bool   insert_semicolon;
 
 
+
 	bool   ignore_warnings;
 	bool   ignore_warnings;
 	bool   warnings_as_errors;
 	bool   warnings_as_errors;
+	bool   show_error_line;
 
 
 	bool   use_subsystem_windows;
 	bool   use_subsystem_windows;
 	bool   ignore_microsoft_magic;
 	bool   ignore_microsoft_magic;
@@ -746,6 +748,9 @@ String get_fullpath_core(gbAllocator a, String path) {
 	return path_to_fullpath(a, res);
 	return path_to_fullpath(a, res);
 }
 }
 
 
+bool show_error_line(void) {
+	return build_context.show_error_line;
+}
 
 
 
 
 void init_build_context(TargetMetrics *cross_target) {
 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: {
 	case BuiltinProc_DIRECTIVE: {
 		ast_node(bd, BasicDirective, ce->proc);
 		ast_node(bd, BasicDirective, ce->proc);
-		String name = bd->name;
+		String name = bd->name.string;
 		if (name == "defined") {
 		if (name == "defined") {
 			break;
 			break;
 		}
 		}
@@ -124,7 +124,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 
 
 	case BuiltinProc_DIRECTIVE: {
 	case BuiltinProc_DIRECTIVE: {
 		ast_node(bd, BasicDirective, ce->proc);
 		ast_node(bd, BasicDirective, ce->proc);
-		String name = bd->name;
+		String name = bd->name.string;
 		if (name == "location") {
 		if (name == "location") {
 			if (ce->args.count > 1) {
 			if (ce->args.count > 1) {
 				error(ce->args[0], "'#location' expects either 0 or 1 arguments, got %td", ce->args.count);
 				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;
 		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:
 	case BuiltinProc_atomic_fence_acq:
 	case BuiltinProc_atomic_fence_acq:
 	case BuiltinProc_atomic_fence_rel:
 	case BuiltinProc_atomic_fence_rel:
@@ -2429,6 +2551,46 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		}
 		}
 		break;
 		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:
 	case BuiltinProc_type_struct_field_count:
 		operand->value = exact_value_i64(0);
 		operand->value = exact_value_i64(0);
 		if (operand->mode != Addressing_Type) {
 		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) {
 	if (decl != nullptr) {
 		AttributeContext ac = {};
 		AttributeContext ac = {};
 		check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &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) {
 void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) {
 	GB_ASSERT(e->type == nullptr);
 	GB_ASSERT(e->type == nullptr);
 	GB_ASSERT(e->kind == Entity_Constant);
 	GB_ASSERT(e->kind == Entity_Constant);
+	init = unparen_expr(init);
 
 
 	if (e->flags & EntityFlag_Visited) {
 	if (e->flags & EntityFlag_Visited) {
 		e->type = t_invalid;
 		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->kind = Entity_TypeName;
 			e->type = nullptr;
 			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);
 			check_type_decl(ctx, e, ctx->decl->init_expr, named_type);
 			return;
 			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.thread_local_model = ac.thread_local_model;
 	e->Variable.is_export = ac.is_export;
 	e->Variable.is_export = ac.is_export;
+	e->flags &= ~EntityFlag_Static;
 	if (ac.is_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);
 	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) {
 	if (ac.link_name.len > 0) {
 		e->Variable.link_name = ac.link_name;
 		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) {
 	if (e->Variable.is_foreign || e->Variable.is_export) {
 		String name = e->token.string;
 		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 = {};
 		PolyProcData poly_proc_data = {};
 		if (check_polymorphic_procedure_assignment(c, operand, type, operand->expr, &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;
 			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 refers to", LIT(curr->token.string));
 				}
 				}
 				error(curr->token, "\t%.*s", LIT(curr->token.string));
 				error(curr->token, "\t%.*s", LIT(curr->token.string));
+				curr->type = t_invalid;
 			}
 			}
 			return true;
 			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);
 	Entity *e = scope_lookup(c->scope, name);
 	if (e == nullptr) {
 	if (e == nullptr) {
 		if (is_blank_ident(name)) {
 		if (is_blank_ident(name)) {
-			error(n, "'_' cannot be used as a value type");
+			error(n, "'_' cannot be used as a value");
 		} else {
 		} else {
 			error(n, "Undeclared name: %.*s", LIT(name));
 			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;
 		return;
 	}
 	}
 
 
+	if (is_type_untyped(y->type)) {
+		convert_to_typed(c, y, t_uint);
+	}
+
 	x->mode = Addressing_Value;
 	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 (core_type(bt)->kind == Type_Basic) {
 			if (check_representable_as_constant(c, x->value, bt, &x->value)) {
 			if (check_representable_as_constant(c, x->value, bt, &x->value)) {
 				return true;
 				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)) {
 	} else if (check_is_castable_to(c, x, type)) {
 		if (x->mode != Addressing_Constant) {
 		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)) {
 		} else if (is_type_union(type)) {
 			x->mode = Addressing_Value;
 			x->mode = Addressing_Value;
 		}
 		}
+		if (x->mode == Addressing_Value) {
+			x->value = {};
+		}
 		return true;
 		return true;
 	}
 	}
 	return false;
 	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)) {
 		if (token_is_comparison(be->op.kind)) {
 			// NOTE(bill): Do nothing as the types are fine
 			// NOTE(bill): Do nothing as the types are fine
 		} else if (token_is_shift(be->op.kind)) {
 		} 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 {
 		} else {
 			update_expr_type(c, be->left,  type, final);
 			update_expr_type(c, be->left,  type, final);
 			update_expr_type(c, be->right, 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;
 		break;
 	}
 	}
 
 
-	operand->type = target_type;
 	update_expr_type(c, operand->expr, target_type, true);
 	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) {
 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);
 							GB_ASSERT(bt->kind == Type_EnumeratedArray);
 							corrected_index = index + exact_value_to_i64(bt->EnumeratedArray.min_value);
 							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) {
 							if (lo <= corrected_index && corrected_index <= hi) {
 								TypeAndValue tav = fv->value->tav;
 								TypeAndValue tav = fv->value->tav;
 								if (success_) *success_ = true;
 								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);
 					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, val0);
 				array_add(operands, val1);
 				array_add(operands, val1);
 				optional_ok = true;
 				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);
 					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, val0);
 				array_add(operands, val1);
 				array_add(operands, val1);
 				optional_ok = true;
 				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) {
 CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 	ast_node(ce, CallExpr, call);
 	ast_node(ce, CallExpr, call);
 	GB_ASSERT(is_type_proc(proc_type));
 	GB_ASSERT(is_type_proc(proc_type));
@@ -4250,6 +4296,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 							err = CallArgumentError_WrongTypes;
 							err = CallArgumentError_WrongTypes;
 						}
 						}
 					}
 					}
+				} else if (show_error) {
+					check_assignment(c, &o, t, str_lit("argument"));
 				}
 				}
 				score += s;
 				score += s;
 
 
@@ -4265,7 +4313,10 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 				if (o.mode == Addressing_Type && is_type_typeid(e->type)) {
 				if (o.mode == Addressing_Type && is_type_typeid(e->type)) {
 					add_type_info_type(c, o.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));
 					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) {
 			if (variadic) {
@@ -4303,6 +4354,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 							check_assignment(c, &o, t, str_lit("argument"));
 							check_assignment(c, &o, t, str_lit("argument"));
 						}
 						}
 						err = CallArgumentError_WrongTypes;
 						err = CallArgumentError_WrongTypes;
+					} else if (show_error) {
+						check_assignment(c, &o, t, str_lit("argument"));
 					}
 					}
 					score += s;
 					score += s;
 					if (is_type_any(elem)) {
 					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)) {
 					if (o.mode == Addressing_Type && is_type_typeid(t)) {
 						add_type_info_type(c, o.type);
 						add_type_info_type(c, o.type);
 						add_type_and_value(c->info, o.expr, Addressing_Value, t, exact_value_typeid(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;
 						err = CallArgumentError_NoneConstantParameter;
 					}
 					}
 				}
 				}
+			} else if (show_error) {
+				check_assignment(c, o, e->type, str_lit("procedure argument"));
 			}
 			}
 			score += s;
 			score += s;
 		}
 		}
@@ -5459,7 +5516,7 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 	if (proc != nullptr &&
 	if (proc != nullptr &&
 	    proc->kind == Ast_BasicDirective) {
 	    proc->kind == Ast_BasicDirective) {
 		ast_node(bd, BasicDirective, proc);
 		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") {
 		if (name == "location" || name == "assert" || name == "panic" || name == "defined" || name == "config" || name == "load") {
 			operand->mode = Addressing_Builtin;
 			operand->mode = Addressing_Builtin;
 			operand->builtin_id = BuiltinProc_DIRECTIVE;
 			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);
 				check_expr(c, operand, arg);
 				if (operand->mode != Addressing_Invalid) {
 				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;
 				break;
 			}
 			}
 			}
 			}
@@ -5715,7 +5778,7 @@ void check_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *t) {
 		break;
 		break;
 	case Addressing_Type:
 	case Addressing_Type:
 		if (t == nullptr || !is_type_typeid(t)) {
 		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;
 		break;
 	case Addressing_Builtin:
 	case Addressing_Builtin:
@@ -5903,8 +5966,9 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu
 
 
 		TokenKind op = Token_Lt;
 		TokenKind op = Token_Lt;
 		switch (ie->op.kind) {
 		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;
 		default: error(ie->op, "Invalid range operator"); break;
 		}
 		}
 		bool ok = compare_exact_values(op, a, b);
 		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);
 		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);
 			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);
 	case_ast_node(bd, BasicDirective, node);
 		o->mode = Addressing_Constant;
 		o->mode = Addressing_Constant;
-		if (bd->name == "file") {
+		String name = bd->name.string;
+		if (name == "file") {
 			o->type = t_untyped_string;
 			o->type = t_untyped_string;
 			o->value = exact_value_string(get_file_path_string(bd->token.pos.file_id));
 			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->type = t_untyped_integer;
 			o->value = exact_value_i64(bd->token.pos.line);
 			o->value = exact_value_i64(bd->token.pos.line);
-		} else if (bd->name == "procedure") {
+		} else if (name == "procedure") {
 			if (c->curr_proc_decl == nullptr) {
 			if (c->curr_proc_decl == nullptr) {
 				error(node, "#procedure may only be used within procedures");
 				error(node, "#procedure may only be used within procedures");
 				o->type = t_untyped_string;
 				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->type = t_untyped_string;
 				o->value = exact_value_string(c->proc_name);
 				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);
 			init_core_source_code_location(c->checker);
 			error(node, "#caller_location may only be used as a default argument parameter");
 			error(node, "#caller_location may only be used as a default argument parameter");
 			o->type = t_source_code_location;
 			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) {
 					if (cl->type->ArrayType.tag != nullptr) {
 						Ast *tag = cl->type->ArrayType.tag;
 						Ast *tag = cl->type->ArrayType.tag;
 						GB_ASSERT(tag->kind == Ast_BasicDirective);
 						GB_ASSERT(tag->kind == Ast_BasicDirective);
-						String name = tag->BasicDirective.name;
+						String name = tag->BasicDirective.name.string;
 						if (name == "soa") {
 						if (name == "soa") {
 							error(node, "#soa arrays are not supported for compound literals");
 							error(node, "#soa arrays are not supported for compound literals");
 							return kind;
 							return kind;
@@ -6321,7 +6386,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 				if (cl->elems.count > 0) {
 				if (cl->elems.count > 0) {
 					Ast *tag = cl->type->DynamicArrayType.tag;
 					Ast *tag = cl->type->DynamicArrayType.tag;
 					GB_ASSERT(tag->kind == Ast_BasicDirective);
 					GB_ASSERT(tag->kind == Ast_BasicDirective);
-					String name = tag->BasicDirective.name;
+					String name = tag->BasicDirective.name.string;
 					if (name == "soa") {
 					if (name == "soa") {
 						error(node, "#soa arrays are not supported for compound literals");
 						error(node, "#soa arrays are not supported for compound literals");
 						return kind;
 						return kind;
@@ -7536,47 +7601,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			return Expr_Expr;
 			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;
 		i64 max_count = -1;
 		bool valid = check_set_index_data(o, t, is_ptr, &max_count, o->type);
 		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)) {
 			if (is_type_soa_struct(t)) {
 				valid = true;
 				valid = true;
 				o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem);
 				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;
 			break;
 
 
@@ -7774,10 +7767,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			return kind;
 			return kind;
 		}
 		}
 
 
-		o->mode = Addressing_Value;
-
 		if (se->low == nullptr && se->high != nullptr) {
 		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
 			// 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) {
 		if (is_type_string(t) && max_count >= 0) {
 			bool all_constant = true;
 			bool all_constant = true;
 			for (isize i = 0; i < gb_count_of(nodes); i++) {
 			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);
 	case_ast_node(bd, BasicDirective, node);
 		str = gb_string_append_rune(str, '#');
 		str = gb_string_append_rune(str, '#');
-		str = string_append_string(str, bd->name);
+		str = string_append_string(str, bd->name.string);
 	case_end;
 	case_end;
 
 
 	case_ast_node(ud, Undef, node);
 	case_ast_node(ud, Undef, node);

+ 70 - 55
src/check_stmt.cpp

@@ -7,7 +7,7 @@ bool is_diverging_stmt(Ast *stmt) {
 		return false;
 		return false;
 	}
 	}
 	if (expr->CallExpr.proc->kind == Ast_BasicDirective) {
 	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";
 		return name == "panic";
 	}
 	}
 	Ast *proc = unparen_expr(expr->CallExpr.proc);
 	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;
 				TokenKind upper_op = Token_Invalid;
 				switch (be->op.kind) {
 				switch (be->op.kind) {
 				case Token_Ellipsis:  upper_op = Token_GtEq; break;
 				case Token_Ellipsis:  upper_op = Token_GtEq; break;
+				case Token_RangeFull: upper_op = Token_GtEq; break;
 				case Token_RangeHalf: upper_op = Token_Gt;   break;
 				case Token_RangeHalf: upper_op = Token_Gt;   break;
 				default: GB_PANIC("Invalid range operator"); 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;
 				Operand b1 = rhs;
 				check_comparison(ctx, &a1, &b1, Token_LtEq);
 				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)) {
 				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);
 			gbString expr_str = expr_to_string(operand.expr);
 			error(node, "Expression is not used: '%s'", expr_str);
 			error(node, "Expression is not used: '%s'", expr_str);
 			gb_string_free(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;
 			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);
 			auto lhs_to_ignore = array_make<bool>(temporary_allocator(), lhs_count);
 
 
 			isize max = gb_min(lhs_count, rhs_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++) {
 			for (isize i = 0; i < max; i++) {
 				if (lhs_to_ignore[i]) {
 				if (lhs_to_ignore[i]) {
 					continue;
 					continue;
@@ -1526,8 +1537,8 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 			}
 			}
 			Operand lhs = {Addressing_Invalid};
 			Operand lhs = {Addressing_Invalid};
 			Operand rhs = {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 = op;
 			be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
 			be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
 			 // NOTE(bill): Only use the first one will be used
 			 // 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];
 			be->right = as->rhs[0];
 
 
 			check_expr(ctx, &lhs, as->lhs[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) {
 			if (rhs.mode == Addressing_Invalid) {
 				return;
 				return;
 			}
 			}
@@ -1632,7 +1643,11 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 		} else {
 		} else {
 			for (isize i = 0; i < result_count; i++) {
 			for (isize i = 0; i < result_count; i++) {
 				Entity *e = pt->results->Tuple.variables[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;
 	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);
 		array_add(&array, e);
 		map_set(&ctx->checker->info.gen_types, hash_pointer(original_type), array);
 		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,
 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) {
 		switch (be->op.kind) {
 		case Token_Ellipsis:
 		case Token_Ellipsis:
+		case Token_RangeFull:
 			if (upper - lower >= bits) {
 			if (upper - lower >= bits) {
 				error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, (upper-lower+1));
 				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 &&
 	if (allow_caller_location &&
 	    expr->kind == Ast_BasicDirective &&
 	    expr->kind == Ast_BasicDirective &&
-	    expr->BasicDirective.name == "caller_location") {
+	    expr->BasicDirective.name.string == "caller_location") {
 		init_core_source_code_location(ctx->checker);
 		init_core_source_code_location(ctx->checker);
 		param_value.kind = ParameterValue_Location;
 		param_value.kind = ParameterValue_Location;
 		o.type = t_source_code_location;
 		o.type = t_source_code_location;
+
+		if (in_type) {
+			check_assignment(ctx, &o, in_type, str_lit("parameter value"));
+		}
+
 	} else {
 	} else {
 		if (in_type) {
 		if (in_type) {
 			check_expr_with_type_hint(ctx, &o, expr, 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);
 			check_expr(ctx, &o, expr);
 		}
 		}
 
 
+		if (in_type) {
+			check_assignment(ctx, &o, in_type, str_lit("parameter value"));
+		}
+
+
 		if (is_operand_nil(o)) {
 		if (is_operand_nil(o)) {
 			param_value.kind = ParameterValue_Nil;
 			param_value.kind = ParameterValue_Nil;
 		} else if (o.mode != Addressing_Constant) {
 		} 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.kind = ParameterValue_Constant;
 				param_value.value = exact_value_procedure(expr);
 				param_value.value = exact_value_procedure(expr);
 			} else {
 			} 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 != nullptr) {
 					if (e->kind == Entity_Procedure) {
 					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) {
 				} else if (allow_caller_location && o.mode == Addressing_Context) {
 					param_value.kind = ParameterValue_Value;
 					param_value.kind = ParameterValue_Value;
 					param_value.ast_value = expr;
 					param_value.ast_value = expr;
+				} else if (o.value.kind != ExactValue_Invalid) {
+					param_value.kind = ParameterValue_Constant;
+					param_value.value = o.value;
 				} else {
 				} else {
-					error(expr, "Default parameter must be a constant");
+					error(expr, "Default parameter must be a constant, %d", o.mode);
 				}
 				}
 			}
 			}
 		} else {
 		} 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;
 	return param_value;
 }
 }
 
 
@@ -1389,6 +1383,9 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
 				}
 				}
 			}
 			}
 		}
 		}
+
+
+
 		if (type == nullptr) {
 		if (type == nullptr) {
 			error(param, "Invalid parameter type");
 			error(param, "Invalid parameter type");
 			type = t_invalid;
 			type = t_invalid;
@@ -1408,6 +1405,21 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
 			type = t_invalid;
 			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->flags&FieldFlag_c_vararg) {
 			if (p->type == nullptr ||
 			if (p->type == nullptr ||
@@ -2517,16 +2529,6 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 					return true;
 					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;
 			return true;
 		}
 		}
 
 
@@ -2709,7 +2711,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 				bool is_partial = false;
 				bool is_partial = false;
 				if (at->tag != nullptr) {
 				if (at->tag != nullptr) {
 					GB_ASSERT(at->tag->kind == Ast_BasicDirective);
 					GB_ASSERT(at->tag->kind == Ast_BasicDirective);
-					String name = at->tag->BasicDirective.name;
+					String name = at->tag->BasicDirective.name.string;
 					if (name == "partial") {
 					if (name == "partial") {
 						is_partial = true;
 						is_partial = true;
 					} else {
 					} else {
@@ -2743,7 +2745,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 
 
 			if (at->tag != nullptr) {
 			if (at->tag != nullptr) {
 				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
 				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
-				String name = at->tag->BasicDirective.name;
+				String name = at->tag->BasicDirective.name.string;
 				if (name == "soa") {
 				if (name == "soa") {
 					*type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type);
 					*type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type);
 				} else if (name == "simd") {
 				} 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) {
 			if (at->tag != nullptr) {
 				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
 				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
-				String name = at->tag->BasicDirective.name;
+				String name = at->tag->BasicDirective.name.string;
 				if (name == "soa") {
 				if (name == "soa") {
 					*type = make_soa_struct_slice(ctx, e, at->elem, elem);
 					*type = make_soa_struct_slice(ctx, e, at->elem, elem);
 				} else {
 				} 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);
 		Type *elem = check_type(ctx, dat->elem);
 		if (dat->tag != nullptr) {
 		if (dat->tag != nullptr) {
 			GB_ASSERT(dat->tag->kind == Ast_BasicDirective);
 			GB_ASSERT(dat->tag->kind == Ast_BasicDirective);
-			String name = dat->tag->BasicDirective.name;
+			String name = dat->tag->BasicDirective.name.string;
 			if (name == "soa") {
 			if (name == "soa") {
 				*type = make_soa_struct_dynamic_array(ctx, e, dat->elem, elem);
 				*type = make_soa_struct_dynamic_array(ctx, e, dat->elem, elem);
 			} else {
 			} 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));
 			error(elem, "Expected a string value for '%.*s'", LIT(name));
 		}
 		}
 		return true;
 		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;
 	return false;
 }
 }
@@ -2666,206 +2676,6 @@ DECL_ATTRIBUTE_PROC(type_decl_attribute) {
 	if (name == "private") {
 	if (name == "private") {
 		// NOTE(bill): Handled elsewhere `check_collect_value_decl`
 		// NOTE(bill): Handled elsewhere `check_collect_value_decl`
 		return true;
 		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;
 	return false;
 }
 }

+ 1 - 1
src/checker.hpp

@@ -109,12 +109,12 @@ struct AttributeContext {
 	bool    set_cold;
 	bool    set_cold;
 	String  link_name;
 	String  link_name;
 	String  link_prefix;
 	String  link_prefix;
+	String  link_section;
 	isize   init_expr_list_count;
 	isize   init_expr_list_count;
 	String  thread_local_model;
 	String  thread_local_model;
 	String  deprecated_message;
 	String  deprecated_message;
 	DeferredProcedure deferred_procedure;
 	DeferredProcedure deferred_procedure;
 	u32 optimization_mode; // ProcedureOptimizationMode
 	u32 optimization_mode; // ProcedureOptimizationMode
-	struct TypeAtomOpTable *atom_op_table;
 };
 };
 
 
 AttributeContext make_attribute_context(String link_prefix) {
 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_sub,
 	BuiltinProc_overflow_mul,
 	BuiltinProc_overflow_mul,
 
 
+	BuiltinProc_sqrt,
+
+	BuiltinProc_mem_copy,
+	BuiltinProc_mem_copy_non_overlapping,
+	BuiltinProc_mem_zero,
+
 	BuiltinProc_volatile_store,
 	BuiltinProc_volatile_store,
 	BuiltinProc_volatile_load,
 	BuiltinProc_volatile_load,
 
 
@@ -197,6 +203,8 @@ BuiltinProc__type_simple_boolean_end,
 
 
 	BuiltinProc_type_is_specialization_of,
 	BuiltinProc_type_is_specialization_of,
 
 
+	BuiltinProc_type_is_variant_of,
+
 	BuiltinProc_type_struct_field_count,
 	BuiltinProc_type_struct_field_count,
 
 
 	BuiltinProc_type_proc_parameter_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_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("overflow_mul"), 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_store"),  2, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT("volatile_load"),   1, false, Expr_Expr, 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_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_struct_field_count"),   1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 
 	{STR_LIT("type_proc_parameter_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;
 			Ast *      foreign_library_ident;
 			String     link_name;
 			String     link_name;
 			String     link_prefix;
 			String     link_prefix;
+			String     link_section;
 			bool       is_foreign;
 			bool       is_foreign;
 			bool       is_export;
 			bool       is_export;
 		} Variable;
 		} Variable;

+ 77 - 54
src/llvm_abi.cpp

@@ -10,21 +10,37 @@ struct lbArgType {
 	LLVMTypeRef cast_type;      // Optional
 	LLVMTypeRef cast_type;      // Optional
 	LLVMTypeRef pad_type;       // Optional
 	LLVMTypeRef pad_type;       // Optional
 	LLVMAttributeRef attribute; // 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) {
 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) {
 lbArgType lb_arg_type_direct(LLVMTypeRef type) {
 	return lb_arg_type_direct(type, nullptr, nullptr, nullptr);
 	return lb_arg_type_direct(type, nullptr, nullptr, nullptr);
 }
 }
 
 
 lbArgType lb_arg_type_indirect(LLVMTypeRef type, LLVMAttributeRef attr) {
 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) {
 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 {
 struct lbFunctionType {
@@ -121,6 +137,9 @@ void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType *ft, ProcCa
 		if (arg->attribute) {
 		if (arg->attribute) {
 			LLVMAddAttributeAtIndex(fn, arg_index+1, arg->attribute);
 			LLVMAddAttributeAtIndex(fn, arg_index+1, arg->attribute);
 		}
 		}
+		if (arg->align_attribute) {
+			LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute);
+		}
 
 
 		arg_index++;
 		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) {
 i64 lb_sizeof(LLVMTypeRef type) {
 	LLVMTypeKind kind = LLVMGetTypeKind(type);
 	LLVMTypeKind kind = LLVMGetTypeKind(type);
@@ -328,7 +345,7 @@ namespace lbAbi386 {
 				if (sz == 0) {
 				if (sz == 0) {
 					args[i] = lb_arg_type_ignore(t);
 					args[i] = lb_arg_type_ignore(t);
 				} else {
 				} else {
-					args[i] = lb_arg_type_indirect(t, lb_create_enum_attribute(c, "byval"));
+					args[i] = lb_arg_type_indirect(t, nullptr);
 				}
 				}
 			} else {
 			} else {
 				args[i] = non_struct(c, t, false);
 				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 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);
 			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 lb_arg_type_indirect(return_type, attr);
 		}
 		}
 		return non_struct(c, return_type, true);
 		return non_struct(c, return_type, true);
@@ -419,8 +436,14 @@ namespace lbAbiAmd64SysV {
 		switch (reg_class) {
 		switch (reg_class) {
 		case RegClass_SSEFs:
 		case RegClass_SSEFs:
 		case RegClass_SSEFv:
 		case RegClass_SSEFv:
+		case RegClass_SSEDs:
 		case RegClass_SSEDv:
 		case RegClass_SSEDv:
 			return true;
 			return true;
+		case RegClass_SSEInt8:
+		case RegClass_SSEInt16:
+		case RegClass_SSEInt32:
+		case RegClass_SSEInt64:
+			return true;
 		}
 		}
 		return false;
 		return false;
 	}
 	}
@@ -437,11 +460,10 @@ namespace lbAbiAmd64SysV {
 		Amd64TypeAttribute_StructRect,
 		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);
 	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 classify_with(LLVMTypeRef t, Array<RegClass> *cls, i64 ix, i64 off);
 	void fixup(LLVMTypeRef t, Array<RegClass> *cls);
 	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);
 	Array<RegClass> classify(LLVMTypeRef t);
 	LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const &reg_classes);
 	LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const &reg_classes);
 
 
@@ -452,11 +474,11 @@ namespace lbAbiAmd64SysV {
 
 
 		ft->args = array_make<lbArgType>(heap_allocator(), arg_count);
 		ft->args = array_make<lbArgType>(heap_allocator(), arg_count);
 		for (unsigned i = 0; i < arg_count; i++) {
 		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) {
 		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 {
 		} else {
 			ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c));
 			ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c));
 		}
 		}
@@ -493,7 +515,7 @@ namespace lbAbiAmd64SysV {
 		return false;
 		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)) {
 		if (is_register(type)) {
 			LLVMAttributeRef attribute = nullptr;
 			LLVMAttributeRef attribute = nullptr;
 			if (type == LLVMInt1TypeInContext(c)) {
 			if (type == LLVMInt1TypeInContext(c)) {
@@ -506,9 +528,12 @@ namespace lbAbiAmd64SysV {
 		if (is_mem_cls(cls, attribute_kind)) {
 		if (is_mem_cls(cls, attribute_kind)) {
 			LLVMAttributeRef attribute = nullptr;
 			LLVMAttributeRef attribute = nullptr;
 			if (attribute_kind == Amd64TypeAttribute_ByVal) {
 			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) {
 			} 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);
 			return lb_arg_type_indirect(type, attribute);
 		} else {
 		} else {
@@ -538,30 +563,48 @@ namespace lbAbiAmd64SysV {
 		return reg_classes;
 		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) {
 		if (oldv == newv) {
 			return;
 			return;
-		} else if (oldv == RegClass_NoClass) {
-			oldv = newv;
+		}
+
+		RegClass to_write = newv;
+		if (oldv == RegClass_NoClass) {
+			to_write = newv;
 		} else if (newv == RegClass_NoClass) {
 		} else if (newv == RegClass_NoClass) {
 			return;
 			return;
 		} else if (oldv == RegClass_Memory || newv == RegClass_Memory) {
 		} 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) {
 	void fixup(LLVMTypeRef t, Array<RegClass> *cls) {
 		i64 i = 0;
 		i64 i = 0;
 		i64 e = cls->count;
 		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];
 			RegClass &oldv = (*cls)[i];
 			if (is_sse(oldv)) {
 			if (is_sse(oldv)) {
 				for (i++; i < e; i++) {
 				for (i++; i < e; i++) {
@@ -605,8 +648,8 @@ namespace lbAbiAmd64SysV {
 
 
 	unsigned llvec_len(Array<RegClass> const &reg_classes, isize offset) {
 	unsigned llvec_len(Array<RegClass> const &reg_classes, isize offset) {
 		unsigned len = 1;
 		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;
 				break;
 			}
 			}
 			len++;
 			len++;
@@ -617,7 +660,7 @@ namespace lbAbiAmd64SysV {
 
 
 	LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const &reg_classes) {
 	LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const &reg_classes) {
 		auto types = array_make<LLVMTypeRef>(heap_allocator(), 0, reg_classes.count);
 		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];
 			RegClass reg_class = reg_classes[i];
 			switch (reg_class) {
 			switch (reg_class) {
 			case RegClass_Int:
 			case RegClass_Int:
@@ -659,7 +702,7 @@ namespace lbAbiAmd64SysV {
 						break;
 						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);
 					LLVMTypeRef vec_type = LLVMVectorType(elem_type, vec_len * elems_per_word);
 					array_add(&types, vec_type);
 					array_add(&types, vec_type);
 					i += vec_len;
 					i += vec_len;
@@ -675,9 +718,9 @@ namespace lbAbiAmd64SysV {
 			default:
 			default:
 				GB_PANIC("Unhandled RegClass");
 				GB_PANIC("Unhandled RegClass");
 			}
 			}
+			i += 1;
 		}
 		}
 
 
-		GB_ASSERT(types.count != 0);
 		if (types.count == 1) {
 		if (types.count == 1) {
 			return types[0];
 			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) {
 	lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined) {
 		if (!return_is_defined) {
 		if (!return_is_defined) {
 			return lb_arg_type_direct(LLVMVoidTypeInContext(c));
 			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 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);
 			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 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) {
 		} 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);
 			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);
 				return lb_arg_type_direct(type, cast_type, nullptr, nullptr);
 			} else {
 			} 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);
 				return lb_arg_type_indirect(type, attr);
 			}
 			}
 		}
 		}

文件差異過大導致無法顯示
+ 579 - 302
src/llvm_backend.cpp


+ 9 - 3
src/llvm_backend.hpp

@@ -215,6 +215,12 @@ enum lbProcedureFlag : u32 {
 	lbProcedureFlag_WithoutMemcpyPass = 1<<0,
 	lbProcedureFlag_WithoutMemcpyPass = 1<<0,
 };
 };
 
 
+struct lbCopyElisionHint {
+	lbValue ptr;
+	Ast *   ast;
+	bool    used;
+};
+
 struct lbProcedure {
 struct lbProcedure {
 	u32 flags;
 	u32 flags;
 	u16 state_flags;
 	u16 state_flags;
@@ -260,9 +266,7 @@ struct lbProcedure {
 
 
 	LLVMMetadataRef debug_info;
 	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 = {});
 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(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, u64 value);
 void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name);
 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);
 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);
 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"
 #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_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_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) {
 void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) {
 	LLVMAddPromoteMemoryToRegisterPass(fpm);
 	LLVMAddPromoteMemoryToRegisterPass(fpm);
@@ -15,11 +33,13 @@ void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) {
 	LLVMAddCFGSimplificationPass(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
 	// NOTE(bill): Treat -opt:3 as if it was -opt:2
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	optimization_level = gb_clamp(optimization_level, 0, 2);
 	optimization_level = gb_clamp(optimization_level, 0, 2);
 
 
+	lb_add_must_preserve_predicate_pass(m, fpm, optimization_level);
+
 	if (ignore_memcpy_pass) {
 	if (ignore_memcpy_pass) {
 		lb_basic_populate_function_pass_manager(fpm);
 		lb_basic_populate_function_pass_manager(fpm);
 		return;
 		return;
@@ -57,11 +77,13 @@ void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcp
 #endif
 #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
 	// NOTE(bill): Treat -opt:3 as if it was -opt:2
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	optimization_level = gb_clamp(optimization_level, 0, 2);
 	optimization_level = gb_clamp(optimization_level, 0, 2);
 
 
+	lb_add_must_preserve_predicate_pass(m, fpm, optimization_level);
+
 	if (optimization_level == 0) {
 	if (optimization_level == 0) {
 		LLVMAddMemCpyOptPass(fpm);
 		LLVMAddMemCpyOptPass(fpm);
 		lb_basic_populate_function_pass_manager(fpm);
 		lb_basic_populate_function_pass_manager(fpm);
@@ -226,3 +248,104 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa
 
 
 	LLVMAddCFGSimplificationPass(mpm);
 	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_IgnoreWarnings,
 	BuildFlag_WarningsAsErrors,
 	BuildFlag_WarningsAsErrors,
+	BuildFlag_VerboseErrors,
 
 
 #if defined(GB_SYSTEM_WINDOWS)
 #if defined(GB_SYSTEM_WINDOWS)
 	BuildFlag_IgnoreVsSearch,
 	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_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_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)
 #if defined(GB_SYSTEM_WINDOWS)
 	add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"),  BuildFlagParam_None, Command__does_build);
 	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;
 							break;
 
 
+						case BuildFlag_VerboseErrors:
+							build_context.show_error_line = true;
+							break;
+
 					#if defined(GB_SYSTEM_WINDOWS)
 					#if defined(GB_SYSTEM_WINDOWS)
 						case BuildFlag_IgnoreVsSearch:
 						case BuildFlag_IgnoreVsSearch:
 							GB_ASSERT(value.kind == ExactValue_Invalid);
 							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(2, "Disables automatic linking with the C Run Time");
 		print_usage_line(0, "");
 		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(2, "Use the LLD linker rather than the default");
 		print_usage_line(0, "");
 		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) {
 Token token_end_of_line(AstFile *f, Token tok) {
 	u8 const *start = f->tokenizer.start + tok.pos.offset;
 	u8 const *start = f->tokenizer.start + tok.pos.offset;
@@ -115,6 +10,48 @@ Token token_end_of_line(AstFile *f, Token tok) {
 	return 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) {
 isize ast_node_size(AstKind kind) {
 	return align_formula_isize(gb_size_of(AstCommonStuff) + ast_variant_sizes[kind], gb_align_of(void *));
 	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, ...) {
 void error(Ast *node, char const *fmt, ...) {
 	Token token = {};
 	Token token = {};
+	TokenPos end_pos = {};
 	if (node != nullptr) {
 	if (node != nullptr) {
 		token = ast_token(node);
 		token = ast_token(node);
+		end_pos = ast_end_pos(node);
 	}
 	}
+
 	va_list va;
 	va_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	error_va(token, fmt, va);
+	error_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
 		node->file->error_count += 1;
@@ -451,7 +391,7 @@ void error_no_newline(Ast *node, char const *fmt, ...) {
 	}
 	}
 	va_list va;
 	va_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	error_no_newline_va(token, fmt, va);
+	error_no_newline_va(token.pos, fmt, va);
 	va_end(va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
 		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, ...) {
 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_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	warning_va(ast_token(node), fmt, va);
+	warning_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 	va_end(va);
 }
 }
 
 
 void syntax_error(Ast *node, char const *fmt, ...) {
 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_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	syntax_error_va(ast_token(node), fmt, va);
+	syntax_error_va(token.pos, end_pos, fmt, va);
 	va_end(va);
 	va_end(va);
 	if (node != nullptr && node->file != nullptr) {
 	if (node != nullptr && node->file != nullptr) {
 		node->file->error_count += 1;
 		node->file->error_count += 1;
@@ -640,7 +592,7 @@ Ast *ast_basic_lit(AstFile *f, Token basic_lit) {
 	return result;
 	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);
 	Ast *result = alloc_ast_node(f, Ast_BasicDirective);
 	result->BasicDirective.token = token;
 	result->BasicDirective.token = token;
 	result->BasicDirective.name = name;
 	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) {
 bool is_token_range(TokenKind kind) {
 	switch (kind) {
 	switch (kind) {
 	case Token_Ellipsis:
 	case Token_Ellipsis:
+	case Token_RangeFull:
 	case Token_RangeHalf:
 	case Token_RangeHalf:
 		return true;
 		return true;
 	}
 	}
@@ -1574,6 +1527,10 @@ void expect_semicolon(AstFile *f, Ast *s) {
 		return;
 		return;
 	}
 	}
 
 
+	if (f->curr_token.kind == Token_EOF) {
+		return;
+	}
+
 	if (s != nullptr) {
 	if (s != nullptr) {
 		bool insert_semi = (f->tokenizer.flags & TokenizerFlag_InsertSemicolon) != 0;
 		bool insert_semi = (f->tokenizer.flags & TokenizerFlag_InsertSemicolon) != 0;
 		if (insert_semi) {
 		if (insert_semi) {
@@ -1994,35 +1951,28 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 		Token name = expect_token(f, Token_Ident);
 		Token name = expect_token(f, Token_Ident);
 		if (name.string == "type") {
 		if (name.string == "type") {
 			return ast_helper_type(f, token, parse_type(f));
 			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") {
 		} 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);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "load") {
 		} 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);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "assert") {
 		} 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);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "defined") {
 		} 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);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "config") {
 		} 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);
 			return parse_call_expr(f, tag);
 		} else if (name.string == "soa" || name.string == "simd") {
 		} 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 *original_type = parse_type(f);
 			Ast *type = unparen_expr(original_type);
 			Ast *type = unparen_expr(original_type);
 			switch (type->kind) {
 			switch (type->kind) {
@@ -2034,7 +1984,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			}
 			}
 			return original_type;
 			return original_type;
 		} else if (name.string == "partial") {
 		} 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 *original_type = parse_type(f);
 			Ast *type = unparen_expr(original_type);
 			Ast *type = unparen_expr(original_type);
 			switch (type->kind) {
 			switch (type->kind) {
@@ -2046,6 +1996,10 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			return original_type;
 			return original_type;
 		} else if (name.string == "bounds_check") {
 		} else if (name.string == "bounds_check") {
 			Ast *operand = parse_expr(f, lhs);
 			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;
 			operand->state_flags |= StateFlag_bounds_check;
 			if ((operand->state_flags & StateFlag_no_bounds_check) != 0) {
 			if ((operand->state_flags & StateFlag_no_bounds_check) != 0) {
 				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
 				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;
 			return operand;
 		} else if (name.string == "no_bounds_check") {
 		} else if (name.string == "no_bounds_check") {
 			Ast *operand = parse_expr(f, lhs);
 			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;
 			operand->state_flags |= StateFlag_no_bounds_check;
 			if ((operand->state_flags & StateFlag_bounds_check) != 0) {
 			if ((operand->state_flags & StateFlag_bounds_check) != 0) {
 				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
 				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
 			}
 			}
 			return operand;
 			return operand;
 		} else if (name.string == "relative") {
 		} 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);
 			tag = parse_call_expr(f, tag);
 			Ast *type = parse_type(f);
 			Ast *type = parse_type(f);
 			return ast_relative_type(f, tag, type);
 			return ast_relative_type(f, tag, type);
@@ -2314,7 +2272,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 			f->expr_level = prev_level;
 			f->expr_level = prev_level;
 		}
 		}
 
 
-
+		skip_possible_newline_for_literal(f);
 		Token open = expect_token_after(f, Token_OpenBrace, "struct");
 		Token open = expect_token_after(f, Token_OpenBrace, "struct");
 
 
 		isize name_count = 0;
 		isize name_count = 0;
@@ -2674,6 +2632,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 
 
 			switch (f->curr_token.kind) {
 			switch (f->curr_token.kind) {
 			case Token_Ellipsis:
 			case Token_Ellipsis:
+			case Token_RangeFull:
 			case Token_RangeHalf:
 			case Token_RangeHalf:
 				// NOTE(bill): Do not err yet
 				// NOTE(bill): Do not err yet
 			case Token_Colon:
 			case Token_Colon:
@@ -2685,6 +2644,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 
 
 			switch (f->curr_token.kind) {
 			switch (f->curr_token.kind) {
 			case Token_Ellipsis:
 			case Token_Ellipsis:
+			case Token_RangeFull:
 			case Token_RangeHalf:
 			case Token_RangeHalf:
 				syntax_error(f->curr_token, "Expected a colon, not a range");
 				syntax_error(f->curr_token, "Expected a colon, not a range");
 				/* fallthrough */
 				/* fallthrough */
@@ -2723,6 +2683,16 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 			}
 			}
 			break;
 			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:
 		default:
 			loop = false;
 			loop = false;
 			break;
 			break;
@@ -2753,16 +2723,26 @@ Ast *parse_unary_expr(AstFile *f, bool lhs) {
 		return ast_auto_cast(f, token, expr);
 		return ast_auto_cast(f, token, expr);
 	}
 	}
 
 
+
 	case Token_Add:
 	case Token_Add:
 	case Token_Sub:
 	case Token_Sub:
-	case Token_Not:
 	case Token_Xor:
 	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);
 		Token token = advance_token(f);
+		syntax_error(token, "Unary '%.*s' operator is not supported", LIT(token.string));
 		Ast *expr = parse_unary_expr(f, lhs);
 		Ast *expr = parse_unary_expr(f, lhs);
 		return ast_unary_expr(f, token, expr);
 		return ast_unary_expr(f, token, expr);
 	}
 	}
 
 
+
 	case Token_Period: {
 	case Token_Period: {
 		Token token = expect_token(f, Token_Period);
 		Token token = expect_token(f, Token_Period);
 		Ast *ident = parse_ident(f);
 		Ast *ident = parse_ident(f);
@@ -2791,6 +2771,7 @@ i32 token_precedence(AstFile *f, TokenKind t) {
 	case Token_when:
 	case Token_when:
 		return 1;
 		return 1;
 	case Token_Ellipsis:
 	case Token_Ellipsis:
+	case Token_RangeFull:
 	case Token_RangeHalf:
 	case Token_RangeHalf:
 		if (!f->allow_range) {
 		if (!f->allow_range) {
 			return 0;
 			return 0;
@@ -3152,6 +3133,13 @@ Ast *parse_simple_stmt(AstFile *f, u32 flags) {
 		return ast_bad_stmt(f, token, f->curr_token);
 		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
 	#if 0
@@ -3898,12 +3886,6 @@ Ast *parse_return_stmt(AstFile *f) {
 
 
 	while (f->curr_token.kind != Token_Semicolon) {
 	while (f->curr_token.kind != Token_Semicolon) {
 		Ast *arg = parse_expr(f, false);
 		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);
 		array_add(&results, arg);
 		if (f->curr_token.kind != Token_Comma ||
 		if (f->curr_token.kind != Token_Comma ||
 		    f->curr_token.kind == Token_EOF) {
 		    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_range = prev_allow_range;
 	f->allow_in_expr = prev_allow_in_expr;
 	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);
 	Array<Ast *> stmts = parse_stmt_list(f);
 
 
 	return ast_case_clause(f, token, list, stmts);
 	return ast_case_clause(f, token, list, stmts);
@@ -4482,10 +4464,10 @@ Ast *parse_stmt(AstFile *f) {
 			}
 			}
 			return s;
 			return s;
 		} else if (tag == "assert") {
 		} 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));
 			return ast_expr_stmt(f, parse_call_expr(f, t));
 		} else if (tag == "panic") {
 		} 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));
 			return ast_expr_stmt(f, parse_call_expr(f, t));
 		} else if (name.string == "force_inline" ||
 		} else if (name.string == "force_inline" ||
 		           name.string == "force_no_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);
 	GB_ASSERT(f != nullptr);
 	f->fullpath = string_trim_whitespace(fullpath); // Just in case
 	f->fullpath = string_trim_whitespace(fullpath); // Just in case
 	set_file_path_string(f->id, fullpath);
 	set_file_path_string(f->id, fullpath);
+	set_ast_file_from_id(f->id, f);
 	if (!string_ends_with(f->fullpath, str_lit(".odin"))) {
 	if (!string_ends_with(f->fullpath, str_lit(".odin"))) {
 		return ParseFile_WrongExtension;
 		return ParseFile_WrongExtension;
 	}
 	}

+ 25 - 20
src/parser.hpp

@@ -7,22 +7,21 @@ struct AstFile;
 struct AstPackage;
 struct AstPackage;
 
 
 enum AddressingMode {
 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 {
 struct TypeAndValue {
@@ -287,8 +286,8 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = {
 		Token token; \
 		Token token; \
 	}) \
 	}) \
 	AST_KIND(BasicDirective, "basic directive", struct { \
 	AST_KIND(BasicDirective, "basic directive", struct { \
-		Token  token; \
-		String name; \
+		Token token; \
+		Token name; \
 	}) \
 	}) \
 	AST_KIND(Ellipsis,       "ellipsis", struct { \
 	AST_KIND(Ellipsis,       "ellipsis", struct { \
 		Token    token; \
 		Token    token; \
@@ -325,7 +324,7 @@ AST_KIND(_ExprBegin,  "",  bool) \
 	AST_KIND(ImplicitSelectorExpr, "implicit selector expression",    struct { Token token; Ast *selector; }) \
 	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(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(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_KIND(SliceExpr,    "slice expression", struct { \
 		Ast *expr; \
 		Ast *expr; \
 		Token open, close; \
 		Token open, close; \
@@ -345,7 +344,13 @@ AST_KIND(_ExprBegin,  "",  bool) \
 	AST_KIND(FieldValue,      "field value",              struct { Token eq; Ast *field, *value; }) \
 	AST_KIND(FieldValue,      "field value",              struct { Token eq; Ast *field, *value; }) \
 	AST_KIND(TernaryIfExpr,   "ternary if expression",    struct { Ast *x, *cond, *y; }) \
 	AST_KIND(TernaryIfExpr,   "ternary if expression",    struct { Ast *x, *cond, *y; }) \
 	AST_KIND(TernaryWhenExpr, "ternary when 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(TypeCast,      "type cast",           struct { Token token; Ast *type, *expr; }) \
 	AST_KIND(AutoCast,      "auto_cast",           struct { Token token; Ast *expr; }) \
 	AST_KIND(AutoCast,      "auto_cast",           struct { Token token; Ast *expr; }) \
 	AST_KIND(InlineAsmExpr, "inline asm expression", struct { \
 	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_CmpAndEq, "&&="), \
 	TOKEN_KIND(Token_CmpOrEq,  "||="), \
 	TOKEN_KIND(Token_CmpOrEq,  "||="), \
 TOKEN_KIND(Token__AssignOpEnd, ""), \
 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__ComparisonBegin, ""), \
 	TOKEN_KIND(Token_CmpEq, "=="), \
 	TOKEN_KIND(Token_CmpEq, "=="), \
@@ -74,6 +76,7 @@ TOKEN_KIND(Token__ComparisonEnd, ""), \
 	TOKEN_KIND(Token_Period,        "."),   \
 	TOKEN_KIND(Token_Period,        "."),   \
 	TOKEN_KIND(Token_Comma,         ","),   \
 	TOKEN_KIND(Token_Comma,         ","),   \
 	TOKEN_KIND(Token_Ellipsis,      ".."),  \
 	TOKEN_KIND(Token_Ellipsis,      ".."),  \
+	TOKEN_KIND(Token_RangeFull,     "..="), \
 	TOKEN_KIND(Token_RangeHalf,     "..<"), \
 	TOKEN_KIND(Token_RangeHalf,     "..<"), \
 	TOKEN_KIND(Token_BackSlash,     "\\"),  \
 	TOKEN_KIND(Token_BackSlash,     "\\"),  \
 TOKEN_KIND(Token__OperatorEnd, ""), \
 TOKEN_KIND(Token__OperatorEnd, ""), \
@@ -185,9 +188,11 @@ void init_keyword_hash_table(void) {
 	GB_ASSERT(max_keyword_size < 16);
 	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 {
 struct TokenPos {
 	i32 file_id;
 	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.errors, heap_allocator());
 	array_init(&global_error_collector.error_buffer, heap_allocator());
 	array_init(&global_error_collector.error_buffer, heap_allocator());
 	array_init(&global_file_path_strings, heap_allocator(), 4096);
 	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;
 	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) {
 String get_file_path_string(i32 index) {
 	GB_ASSERT(index >= 0);
 	GB_ASSERT(index >= 0);
 	gb_mutex_lock(&global_error_collector.string_mutex);
 	gb_mutex_lock(&global_error_collector.string_mutex);
@@ -315,6 +339,20 @@ String get_file_path_string(i32 index) {
 	return path;
 	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) {
 void begin_error_block(void) {
 	gb_mutex_lock(&global_error_collector.mutex);
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.in_block = true;
 	global_error_collector.in_block = true;
@@ -374,6 +412,8 @@ ErrorOutProc *error_out_va = default_error_out_va;
 // NOTE: defined in build_settings.cpp
 // NOTE: defined in build_settings.cpp
 bool global_warnings_as_errors(void);
 bool global_warnings_as_errors(void);
 bool global_ignore_warnings(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, ...) {
 void error_out(char const *fmt, ...) {
 	va_list va;
 	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);
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
 	// 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));
 		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",
 		error_out("%s %s\n",
-		          token_pos_to_string(token.pos),
+		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
 		          gb_bprintf_va(fmt, va));
+		show_error_on_line(pos, end);
 	}
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	gb_mutex_unlock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
 	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()) {
 	if (global_warnings_as_errors()) {
-		error_va(token, fmt, va);
+		error_va(pos, end, fmt, va);
 		return;
 		return;
 	}
 	}
 	gb_mutex_lock(&global_error_collector.mutex);
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.warning_count++;
 	global_error_collector.warning_count++;
 	if (!global_ignore_warnings()) {
 	if (!global_ignore_warnings()) {
 		// NOTE(bill): Duplicate error, skip it
 		// 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));
 			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",
 			error_out("%s Warning: %s\n",
-			          token_pos_to_string(token.pos),
+			          token_pos_to_string(pos),
 			          gb_bprintf_va(fmt, va));
 			          gb_bprintf_va(fmt, va));
+			show_error_on_line(pos, end);
 		}
 		}
 	}
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	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);
 	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);
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
 	// NOTE(bill): Duplicate error, skip it
-	if (token.pos.line == 0) {
+	if (pos.line == 0) {
 		error_out("Error: %s", gb_bprintf_va(fmt, va));
 		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",
 		error_out("%s %s",
-		          token_pos_to_string(token.pos),
+		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
 		          gb_bprintf_va(fmt, va));
 	}
 	}
 	gb_mutex_unlock(&global_error_collector.mutex);
 	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);
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.count++;
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
 	// 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",
 		error_out("%s Syntax Error: %s\n",
-		          token_pos_to_string(token.pos),
+		          token_pos_to_string(pos),
 		          gb_bprintf_va(fmt, va));
 		          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));
 		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()) {
 	if (global_warnings_as_errors()) {
-		syntax_error_va(token, fmt, va);
+		syntax_error_va(pos, end, fmt, va);
 		return;
 		return;
 	}
 	}
 	gb_mutex_lock(&global_error_collector.mutex);
 	gb_mutex_lock(&global_error_collector.mutex);
 	global_error_collector.warning_count++;
 	global_error_collector.warning_count++;
 	if (!global_ignore_warnings()) {
 	if (!global_ignore_warnings()) {
 		// NOTE(bill): Duplicate error, skip it
 		// 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",
 			error_out("%s Syntax Warning: %s\n",
-			          token_pos_to_string(token.pos),
+			          token_pos_to_string(pos),
 			          gb_bprintf_va(fmt, va));
 			          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));
 			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_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	warning_va(token, fmt, va);
+	warning_va(token.pos, {}, fmt, va);
 	va_end(va);
 	va_end(va);
 }
 }
 
 
-void error(Token token, char const *fmt, ...) {
+void error(Token const &token, char const *fmt, ...) {
 	va_list va;
 	va_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	error_va(token, fmt, va);
+	error_va(token.pos, {}, fmt, va);
 	va_end(va);
 	va_end(va);
 }
 }
 
 
@@ -509,7 +620,7 @@ void error(TokenPos pos, char const *fmt, ...) {
 	va_start(va, fmt);
 	va_start(va, fmt);
 	Token token = {};
 	Token token = {};
 	token.pos = pos;
 	token.pos = pos;
-	error_va(token, fmt, va);
+	error_va(pos, {}, fmt, va);
 	va_end(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_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	syntax_error_va(token, fmt, va);
+	syntax_error_va(token.pos, {}, fmt, va);
 	va_end(va);
 	va_end(va);
 }
 }
 
 
 void syntax_error(TokenPos pos, char const *fmt, ...) {
 void syntax_error(TokenPos pos, char const *fmt, ...) {
 	va_list va;
 	va_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	Token token = {};
-	token.pos = pos;
-	syntax_error_va(token, fmt, va);
+	syntax_error_va(pos, {}, fmt, va);
 	va_end(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_list va;
 	va_start(va, fmt);
 	va_start(va, fmt);
-	syntax_warning_va(token, fmt, va);
+	syntax_warning_va(token.pos, {}, fmt, va);
 	va_end(va);
 	va_end(va);
 }
 }
 
 
@@ -652,13 +761,14 @@ void tokenizer_err(Tokenizer *t, char const *msg, ...) {
 	if (column < 1) {
 	if (column < 1) {
 		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);
 	va_start(va, msg);
-	syntax_error_va(token, msg, va);
+	syntax_error_va(pos, {}, msg, va);
 	va_end(va);
 	va_end(va);
 
 
 	t->error_count++;
 	t->error_count++;
@@ -670,11 +780,9 @@ void tokenizer_err(Tokenizer *t, TokenPos const &pos, char const *msg, ...) {
 	if (column < 1) {
 	if (column < 1) {
 		column = 1;
 		column = 1;
 	}
 	}
-	Token token = {};
-	token.pos = pos;
 
 
 	va_start(va, msg);
 	va_start(va, msg);
-	syntax_error_va(token, msg, va);
+	syntax_error_va(pos, {}, msg, va);
 	va_end(va);
 	va_end(va);
 
 
 	t->error_count++;
 	t->error_count++;
@@ -1202,6 +1310,9 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
 				if (t->curr_rune == '<') {
 				if (t->curr_rune == '<') {
 					advance_to_next_rune(t);
 					advance_to_next_rune(t);
 					token->kind = Token_RangeHalf;
 					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') {
 			} else if ('0' <= t->curr_rune && t->curr_rune <= '9') {
 				scan_number_to_token(t, token, true);
 				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 == '=') {
 			if (t->curr_rune == '=') {
 				advance_to_next_rune(t);
 				advance_to_next_rune(t);
 				token->kind = Token_AddEq;
 				token->kind = Token_AddEq;
+			} else if (t->curr_rune == '+') {
+				advance_to_next_rune(t);
+				token->kind = Token_Increment;
+				insert_semicolon = true;
 			}
 			}
 			break;
 			break;
 		case '-':
 		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);
 				advance_to_next_rune(t);
 				advance_to_next_rune(t);
 				token->kind = Token_Undef;
 				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 == '>') {
 			} else if (t->curr_rune == '>') {
 				advance_to_next_rune(t);
 				advance_to_next_rune(t);
 				token->kind = Token_ArrowRight;
 				token->kind = Token_ArrowRight;

+ 0 - 19
src/types.cpp

@@ -128,21 +128,6 @@ enum StructSoaKind {
 	StructSoa_Dynamic = 3,
 	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 {
 struct TypeStruct {
 	Array<Entity *> fields;
 	Array<Entity *> fields;
 	Array<String>   tags;
 	Array<String>   tags;
@@ -156,8 +141,6 @@ struct TypeStruct {
 	i64      custom_align;
 	i64      custom_align;
 	Entity * names;
 	Entity * names;
 
 
-	TypeAtomOpTable *atom_op_table;
-
 	Type *        soa_elem;
 	Type *        soa_elem;
 	i64           soa_count;
 	i64           soa_count;
 	StructSoaKind soa_kind;
 	StructSoaKind soa_kind;
@@ -180,8 +163,6 @@ struct TypeUnion {
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_parent;
 	Type *        polymorphic_parent;
 
 
-	TypeAtomOpTable *atom_op_table;
-
 	bool          no_nil;
 	bool          no_nil;
 	bool          maybe;
 	bool          maybe;
 	bool          is_polymorphic;
 	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);
+}

部分文件因文件數量過多而無法顯示