Browse Source

++ -- statements; add strconv.odin (and replace some of the fmt procs); Fix ~ on 64 bit constants; Fix integer casts from smaller to larger size

Ginger Bill 8 years ago
parent
commit
c59f6b7d0b
17 changed files with 879 additions and 174 deletions
  1. 10 5
      code/demo.odin
  2. 3 3
      core/_preload.odin
  3. 2 2
      core/atomic.odin
  4. 258 0
      core/decimal.odin
  5. 86 124
      core/fmt.odin
  6. 3 3
      core/hash.odin
  7. 3 3
      core/mem.odin
  8. 337 0
      core/strconv.odin
  9. 1 1
      core/strings.odin
  10. 3 3
      core/sync.odin
  11. 51 7
      core/utf8.odin
  12. 19 8
      src/check_expr.c
  13. 46 2
      src/check_stmt.c
  14. 4 3
      src/exact_value.c
  15. 22 1
      src/ir.c
  16. 22 0
      src/parser.c
  17. 9 9
      src/tokenizer.c

+ 10 - 5
code/demo.odin

@@ -5,14 +5,19 @@
 #import "mem.odin";
 #import "opengl.odin";
 #import "os.odin";
-
-foo :: proc(x: int) -> f32 {
-	return 123;
-}
+#import "strconv.odin";
 
 main :: proc() {
+	buf: [64]byte;
+	// len := strconv.generic_ftoa(buf[:], 123.5431, 'f', 4, 64);
+	x := 624.123;
+	s := strconv.format_float(buf[:], x, 'f', 6, 64);
+	fmt.println(s);
+	fmt.printf("%.3f\n", x);
+
+	i := 123;
+	fmt.println(123);
 
-	fmt.printf("%T\n", foo);
 
 when false {
 /*

+ 3 - 3
core/_preload.odin

@@ -447,7 +447,7 @@ __dynamic_array_append_nothing :: proc(array_: rawptr, elem_size, elem_align: in
 	data := cast(^byte)array.data;
 	assert(data != nil);
 	mem.zero(data + (elem_size*array.count), elem_size);
-	array.count += 1;
+	array.count++;
 	return array.count;
 }
 
@@ -506,7 +506,7 @@ __dynamic_map_rehash :: proc(using header: __Map_Header, new_count: int) {
 		nm.hashes[i] = -1;
 	}
 
-	for i := 0; i < nm.entries.count; i += 1 {
+	for i := 0; i < nm.entries.count; i++ {
 		entry_header := __dynamic_map_get_entry(new_header, i);
 		data := cast(^byte)entry_header;
 
@@ -645,7 +645,7 @@ __dynamic_map_erase :: proc(using h: __Map_Header, fr: __Map_Find_Result) {
 	}
 
 	if fr.entry_index == m.entries.count-1 {
-		m.entries.count -= 1;
+		m.entries.count--;
 	}
 	mem.copy(__dynamic_map_get_entry(h, fr.entry_index), __dynamic_map_get_entry(h, m.entries.count-1), entry_size);
 	last := __dynamic_map_find(h, __dynamic_map_get_entry(h, fr.entry_index).key);

+ 2 - 2
core/atomic.odin

@@ -37,7 +37,7 @@ spin_lock :: proc(a: ^i32, time_out: int) -> bool { // NOTE(bill) time_out = -1
 	old_value := compare_exchange(a, 1, 0);
 	counter := 0;
 	for old_value != 0 && (time_out < 0 || counter < time_out) {
-		counter += 1;
+		counter++;
 		yield_thread();
 		old_value = compare_exchange(a, 1, 0);
 		mfence();
@@ -81,7 +81,7 @@ spin_lock :: proc(a: ^i64, time_out: int) -> bool { // NOTE(bill) time_out = -1
 	old_value := compare_exchange(a, 1, 0);
 	counter := 0;
 	for old_value != 0 && (time_out < 0 || counter < time_out) {
-		counter += 1;
+		counter++;
 		yield_thread();
 		old_value = compare_exchange(a, 1, 0);
 		mfence();

+ 258 - 0
core/decimal.odin

@@ -0,0 +1,258 @@
+// #import "fmt.odin";
+// Multiple precision decimal numbers
+// NOTE: This is only for floating point printing and nothing else
+
+Decimal :: struct {
+	d:     [384]byte, // big-endian digits
+	ndu:   int,
+	dp:    int,
+	neg:   bool,
+	trunc: bool,
+}
+
+decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
+	digit_zero :: proc(buf: []byte) -> int {
+		for _, i in buf {
+			buf[i] = '0';
+		}
+		return buf.count;
+	}
+
+
+	n := 10 + a.ndu + abs(a.dp);
+
+	// TODO(bill): make this work with a buffer that's not big enough
+	assert(buf.count >= n);
+	buf = buf[:n];
+
+	if a.ndu == 0 {
+		buf[0] = '0';
+		return cast(string)buf[0:1];
+	}
+
+	w := 0;
+	if a.dp <= 0 {
+		buf[w] = '0'; w++;
+		buf[w] = '.'; w++;
+		w += digit_zero(buf[w: w-a.dp]);
+		w += copy(buf[w:], a.d[0:a.ndu]);
+	} else if a.dp < a.ndu {
+		w += copy(buf[w:], a.d[0:a.dp]);
+		buf[w] = '.'; w++;
+		w += copy(buf[w:], a.d[a.dp:a.ndu]);
+	} else {
+		w += copy(buf[w:], a.d[0:a.ndu]);
+		w += digit_zero(buf[w : w+a.dp-a.ndu]);
+	}
+
+	return cast(string)buf[0:w];
+}
+
+// trim trailing zeros
+trim :: proc(a: ^Decimal) {
+	for a.ndu > 0 && a.d[a.ndu-1] == '0' {
+		a.ndu--;
+	}
+	if a.ndu == 0 {
+		a.dp = 0;
+	}
+}
+
+
+assign :: proc(a: ^Decimal, i: u64) {
+	buf: [32]byte;
+	n := 0;
+	for i > 0 {
+		j := i/10;
+		i -= 10*j;
+		buf[n] = cast(byte)('0'+i);
+		n++;
+		i = j;
+	}
+
+	a.ndu = 0;
+	for n--; n >= 0; n-- {
+		a.d[a.ndu] = buf[n];
+		a.ndu++;
+	}
+	a.dp = a.ndu;
+	trim(a);
+}
+
+uint_size :: 8*size_of(uint);
+max_shift :: uint_size-4;
+
+shift_right :: proc(a: ^Decimal, k: uint) {
+	r := 0; // read index
+	w := 0; // write index
+
+	n: uint;
+	for ; n>>k == 0; r++ {
+		if r >= a.ndu {
+			if n == 0 {
+				// Just in case
+				a.ndu = 0;
+				return;
+			}
+			for n>>k == 0 {
+				n = n * 10;
+				r++;
+			}
+			break;
+		}
+		c := cast(uint)a.d[r];
+		n = n*10 + c - '0';
+	}
+	a.dp -= r-1;
+
+	mask: uint = (1<<k) - 1;
+
+	for ; r < a.ndu; r++ {
+		c := cast(uint)a.d[r];
+		dig := n>>k;
+		n &= mask;
+		a.d[w] = cast(byte)('0' + dig);
+		w++;
+		n = n*10 + c - '0';
+	}
+
+	for n > 0 {
+		dig := n>>k;
+		n &= mask;
+		if w < a.d.count {
+			a.d[w] = cast(byte)('0' + dig);
+			w++;
+		} else if dig > 0 {
+			a.trunc = true;
+		}
+		n *= 10;
+	}
+
+
+	a.ndu = w;
+	trim(a);
+}
+
+shift_left :: proc(a: ^Decimal, k: uint) {
+	delta := cast(int)(k/4);
+
+	r := a.ndu;       // read index
+	w := a.ndu+delta; // write index
+
+	n: uint;
+	for r--; r >= 0; r-- {
+		n += (cast(uint)a.d[r] - '0') << k;
+		quo := n/10;
+		rem := n - 10*quo;
+		w--;
+		if w < a.d.count {
+			a.d[w] = cast(byte)('0' + rem);
+		} else if rem != 0 {
+			a.trunc = true;
+		}
+		n = quo;
+	}
+
+	for n > 0 {
+		quo := n/10;
+		rem := n - 10*quo;
+		w--;
+		if w < a.d.count {
+			a.d[w] = cast(byte)('0' + rem);
+		} else if rem != 0 {
+			a.trunc = true;
+		}
+		n = quo;
+	}
+
+	a.ndu += delta;
+	a.ndu = min(a.ndu, a.d.count);
+	a.dp += delta;
+	trim(a);
+}
+
+shift :: proc(a: ^Decimal, k: int) {
+	match {
+	case a.ndu == 0:
+		// no need to update
+	case k > 0:
+		for k > max_shift {
+			shift_left(a, max_shift);
+			k -= max_shift;
+		}
+		shift_left(a, cast(uint)k);
+
+
+	case k < 0:
+		for k < -max_shift {
+			shift_right(a, max_shift);
+			k += max_shift;
+		}
+		shift_right(a, cast(uint)-k);
+	}
+}
+
+can_round_up :: proc(a: ^Decimal, nd: int) -> bool {
+	if nd < 0 || nd >= a.ndu { return false ; }
+	if a.d[nd] == '5' && nd+1 == a.ndu {
+		if a.trunc {
+			return true;
+		}
+		return nd > 0 && (a.d[nd-1]-'0')%2 != 0;
+	}
+
+	return a.d[nd] >= '5';
+}
+
+round :: proc(a: ^Decimal, nd: int) {
+	if nd < 0 || nd >= a.ndu { return; }
+	if can_round_up(a, nd) {
+		round_up(a, nd);
+	} else {
+		round_down(a, nd);
+	}
+}
+
+round_up :: proc(a: ^Decimal, nd: int) {
+	if nd < 0 || nd >= a.ndu { return; }
+
+	for i := nd-1; i >= 0; i-- {
+		if c := a.d[i]; c < '9' {
+			a.d[i]++;
+			a.ndu = i+1;
+			return;
+		}
+	}
+
+	// Number is just 9s
+	a.d[0] = '1';
+	a.ndu = 1;
+	a.dp++;
+}
+
+round_down :: proc(a: ^Decimal, nd: int) {
+	if nd < 0 || nd >= a.ndu { return; }
+	a.ndu = nd;
+	trim(a);
+}
+
+
+// Extract integer part, rounded appropriately. There are no guarantees about overflow.
+rounded_integer :: proc(a: ^Decimal) -> u64 {
+	if a.dp > 20 {
+		return 0xffff_ffff_ffff_ffff;
+	}
+	i: int;
+	n: u64 = 0;
+	m := min(a.dp, a.ndu);
+	for i = 0; i < m; i++ {
+		n = n*10 + cast(u64)(a.d[i]-'0');
+	}
+	for ; i < a.dp; i++ {
+		n *= 10;
+	}
+	if can_round_up(a, a.dp) {
+		n++;
+	}
+	return n;
+}

+ 86 - 124
core/fmt.odin

@@ -2,6 +2,7 @@
 #import "mem.odin";
 #import "utf8.odin";
 #import "types.odin";
+#import "strconv.odin";
 
 
 _BUFFER_SIZE :: 1<<12;
@@ -26,7 +27,7 @@ buffer_write_string :: proc(buf: ^Buffer, s: string) {
 buffer_write_byte :: proc(buf: ^Buffer, b: byte) {
 	if buf.length < buf.data.count {
 		buf.data[buf.length] = b;
-		buf.length += 1;
+		buf.length++;
 	}
 }
 buffer_write_rune :: proc(buf: ^Buffer, r: rune) {
@@ -226,7 +227,7 @@ buffer_write_type :: proc(buf: ^Buffer, ti: ^Type_Info) {
 			buffer_write_string(buf, name);
 			buffer_write_string(buf, ": ");
 			buffer_write_type(buf, cf.types[i]);
-			total_count += 1;
+			total_count++;
 		}
 		for name, i in info.variant_names {
 			if total_count > 0 || i > 0 {
@@ -345,7 +346,7 @@ parse_int :: proc(s: string, offset: int) -> (result: int, offset: int, ok: bool
 		if !is_digit(c) {
 			break;
 		}
-		i += 1;
+		i++;
 
 		result *= 10;
 		result += cast(int)(c - '0');
@@ -459,17 +460,16 @@ fmt_write_padding :: proc(fi: ^Fmt_Info, width: int) {
 	fi.buf.length += count;
 }
 
-fmt_integer :: proc(fi: ^Fmt_Info, u: u64, base: int, signed: bool, digits: string) {
-	negative := signed && cast(i64)u < 0;
-	if signed {
-		u = cast(u64)abs(cast(i64)u);
+_write_int :: proc(fi: ^Fmt_Info, u: u64, base: int, neg: bool, digits: string) {
+	if neg {
+		u = -u;
 	}
-	buf: [256]byte;
+	BUF_SIZE :: 256;
 	if fi.width_set || fi.prec_set {
 		width := fi.width + fi.prec + 3;
-		if width > buf.count {
+		if width > BUF_SIZE {
 			// TODO(bill):????
-			panic("fmt_integer buffer overrun. Width and precision too big");
+			panic("_write_int buffer overrun. Width and precision too big");
 		}
 	}
 
@@ -485,95 +485,64 @@ fmt_integer :: proc(fi: ^Fmt_Info, u: u64, base: int, signed: bool, digits: stri
 		}
 	} else if fi.zero && fi.width_set {
 		prec = fi.width;
-		if negative || fi.plus || fi.space {
+		if neg || fi.plus || fi.space {
 			// There needs to be space for the "sign"
-			prec -= 1;
+			prec--;
 		}
 	}
 
-	i := buf.count;
-
 	match base {
 	case 2, 8, 10, 16:
 		break;
 	default:
-		panic("fmt_integer: unknown base, whoops");
-	}
-
-	for b := cast(u64)base; u >= b;  {
-		i -= 1;
-		next := u / b;
-		buf[i] = digits[u%b];
-		u = next;
-	}
-	i -= 1;
-	buf[i] = digits[u];
-	for i > 0 && prec > buf.count-i {
-		i -= 1;
-		buf[i] = '0';
-	}
-
-	if fi.hash {
-		i -= 1;
-		match base {
-		case 2:  buf[i] = 'b';
-		case 8:  buf[i] = 'o';
-		case 10: buf[i] = 'd';
-		case 16: buf[i] = digits[16];
-		}
-		i -= 1;
-		buf[i] = '0';
+		panic("_write_int: unknown base, whoops");
 	}
 
-	if negative {
-		i -= 1;
-		buf[i] = '-';
-	} else if fi.plus {
-		i -= 1;
-		buf[i] = '+';
-	} else if fi.space {
-		i -= 1;
-		buf[i] = ' ';
-	}
+	buf: [256]byte;
+	flags: strconv.Int_Flag;
+	if fi.hash  { flags |= strconv.Int_Flag.PREFIX; }
+	if fi.plus  { flags |= strconv.Int_Flag.PLUS; }
+	if fi.space { flags |= strconv.Int_Flag.SPACE; }
+	s := strconv.format_bits(buf[:], u, base, neg, digits, flags);
 
 	old_zero := fi.zero;
 	defer fi.zero = old_zero;
 	fi.zero = false;
 
 	if !fi.width_set || fi.width == 0 {
-		buffer_write(fi.buf, buf[i:]);
+		buffer_write_string(fi.buf, s);
 	} else {
-		width := fi.width - utf8.rune_count(cast(string)buf[i:]);
+		width := fi.width - utf8.rune_count(s);
 			if width > 0 {
 			if fi.minus {
 				// Right pad
-				buffer_write(fi.buf, buf[i:]);
+				buffer_write_string(fi.buf, s);
 				fmt_write_padding(fi, width);
 			} else {
 				// Left pad
 				fmt_write_padding(fi, width);
-				buffer_write(fi.buf, buf[i:]);
+				buffer_write_string(fi.buf, s);
 			}
 		}
 	}
 
 }
 
-__DIGITS_LOWER := "0123456789abcdefx";
-__DIGITS_UPPER := "0123456789ABCDEFX";
+immutable __DIGITS_LOWER := "0123456789abcdefx";
+immutable __DIGITS_UPPER := "0123456789ABCDEFX";
 
 fmt_rune :: proc(fi: ^Fmt_Info, r: rune) {
 	buffer_write_rune(fi.buf, r);
 }
 
-fmt_int :: proc(fi: ^Fmt_Info, u: u64, signed: bool, verb: rune) {
+fmt_int :: proc(fi: ^Fmt_Info, u: u64, neg: bool, verb: rune) {
 	match verb {
-	case 'v': fmt_integer(fi, u, 10, signed, __DIGITS_LOWER);
-	case 'b': fmt_integer(fi, u,  2, signed, __DIGITS_LOWER);
-	case 'o': fmt_integer(fi, u,  8, signed, __DIGITS_LOWER);
-	case 'd': fmt_integer(fi, u, 10, signed, __DIGITS_LOWER);
-	case 'x': fmt_integer(fi, u, 16, signed, __DIGITS_LOWER);
-	case 'X': fmt_integer(fi, u, 16, signed, __DIGITS_UPPER);
+	case 'v': _write_int(fi, u, 10, neg, __DIGITS_LOWER);
+	case 'b': _write_int(fi, u,  2, neg, __DIGITS_LOWER);
+	case 'o': _write_int(fi, u,  8, neg, __DIGITS_LOWER);
+	case 'd': _write_int(fi, u, 10, neg, __DIGITS_LOWER);
+	case 'x': _write_int(fi, u, 16, neg, __DIGITS_LOWER);
+	case 'X': _write_int(fi, u, 16, neg, __DIGITS_UPPER);
 	case 'c', 'r':
 		fmt_rune(fi, cast(rune)u);
 	case 'U':
@@ -582,7 +551,7 @@ fmt_int :: proc(fi: ^Fmt_Info, u: u64, signed: bool, verb: rune) {
 			fmt_bad_verb(fi, verb);
 		} else {
 			buffer_write_string(fi.buf, "U+");
-			fmt_integer(fi, u, 16, false, __DIGITS_UPPER);
+			_write_int(fi, u, 16, false, __DIGITS_UPPER);
 		}
 
 	default:
@@ -592,67 +561,60 @@ fmt_int :: proc(fi: ^Fmt_Info, u: u64, signed: bool, verb: rune) {
 
 
 fmt_float :: proc(fi: ^Fmt_Info, v: f64, bit_size: int, verb: rune) {
+	pad :: proc(fi: ^Fmt_Info, s: string) {
+		if !fi.width_set || fi.width == 0 {
+			buffer_write_string(fi.buf, s);
+			return;
+		}
+		width := fi.width - utf8.rune_count(s);
+		if fi.minus { // right pad
+			buffer_write_string(fi.buf, s);
+			fmt_write_padding(fi, width);
+		} else { // left pad
+			fmt_write_padding(fi, width);
+			buffer_write_string(fi.buf, s);
+		}
+	}
+
 	match verb {
 	// case 'e', 'E', 'f', 'F', 'g', 'G', 'v':
 	// case 'f', 'F', 'v':
 
-	// TODO(bill): This is a shit copy from gb.h and I really need a decent implementation
 	case 'f', 'F', 'v':
-		width := 0;
-		if fi.width_set {
-			width = max(fi.width, 0);
-		}
-		prec := 3;
+		prec: int = 3;
 		if fi.prec_set {
-			prec = max(fi.prec, 0);
+			prec = fi.prec;
 		}
-
-		if v == 0 {
-			buffer_write_byte(fi.buf, '0');
-			if fi.hash && width > 0 {
-				buffer_write_byte(fi.buf, '.');
-			}
+		buf: [128]byte;
+		str := strconv.format_float(buf[1:], v, 'f', prec, bit_size);
+		str = cast(string)buf[:str.count+1];
+		if str[1] == '+' || str[1] == '-' {
+			str = str[1:];
 		} else {
-			signed := v < 0;
-			v = abs(v);
-
-			if signed {
-				buffer_write_byte(fi.buf, '-');
-			}
-
-			val := cast(u64)v;
-			fi.minus = false;
-			fi.width = 0;
-			fi.prec = 0;
-			// TODO(bill): Write integer to buffer than use this crap
-			fmt_integer(fi, val, 10, false, __DIGITS_LOWER);
-
-			if fi.hash || prec > 0 {
-				arg := v - cast(f64)val;
-				mult: f64 = 10;
-				buffer_write_byte(fi.buf, '.');
-				for _ in 0..<prec {
-					val := cast(u64)(arg*mult);
-					buffer_write_byte(fi.buf, __DIGITS_LOWER[cast(u64)val]);
-					arg -= cast(f64)val / mult;
-					mult *= 10;
-				}
-			}
+			str[0] = '+';
 		}
 
+		if fi.space && !fi.plus && str[0] == '+' {
+			str[0] = ' ';
+		}
 
-		if width > 0 {
-			fill: byte = ' ';
-			match {
-			case fi.zero:  fill = '0';
-			case fi.space: fill = ' ';
-			}
+		if str[1] == 'N' && str[1] == 'I' {
+			buffer_write_string(fi.buf, str);
+			return;
+		}
 
-			for width > 0 {
-				width -= 1;
-				buffer_write_byte(fi.buf, fill);
+		if fi.plus || str[0] != '+' {
+			if fi.zero && fi.width_set && fi.width > str.count {
+				buffer_write_byte(fi.buf, str[0]);
+				fmt_write_padding(fi, fi.width - str.count);
+				buffer_write_string(fi.buf, str[1:]);
+			} else {
+				pad(fi, str);
 			}
+		} else {
+			pad(fi, str[1:]);
 		}
+
 	default:
 		fmt_bad_verb(fi, verb);
 		return;
@@ -679,7 +641,7 @@ fmt_pointer :: proc(fi: ^Fmt_Info, p: rawptr, verb: rune) {
 	if !fi.hash || verb == 'v' {
 		buffer_write_string(fi.buf, "0x");
 	}
-	fmt_integer(fi, u, 16, false, __DIGITS_UPPER);
+	_write_int(fi, u, 16, false, __DIGITS_UPPER);
 }
 
 fmt_enum :: proc(fi: ^Fmt_Info, v: any, verb: rune) {
@@ -957,11 +919,11 @@ fmt_arg :: proc(fi: ^Fmt_Info, arg: any, verb: rune) {
 	case f32:     fmt_float(fi, cast(f64)a, 32, verb);
 	case f64:     fmt_float(fi, a, 64, verb);
 
-	case int:     fmt_int(fi, cast(u64)a, true, verb);
-	case i8:      fmt_int(fi, cast(u64)a, true, verb);
-	case i16:     fmt_int(fi, cast(u64)a, true, verb);
-	case i32:     fmt_int(fi, cast(u64)a, true, verb);
-	case i64:     fmt_int(fi, cast(u64)a, true, verb);
+	case int:     fmt_int(fi, cast(u64)a, a < 0, verb);
+	case i8:      fmt_int(fi, cast(u64)a, a < 0, verb);
+	case i16:     fmt_int(fi, cast(u64)a, a < 0, verb);
+	case i32:     fmt_int(fi, cast(u64)a, a < 0, verb);
+	case i64:     fmt_int(fi, cast(u64)a, a < 0, verb);
 	case uint:    fmt_int(fi, cast(u64)a, false, verb);
 	case u8:      fmt_int(fi, cast(u64)a, false, verb);
 	case u16:     fmt_int(fi, cast(u64)a, false, verb);
@@ -984,7 +946,7 @@ bprintf :: proc(b: ^Buffer, fmt: string, args: ...any) -> int {
 
 		prev_i := i;
 		for i < end && fmt[i] != '%' {
-			i += 1;
+			i++;
 		}
 		if i > prev_i {
 			buffer_write_string(b, fmt[prev_i:i]);
@@ -994,10 +956,10 @@ bprintf :: proc(b: ^Buffer, fmt: string, args: ...any) -> int {
 		}
 
 		// Process a "verb"
-		i += 1;
+		i++;
 
 
-		for ; i < end; i += 1 {
+		for ; i < end; i++ {
 			skip_loop := false;
 			c := fmt[i];
 			match c {
@@ -1025,7 +987,7 @@ bprintf :: proc(b: ^Buffer, fmt: string, args: ...any) -> int {
 
 		// Width
 		if i < end && fmt[i] == '*' {
-			i += 1;
+			i++;
 			fi.width, arg_index, fi.width_set = int_from_arg(args, arg_index);
 			if !fi.width_set {
 				buffer_write_string(b, "%!(BAD WIDTH)");
@@ -1046,13 +1008,13 @@ bprintf :: proc(b: ^Buffer, fmt: string, args: ...any) -> int {
 
 		// Precision
 		if i < end && fmt[i] == '.' {
-			i += 1;
+			i++;
 			if was_prev_index { // %[6].2d
 				fi.good_arg_index = false;
 			}
 			if i < end && fmt[i] == '*' {
 				arg_index, i, was_prev_index = _arg_number(^fi, arg_index, fmt, i, args.count);
-				i += 1;
+				i++;
 				fi.prec, arg_index, fi.prec_set = int_from_arg(args, arg_index);
 				if fi.prec < 0 {
 					fi.prec = 0;
@@ -1091,7 +1053,7 @@ bprintf :: proc(b: ^Buffer, fmt: string, args: ...any) -> int {
 			buffer_write_string(b, "%!(MISSING ARGUMENT)");
 		} else {
 			fmt_arg(^fi, args[arg_index], verb);
-			arg_index += 1;
+			arg_index++;
 		}
 	}
 

+ 3 - 3
core/hash.odin

@@ -146,7 +146,7 @@ murmur64 :: proc(data: []byte) -> u64 {
 		i := 0;
 		for len >= 8 {
 			k1, k2: u32;
-			k1 = data32[i]; i += 1;
+			k1 = data32[i]; i++;
 			k1 *= m;
 			k1 ~= k1>>r;
 			k1 *= m;
@@ -154,7 +154,7 @@ murmur64 :: proc(data: []byte) -> u64 {
 			h1 ~= k1;
 			len -= 4;
 
-			k2 = data32[i]; i += 1;
+			k2 = data32[i]; i++;
 			k2 *= m;
 			k2 ~= k2>>r;
 			k2 *= m;
@@ -165,7 +165,7 @@ murmur64 :: proc(data: []byte) -> u64 {
 
 		if len >= 4 {
 			k1: u32;
-			k1 = data32[i]; i += 1;
+			k1 = data32[i]; i++;
 			k1 *= m;
 			k1 ~= k1>>r;
 			k1 *= m;

+ 3 - 3
core/mem.odin

@@ -79,7 +79,7 @@ allocation_header_fill :: proc(header: ^Allocation_Header, data: rawptr, size: i
 	header.size = size;
 	ptr := cast(^int)(header+1);
 
-	for i := 0; cast(rawptr)ptr < data; i += 1 {
+	for i := 0; cast(rawptr)ptr < data; i++ {
 		(ptr+i)^ = -1;
 	}
 }
@@ -183,7 +183,7 @@ begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory {
 	tmp: Arena_Temp_Memory;
 	tmp.arena = a;
 	tmp.original_count = a.memory.count;
-	a.temp_count += 1;
+	a.temp_count++;
 	return tmp;
 }
 
@@ -191,7 +191,7 @@ end_arena_temp_memory :: proc(using tmp: Arena_Temp_Memory) {
 	assert(arena.memory.count >= original_count);
 	assert(arena.temp_count > 0);
 	arena.memory.count = original_count;
-	arena.temp_count -= 1;
+	arena.temp_count--;
 }
 
 

+ 337 - 0
core/strconv.odin

@@ -0,0 +1,337 @@
+#import . "decimal.odin";
+#import "math.odin";
+#import format "fmt.odin";
+
+Int_Flag :: enum {
+	PREFIX = 1<<0,
+	PLUS   = 1<<1,
+	SPACE  = 1<<2,
+}
+
+
+parse_bool :: proc(s: string) -> (result: bool, ok: bool) {
+	match s {
+	case "1", "t", "T", "true", "TRUE", "True":
+		return true, true;
+	case "0", "f", "F", "false", "FALSE", "False":
+		return false, true;
+	}
+	return false, false;
+}
+
+format_bool :: proc(buf: []byte, b: bool) -> string {
+	s := b ? "true" : "false";
+	len := copy(buf, cast([]byte)s);
+	return cast(string)buf[:len];
+}
+
+format_uint :: proc(buf: []byte, u: u64, base: int) -> string {
+	using Int_Flag;
+	return format_bits(buf, u, base, false, digits, 0);
+}
+format_int :: proc(buf: []byte, i: i64, base: int) -> string {
+	return format_bits(buf, cast(u64)i, base, i < 0, digits, 0);
+}
+itoa :: proc(buf: []byte, i: int) -> string {
+	return format_int(buf, cast(i64)i, 10);
+}
+
+format_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
+	return cast(string)generic_ftoa(buf, f, fmt, prec, bit_size);
+}
+
+
+
+
+Decimal_Slice :: struct {
+	d:   []byte,
+	ndu: int,
+	dp:  int,
+	neg: bool,
+}
+
+Float_Info :: struct {
+	mantbits: uint,
+	expbits:  uint,
+	bias:     int,
+}
+
+f32_info := Float_Info{23, 8, -127};
+f64_info := Float_Info{52, 11, -1023};
+
+
+generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, prec, bit_size: int) -> []byte {
+	bits: u64;
+	flt: ^Float_Info;
+	match bit_size {
+	case 32:
+		bits = cast(u64)transmute(u32)cast(f32)val;
+		flt = ^f32_info;
+	case 64:
+		bits = transmute(u64)val;
+		flt = ^f64_info;
+	default:
+		panic("strconv: invalid bit_size");
+	}
+
+	neg := bits>>(flt.expbits+flt.mantbits) != 0;
+	exp := cast(int)(bits>>flt.mantbits) & (1<<flt.expbits - 1);
+	mant := bits & (cast(u64)1 << flt.mantbits - 1);
+
+	match exp {
+	case 1<<flt.expbits - 1:
+		s: string;
+		if mant != 0 {
+			s = "NaN";
+		} else if neg {
+			s = "-Inf";
+		} else {
+			s = "+Inf";
+		}
+		len := copy(buf, cast([]byte)s);
+		return buf[:len];
+
+	case 0: // denormalized
+		exp++;
+
+	default:
+		mant |= cast(u64)1 << flt.mantbits;
+	}
+
+	exp += flt.bias;
+
+	d_: Decimal;
+	d := ^d_;
+	assign(d, mant);
+	shift(d, exp - cast(int)flt.mantbits);
+	digs: Decimal_Slice;
+	shortest := prec < 0;
+	if shortest {
+		round_shortest(d, mant, exp, flt);
+		digs = Decimal_Slice{d = d.d[:], ndu = d.ndu, dp = d.dp};
+		match fmt {
+		case 'e', 'E':
+			prec = digs.ndu-1;
+		case 'f', 'F':
+			prec = max(digs.ndu-digs.dp, 0);
+		case 'g', 'G':
+			prec = digs.ndu;
+		}
+	} else {
+		match fmt {
+		case 'e', 'E': round(d, prec+1);
+		case 'f', 'F': round(d, d.dp+prec);
+		case 'g', 'G':
+			if prec == 0 {
+				prec = 1;
+			}
+			round(d, prec);
+		}
+
+		digs = Decimal_Slice{d = d.d[:], ndu = d.ndu, dp = d.dp};
+	}
+	return format_digits(buf, shortest, neg, digs, prec, fmt);
+}
+
+
+
+format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slice, prec: int, fmt: byte) -> []byte {
+	match fmt {
+	case 'f', 'F':
+		add_bytes :: proc(dst: ^[]byte, w: ^int, bytes: ...byte) {
+			for b in bytes {
+				if dst.count <= w^ {
+					break;
+				}
+				dst[w^] = b;
+				w^++;
+			}
+		}
+
+		dst := buf[:];
+		w := 0;
+		if neg {
+			add_bytes(^dst, ^w, '-');
+		} else {
+			add_bytes(^dst, ^w, '+');
+		}
+
+		// integer, padded with zeros when needed
+		if digs.dp > 0 {
+			m := min(digs.ndu, digs.dp);
+			add_bytes(^dst, ^w, ...digs.d[:m]);
+			for ; m < digs.dp; m++ {
+				add_bytes(^dst, ^w, '0');
+			}
+		} else {
+			add_bytes(^dst, ^w, '0');
+		}
+
+		// fractional part
+		if prec > 0 {
+			add_bytes(^dst, ^w, '.');
+			for i in 0..<prec {
+				c: byte = '0';
+				if j := digs.dp + i; 0 <= j && j < digs.ndu {
+					c = digs.d[j];
+				}
+				add_bytes(^dst, ^w, c);
+			}
+		}
+
+		return buf[:w];
+
+	case 'e', 'E':
+		return nil; // TODO
+
+	case 'g', 'G':
+		return nil; // TODO
+	}
+
+	c: [2]byte;
+	c[0] = '%';
+	c[1] = fmt;
+	len := copy(buf, ...c[:]);
+	return buf[:len];
+}
+
+round_shortest :: proc(d: ^Decimal, mant: u64, exp: int, flt: ^Float_Info) {
+	if mant == 0 { // If mantissa is zero, the number is zero
+		d.ndu = 0;
+		return;
+	}
+
+	/*
+		10^(dp-nd) > 2^(exp-mantbits)
+		log2(10) * (dp-nd) > exp-mantbits
+		log(2) >~ 0.332
+		332*(dp-nd) >= 100*(exp-mantbits)
+	 */
+	minexp := flt.bias+1;
+	if exp > minexp && 332*(d.dp-d.ndu) >= 100*(exp - cast(int)flt.mantbits) {
+		// Number is already its shortest
+		return;
+	}
+
+	upper_: Decimal; upper: = ^upper_;
+	assign(upper, 2*mant - 1);
+	shift(upper, exp - cast(int)flt.mantbits - 1);
+
+	mantlo: u64;
+	explo:  int;
+	if mant > 1<<flt.mantbits || exp == minexp {
+		mantlo = mant-1;
+		explo = exp;
+	} else {
+		mantlo = 2*mant - 1;
+		explo = exp-1;
+	}
+	lower_: Decimal; lower: = ^lower_;
+	assign(lower, 2*mantlo + 1);
+	shift(lower, explo - cast(int)flt.mantbits - 1);
+
+	inclusive := mant%2 == 0;
+
+	for i in 0..<d.ndu {
+		l: byte = '0'; // lower digit
+		if i < lower.ndu {
+			l = lower.d[i];
+		}
+		m := d.d[i];   // middle digit
+		u: byte = '0'; // upper digit
+		if i < upper.ndu {
+			u = upper.d[i];
+		}
+
+		ok_round_down := l != m || inclusive && i+1 == lower.ndu;
+		ok_round_up   := m != u && (inclusive || m+1 < u || i+1 < upper.ndu);
+
+		if (ok_round_down && ok_round_up) {
+			round(d, i+1);
+			return;
+		}
+		if (ok_round_down) {
+			round_down(d, i+1);
+			return;
+		}
+		if (ok_round_up) {
+			round_up(d, i+1);
+			return;
+		}
+	}
+
+}
+
+MAX_BASE :: 32;
+immutable digits := "0123456789abcdefghijklmnopqrstuvwxyz";
+
+
+format_bits :: proc(buf: []byte, u: u64, base: int, neg: bool, digits: string, flags: Int_Flag) -> string {
+	is_pow2 :: proc(x: i64) -> bool {
+		if (x <= 0) {
+			return false;
+		}
+		return x&(x-1) == 0;
+	}
+
+	if base < 2 || base > MAX_BASE {
+		panic("strconv: illegal base passed to format_bits");
+	}
+
+	a: [65]byte;
+	i := a.count;
+	if neg {
+		u = -u;
+	}
+
+	if is_pow2(cast(i64)base) {
+		b := cast(u64)base;
+		m := cast(uint)b - 1;
+		for u >= b {
+			i--;
+			a[i] = digits[cast(uint)u & m];
+			u >>= b;
+		}
+		i--;
+		a[i] = digits[cast(uint)u];
+	} else {
+		b := cast(u64)base;
+		for u >= b {
+			i--;
+			q := u / b;
+			a[i] = digits[cast(uint)(u-q*b)];
+			u = q;
+		}
+
+		i--;
+		a[i] = digits[cast(uint)u];
+	}
+
+	if flags&Int_Flag.PREFIX != 0 {
+		ok := true;
+		match base {
+		case 2:  i--; a[i] = 'b';
+		case 8:  i--; a[i] = 'o';
+		case 10: i--; a[i] = 'd';
+		case 16: i--; a[i] = 'x';
+		default: ok = false;
+		}
+		if ok {
+			i--;
+			a[i] = '0';
+		}
+	}
+
+	if neg {
+		i--; a[i] = '-';
+	} else if flags&Int_Flag.PLUS != 0 {
+		i--; a[i] = '+';
+	} else if flags&Int_Flag.SPACE != 0 {
+		i--; a[i] = ' ';
+	}
+
+
+	len := copy(buf, ...a[i:]);
+	return cast(string)buf[:len];
+}
+

+ 1 - 1
core/strings.odin

@@ -9,7 +9,7 @@ to_odin_string :: proc(c: ^byte) -> string {
 	s: string;
 	s.data = c;
 	for (c+s.count)^ != 0 {
-		s.count += 1;
+		s.count++;
 	}
 	return s;
 }

+ 3 - 3
core/sync.odin

@@ -52,7 +52,7 @@ mutex_lock :: proc(m: ^Mutex) {
 		}
 	}
 	atomic.store(^m.owner, thread_id);
-	m.recursion += 1;
+	m.recursion++;
 }
 mutex_try_lock :: proc(m: ^Mutex) -> bool {
 	thread_id := current_thread_id();
@@ -68,7 +68,7 @@ mutex_try_lock :: proc(m: ^Mutex) -> bool {
 		}
 		atomic.store(^m.owner, thread_id);
 	}
-	m.recursion += 1;
+	m.recursion++;
 	return true;
 }
 mutex_unlock :: proc(m: ^Mutex) {
@@ -76,7 +76,7 @@ mutex_unlock :: proc(m: ^Mutex) {
 	thread_id := current_thread_id();
 	assert(thread_id == atomic.load(^m.owner));
 
-	m.recursion -= 1;
+	m.recursion--;
 	recursion = m.recursion;
 	if recursion == 0 {
 		atomic.store(^m.owner, thread_id);

+ 51 - 7
core/utf8.odin

@@ -92,7 +92,8 @@ encode_rune :: proc(r: rune) -> ([4]byte, int) {
 	return buf, 4;
 }
 
-decode_rune :: proc(s: string) -> (rune, int) {
+decode_rune :: proc(s: string) -> (rune, int) #inline { return decode_rune(cast([]byte)s); }
+decode_rune :: proc(s: []byte) -> (rune, int) {
 	n := s.count;
 	if n < 1 {
 		return RUNE_ERROR, 0;
@@ -130,6 +131,46 @@ decode_rune :: proc(s: string) -> (rune, int) {
 }
 
 
+
+decode_last_rune :: proc(s: string) -> (rune, int) #inline { return decode_last_rune(cast([]byte)s); }
+decode_last_rune :: proc(s: []byte) -> (rune, int) {
+	r: rune;
+	size: int;
+	start, end, limit: int;
+
+	end = s.count;
+	if end == 0 {
+		return RUNE_ERROR, 0;
+	}
+	start = end-1;
+	r = cast(rune)s[start];
+	if r < RUNE_SELF {
+		return r, 1;
+	}
+
+
+	limit = max(end - UTF_MAX, 0);
+
+	start--;
+	for start >= limit {
+		if rune_start(s[start]) {
+			break;
+		}
+		start--;
+	}
+
+	start = max(start, 0);
+	r, size = decode_rune(s[start:end]);
+	if start+size != end {
+		return RUNE_ERROR, 1;
+	}
+	return r, size;
+}
+
+
+
+
+
 valid_rune :: proc(r: rune) -> bool {
 	if r < 0 {
 		return false;
@@ -146,7 +187,7 @@ valid_string :: proc(s: string) -> bool {
 	for i := 0; i < n; {
 		si := s[i];
 		if si < RUNE_SELF { // ascii
-			i += 1;
+			i++;
 			continue;
 		}
 		x := accept_sizes[si];
@@ -174,25 +215,28 @@ valid_string :: proc(s: string) -> bool {
 	return true;
 }
 
-rune_count :: proc(s: string) -> int {
+rune_start :: proc(b: byte) -> bool #inline { return b&0xc0 != 0x80; }
+
+rune_count :: proc(s: string) -> int #inline { return rune_count(cast([]byte)s); }
+rune_count :: proc(s: []byte) -> int {
 	count := 0;
 	n := s.count;
 
 	for i := 0; i < n; {
-		defer count += 1;
+		defer count++;
 		si := s[i];
 		if si < RUNE_SELF { // ascii
-			i += 1;
+			i++;
 			continue;
 		}
 		x := accept_sizes[si];
 		if x == 0xf1 {
-			i += 1;
+			i++;
 			continue;
 		}
 		size := cast(int)(x & 7);
 		if i+size > n {
-			i += 1;
+			i++;
 			continue;
 		}
 		ar := accept_ranges[x>>4];

+ 19 - 8
src/check_expr.c

@@ -197,11 +197,11 @@ i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) {
 		}
 	}
 
-	// if (is_type_proc(dst)) {
-	// 	if (are_types_identical(src, dst)) {
-	// 		return 1;
-	// 	}
-	// }
+	if (is_type_proc(dst)) {
+		if (are_types_identical(src, dst)) {
+			return 3;
+		}
+	}
 
 	if (is_type_any(dst)) {
 		// NOTE(bill): Anything can cast to `Any`
@@ -2827,6 +2827,16 @@ Entity *check_selector(Checker *c, Operand *operand, AstNode *node, Type *type_h
 			goto error;
 		}
 	}
+
+	if (entity == NULL &&
+	    operand->type != NULL && is_type_untyped(operand->type) && is_type_string(operand->type)) {
+		String s = operand->value.value_string;
+		operand->mode = Addressing_Constant;
+		operand->value = make_exact_value_integer(s.len);
+		operand->type = t_untyped_integer;
+		return NULL;
+	}
+
 	if (entity == NULL) {
 		gbString op_str   = expr_to_string(op_expr);
 		gbString type_str = type_to_string(operand->type);
@@ -5445,9 +5455,10 @@ gbString write_expr_to_string(gbString str, AstNode *node) {
 
 	case_ast_node(at, ArrayType, node);
 		str = gb_string_appendc(str, "[");
-		if (at->count->kind == AstNode_UnaryExpr &&
-		    at->count->UnaryExpr.op.kind == Token_Hash) {
-			str = gb_string_appendc(str, "#");
+		if (at->count != NULL &&
+		    at->count->kind == AstNode_UnaryExpr &&
+		    at->count->UnaryExpr.op.kind == Token_Ellipsis) {
+			str = gb_string_appendc(str, "...");
 		} else {
 			str = write_expr_to_string(str, at->count);
 		}

+ 46 - 2
src/check_stmt.c

@@ -421,6 +421,49 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 		check_stmt(c, ts->stmt, flags);
 	case_end;
 
+	case_ast_node(s, IncDecStmt, node);
+		TokenKind op = s->op.kind;
+		switch (op) {
+		case Token_Inc: op = Token_Add; break;
+		case Token_Dec: op = Token_Sub; break;
+		default:
+			error_node(node, "Invalid inc/dec operation");
+			return;
+		}
+
+		Operand x = {0};
+		check_expr(c, &x, s->expr);
+		if (x.mode == Addressing_Invalid) {
+			return;
+		}
+		if (!is_type_integer(x.type) && !is_type_float(x.type)) {
+			gbString e = expr_to_string(s->expr);
+			gbString t = type_to_string(x.type);
+			error_node(node, "%s%.*s used on non-numeric type %s", e, LIT(s->op.string), t);
+			gb_string_free(t);
+			gb_string_free(e);
+			return;
+		}
+		AstNode *left = s->expr;
+		AstNode *right = gb_alloc_item(c->allocator, AstNode);
+		right->kind = AstNode_BasicLit;
+		right->BasicLit.pos = s->op.pos;
+		right->BasicLit.kind = Token_Integer;
+		right->BasicLit.string = str_lit("1");
+
+		AstNode *be = gb_alloc_item(c->allocator, AstNode);
+		be->kind = AstNode_BinaryExpr;
+		be->BinaryExpr.op = s->op;
+		be->BinaryExpr.op.kind = op;
+		be->BinaryExpr.left = left;
+		be->BinaryExpr.right = right;
+		check_binary_expr(c, &x, be);
+		if (x.mode == Addressing_Invalid) {
+			return;
+		}
+		check_assignment_variable(c, &x, left);
+	case_end;
+
 	case_ast_node(as, AssignStmt, node);
 		switch (as->op.kind) {
 		case Token_Eq: {
@@ -591,8 +634,9 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
 		if (fs->post != NULL) {
 			check_stmt(c, fs->post, 0);
 
-			if (fs->post->kind != AstNode_AssignStmt) {
-				error_node(fs->post, "`for` statement post statement must be an assignment");
+			if (fs->post->kind != AstNode_AssignStmt &&
+			    fs->post->kind != AstNode_IncDecStmt) {
+				error_node(fs->post, "`for` statement post statement must be a simple statement");
 			}
 		}
 		check_stmt(c, fs->body, new_flags);

+ 4 - 3
src/exact_value.c

@@ -274,8 +274,7 @@ ExactValue exact_unary_operator_value(TokenKind op, ExactValue v, i32 precision)
 		case ExactValue_Invalid:
 			return v;
 		case ExactValue_Integer:
-			i = v.value_integer;
-			i = ~i;
+			i = ~v.value_integer;
 			break;
 		default:
 			goto failure;
@@ -283,8 +282,10 @@ ExactValue exact_unary_operator_value(TokenKind op, ExactValue v, i32 precision)
 
 		// NOTE(bill): unsigned integers will be negative and will need to be
 		// limited to the types precision
-		if (precision > 0)
+		// IMPORTANT NOTE(bill): Max precision is 64 bits as that's how integers are stored
+		if (0 < precision && precision < 64) {
 			i &= ~((~0ll)<<precision);
+		}
 
 		return make_exact_value_integer(i);
 	} break;

+ 22 - 1
src/ir.c

@@ -233,6 +233,7 @@ struct irProcedure {
 #define IR_CONV_KINDS \
 	IR_CONV_KIND(trunc) \
 	IR_CONV_KIND(zext) \
+	IR_CONV_KIND(sext) \
 	IR_CONV_KIND(fptrunc) \
 	IR_CONV_KIND(fpext) \
 	IR_CONV_KIND(fptoui) \
@@ -2240,12 +2241,23 @@ irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) {
 		i64 sz = type_size_of(proc->module->allocator, src);
 		i64 dz = type_size_of(proc->module->allocator, dst);
 		irConvKind kind = irConv_trunc;
-		if (sz == dz) {
+		if (dz == sz) {
 			// NOTE(bill): In LLVM, all integers are signed and rely upon 2's compliment
 			// NOTE(bill): Copy the value just for type correctness
 			kind = irConv_bitcast;
 		} else if (dz > sz) {
 			kind = irConv_zext;
+
+			// TODO(bill): figure out the rules completely
+			bool ss = !is_type_unsigned(src);
+			bool ds = !is_type_unsigned(dst);
+			if (ss && ds) {
+				kind = irConv_sext;
+			} else if (ss) {
+				kind = irConv_sext;
+			} else {
+				kind = irConv_zext;
+			}
 		}
 
 		return ir_emit(proc, ir_make_instr_conv(proc, kind, value, src, dst));
@@ -4769,6 +4781,15 @@ void ir_build_stmt_internal(irProcedure *proc, AstNode *node) {
 		ir_build_when_stmt(proc, ws);
 	case_end;
 
+	case_ast_node(s, IncDecStmt, node);
+		TokenKind op = Token_Add;
+		if (s->op.kind == Token_Dec) {
+			op = Token_Sub;
+		}
+		irAddr addr = ir_build_addr(proc, s->expr);
+		ir_build_assign_op(proc, addr, v_one, op);
+	case_end;
+
 	case_ast_node(vd, ValueDecl, node);
 		if (vd->is_var) {
 			irModule *m = proc->module;

+ 22 - 0
src/parser.c

@@ -186,6 +186,10 @@ AST_NODE_KIND(_StmtBegin,     "", i32) \
 		Token op; \
 		AstNodeArray lhs, rhs; \
 	}) \
+	AST_NODE_KIND(IncDecStmt, "increment decrement statement", struct { \
+		Token op; \
+		AstNode *expr; \
+	}) \
 AST_NODE_KIND(_ComplexStmtBegin, "", i32) \
 	AST_NODE_KIND(BlockStmt, "block statement", struct { \
 		AstNodeArray stmts; \
@@ -467,6 +471,7 @@ Token ast_node_token(AstNode *node) {
 	case AstNode_ExprStmt:      return ast_node_token(node->ExprStmt.expr);
 	case AstNode_TagStmt:       return node->TagStmt.token;
 	case AstNode_AssignStmt:    return node->AssignStmt.op;
+	case AstNode_IncDecStmt:    return ast_node_token(node->IncDecStmt.expr);
 	case AstNode_BlockStmt:     return node->BlockStmt.open;
 	case AstNode_IfStmt:        return node->IfStmt.token;
 	case AstNode_WhenStmt:      return node->WhenStmt.token;
@@ -802,6 +807,16 @@ AstNode *ast_assign_stmt(AstFile *f, Token op, AstNodeArray lhs, AstNodeArray rh
 	return result;
 }
 
+
+AstNode *ast_inc_dec_stmt(AstFile *f, Token op, AstNode *expr) {
+	AstNode *result = make_ast_node(f, AstNode_IncDecStmt);
+	result->IncDecStmt.op = op;
+	result->IncDecStmt.expr = expr;
+	return result;
+}
+
+
+
 AstNode *ast_block_stmt(AstFile *f, AstNodeArray stmts, Token open, Token close) {
 	AstNode *result = make_ast_node(f, AstNode_BlockStmt);
 	result->BlockStmt.stmts = stmts;
@@ -2272,6 +2287,13 @@ AstNode *parse_simple_stmt(AstFile *f, bool in_stmt_ok) {
 		return ast_bad_stmt(f, token, f->curr_token);
 	}
 
+	switch (token.kind) {
+	case Token_Inc:
+	case Token_Dec:
+		next_token(f);
+		return ast_inc_dec_stmt(f, token, lhs.e[0]);
+	}
+
 	return ast_expr_stmt(f, lhs.e[0]);
 }
 

+ 9 - 9
src/tokenizer.c

@@ -51,8 +51,8 @@ TOKEN_KIND(Token__AssignOpBegin, "_AssignOpBegin"), \
 TOKEN_KIND(Token__AssignOpEnd,   "_AssignOpEnd"), \
 	TOKEN_KIND(Token_ArrowRight, "->"), \
 	TOKEN_KIND(Token_ArrowLeft,  "<-"), \
-	TOKEN_KIND(Token_Increment,  "++"), \
-	TOKEN_KIND(Token_Decrement,  "--"), \
+	TOKEN_KIND(Token_Inc,        "++"), \
+	TOKEN_KIND(Token_Dec,        "--"), \
 \
 TOKEN_KIND(Token__ComparisonBegin, "_ComparisonBegin"), \
 	TOKEN_KIND(Token_CmpEq, "=="), \
@@ -859,13 +859,13 @@ Token tokenizer_get_token(Tokenizer *t) {
 		case '{': token.kind = Token_OpenBrace;    break;
 		case '}': token.kind = Token_CloseBrace;   break;
 
-		case '*': token.kind = token_kind_variant2(t, Token_Mul, Token_MulEq);                                              break;
-		case '%': token.kind = token_kind_variant2(t, Token_Mod, Token_ModEq);                                              break;
-		case '=': token.kind = token_kind_variant2(t, Token_Eq,  Token_CmpEq);                                              break;
-		case '~': token.kind = token_kind_variant2(t, Token_Xor, Token_XorEq);                                              break;
-		case '!': token.kind = token_kind_variant2(t, Token_Not, Token_NotEq);                                              break;
-		case '+': token.kind = token_kind_variant3(t, Token_Add, Token_AddEq, '+', Token_Increment);                        break;
-		case '-': token.kind = token_kind_variant4(t, Token_Sub, Token_SubEq, '-', Token_Decrement, '>', Token_ArrowRight); break;
+		case '*': token.kind = token_kind_variant2(t, Token_Mul, Token_MulEq);                                        break;
+		case '%': token.kind = token_kind_variant2(t, Token_Mod, Token_ModEq);                                        break;
+		case '=': token.kind = token_kind_variant2(t, Token_Eq,  Token_CmpEq);                                        break;
+		case '~': token.kind = token_kind_variant2(t, Token_Xor, Token_XorEq);                                        break;
+		case '!': token.kind = token_kind_variant2(t, Token_Not, Token_NotEq);                                        break;
+		case '+': token.kind = token_kind_variant3(t, Token_Add, Token_AddEq, '+', Token_Inc);                        break;
+		case '-': token.kind = token_kind_variant4(t, Token_Sub, Token_SubEq, '-', Token_Dec, '>', Token_ArrowRight); break;
 		case '/': {
 			if (t->curr_rune == '/') {
 				while (t->curr_rune != '\n' && t->curr_rune != GB_RUNE_EOF) {