Bladeren bron

Fix utf8 stuff, Allow _ in numbers, Begin writing next demo code.

Ginger Bill 8 jaren geleden
bovenliggende
commit
d3d3bfd455
9 gewijzigde bestanden met toevoegingen van 391 en 91 verwijderingen
  1. 254 25
      code/demo.odin
  2. 5 4
      core/fmt.odin
  3. 39 26
      core/utf8.odin
  4. 10 0
      src/check_decl.c
  5. 18 2
      src/check_expr.c
  6. 4 2
      src/checker.c
  7. 39 12
      src/exact_value.c
  8. 4 5
      src/parser.c
  9. 18 15
      src/tokenizer.c

+ 254 - 25
code/demo.odin

@@ -1,34 +1,241 @@
-#import "atomic.odin";
 #import "fmt.odin";
-#import "hash.odin";
-#import "math.odin";
-#import "mem.odin";
-#import "opengl.odin";
-#import "os.odin";
-#import win32 "sys/windows.odin";
-#import "sync.odin";
 #import "utf8.odin";
 
 main :: proc() {
-	foo :: proc(x: ^i32) -> (int, int) {
-		fmt.println("^int");
-		return 123, cast(int)(x^);
+	syntax();
+}
+
+syntax :: proc() {
+	// Cyclic type checking
+	// Uncomment to see the error
+	// A :: struct { b: B };
+	// B :: struct { a: A };
+
+	x: int;
+	y := cast(f32)x;
+	z := transmute(u32)y;
+	// down_cast, union_cast as similar too
+
+
+
+	// Basic directives
+	fmt.printf("Basic directives = %s(%d): %s\n", #file, #line, #procedure);
+	// NOTE: new and improved `printf`
+	// TODO: It does need accurate float printing
+
+
+
+	// record fields use the same syntax a procedure signatures
+	Thing1 :: struct {
+		x: f32,
+		y: int,
+		z: ^[]int,
+	};
+	Thing2 :: struct {x: f32, y: int, z: ^[]int};
+
+	// Slice interals are not just a `ptr+count`
+	slice: []int; compile_assert(size_of_val(slice) == 2*size_of(int));
+
+	// Helper type - Help the reader understand that it is quicker
+	My_Int  :: type int;
+	My_Proc :: type proc(int) -> f32;
+
+
+	// All declarations with : are either variable or constant
+	// To make these declarations syntactically consistent
+	v_variable := 123;
+	c_constant :: 123;
+	c_type1    :: int;
+	c_type2    :: []int;
+	c_proc     :: proc() { /* code here */ };
+
+
+	x += 1;
+	x -= 1;
+	// ++ and -- have been removed
+	// x++;
+	// x--;
+
+
+	// You can now build files as a `.dll`
+	// `odin build_dll demo.odin`
+
+
+	// Next part
+	prefixes();
+}
+
+
+Prefix_Type :: struct {x: int, y: f32, z: rawptr};
+
+thread_local my_tls: Prefix_Type;
+
+prefixes :: proc() {
+	using var: Prefix_Type;
+	immutable const := Prefix_Type{1, 2, nil};
+	var.x = 123;
+	x = 123;
+	// const.x = 123; // const is immutable
+
+
+
+	foo :: proc(using immutable pt: Prefix_Type, immutable int_ptr: ^int) {
+		// int_ptr = nil; // Not valid
+		int_ptr^ = 123; // Is valid
 	}
-	foo :: proc(x: rawptr) {
-		fmt.println("rawptr");
+
+
+
+	// Same as C99's `restrict`
+	bar :: proc(no_alias a, b: ^int) {
+		// Assumes a never equals b so it can perform optimizations with that fact
 	}
 
-	THINGI :: 14451;
-	THINGF :: 14451.1;
 
-	a: i32 = #line;
-	b: f32;
-	c: rawptr;
-	fmt.println(foo(^a));
-	foo(^b);
-	foo(c);
-	// foo(nil);
-	atomic.store(^a, 1);
+	when_statements();
+}
+
+
+
+
+
+when_statements :: proc() {
+	X :: 123 + 12;
+	Y :: X/5;
+	COND :: Y > 0;
+
+	when COND {
+		fmt.println("Y > 0");
+	} else {
+		fmt.println("Y <= 0");
+	}
+
+
+	when false {
+		this_code_does_not_exist(123, 321);
+		but_its_syntax_is_valid();
+		x :: ^^^^int;
+	}
+
+	foreign_procedures();
+}
+
+#import "atomic.odin" when ODIN_OS == "windows";
+#foreign_system_library win32_user "user32.lib" when ODIN_OS == "windows";
+// NOTE: This is done on purpose for two reasons:
+// * Makes it clear where the platform specific stuff is
+// * Removes the need to solve the travelling salesman problem when importing files :P
+
+foreign_procedures :: proc() {
+	ShowWindow  :: proc(hwnd: rawptr, cmd_show: i32) -> i32 #foreign win32_user;
+	show_window :: proc(hwnd: rawptr, cmd_show: i32) -> i32 #foreign win32_user "ShowWindow";
+	// NOTE: If that library doesn't get used, it doesn't get linked with
+	// NOTE: There is not link checking yet to see if that procedure does come from that library
+
+	special_expressions();
+}
+
+special_expressions :: proc() {
+	// Block expression
+	x := {
+		a: f32 = 123;
+		b := a-123;
+		c := b/a;
+		give c;
+	}; // semicolon is required as it's an expression
+
+	y := if x < 50 {
+		give x;
+	} else {
+		// TODO: Type cohesion is not yet finished
+		// E.g. this constant "number" should be able to be cast to a `f32` automatically
+		give cast(f32)123;
+	}; // semicolon is required as it's an expression
+
+
+	// This is allows for inline blocks of code and will be a useful feature to have when
+	// macros will be implemented into the language
+
+	loops();
+}
+
+loops :: proc() {
+
+	// while loops
+	while true {
+		break;
+	}
+	while x := 123; x < 124 {
+		x += 2;
+	}
+
+
+/*
+	This only C-style for loop has now been removed
+
+	for i := 0; i < 123; i += 1 {
+	}
+*/
+	for i : 0..<123 {
+	}
+
+	for i : 0...122 {
+	}
+
+	for val, idx : 12..<16 {
+		fmt.println(val, idx);
+	}
+
+	primes := [...]int{2, 3, 5, 7, 11, 13, 17, 19};
+
+	for p : primes {
+		fmt.println(p);
+	}
+
+	// Pointers to arrays, slices, or strings are allowed
+	for _ : ^primes {
+		// ignore the value and just iterate across it
+	}
+
+
+
+	name := "你好,世界";
+	fmt.println(name);
+	for r : name {
+		compile_assert(type_of_val(r) == rune);
+		fmt.printf("%r\n", r);
+	}
+
+	when false {
+		while i := 0; i < name.count {
+			r, size := utf8.decode_rune(name[i:]);
+			i += size;
+			fmt.printf("%c\n", r);
+		}
+	}
+
+
+
+
+
+	// Emulate a C-style loop (not exactly the same though)
+	while x := 0; x < 10 {
+		defer x += 2;
+
+		/* rest of the code */
+		// If `break` is used, the `defer` is still called so it's not the same
+		// as a C-style for loop
+	}
+
+
+
+	procedure_overloading();
+}
+
+
+procedure_overloading :: proc() {
+	THINGF :: 14451.1;
+	THINGI :: 14451;
 
 	foo :: proc() {
 		fmt.printf("Zero args\n");
@@ -42,11 +249,33 @@ main :: proc() {
 	}
 
 	foo();
-	// foo(THINGI);
 	foo(THINGF);
+	// foo(THINGI);
 	foo(cast(int)THINGI);
-	fmt.println(THINGI);
 	fmt.println(THINGF);
+	fmt.println(THINGI);
+
+
+
+
+	foo :: proc(x: ^i32) -> (int, int) {
+		fmt.println("^int");
+		return 123, cast(int)(x^);
+	}
+	foo :: proc(x: rawptr) {
+		fmt.println("rawptr");
+	}
+
+
+	a: i32 = #line;
+	b: f32;
+	c: rawptr;
+	fmt.println(foo(^a));
+	foo(^b);
+	foo(c);
+	// foo(nil);
+	atomic.store(^a, 1);
+
 
 	f: proc();
 	f = foo;

+ 5 - 4
core/fmt.odin

@@ -233,7 +233,7 @@ bprint :: proc(buf: ^Buffer, args: ...any) -> int {
 	for arg, i : args {
 		is_string := arg.data != nil && is_type_string(arg.type_info);
 		if i > 0 && !is_string && !prev_string {
-			buffer_write_rune(buf, ' ');
+			buffer_write_byte(buf, ' ');
 		}
 		fmt_value(^fi, args[i], 'v');
 		prev_string = is_string;
@@ -247,11 +247,11 @@ bprintln :: proc(buf: ^Buffer, args: ...any) -> int {
 
 	for arg, i : args {
 		if i > 0 {
-			buffer_write_rune(buf, ' ');
+			buffer_write_byte(buf, ' ');
 		}
 		fmt_value(^fi, args[i], 'v');
 	}
-	buffer_write_rune(buf, '\n');
+	buffer_write_byte(buf, '\n');
 	return buf.length;
 }
 
@@ -532,7 +532,8 @@ fmt_int :: proc(fi: ^Fmt_Info, u: u64, signed: bool, verb: rune) {
 	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 'c': fmt_rune(fi, cast(rune)u);
+	case 'c', 'r':
+		fmt_rune(fi, cast(rune)u);
 	case 'U':
 		r := cast(rune)u;
 		if r < 0 || r > utf8.MAX_RUNE {

+ 39 - 26
core/utf8.odin

@@ -8,9 +8,29 @@ UTF_MAX    :: 4;
 SURROGATE_MIN :: 0xd800;
 SURROGATE_MAX :: 0xdfff;
 
+T1 :: 0b0000_0000;
+TX :: 0b1000_0000;
+T2 :: 0b1100_0000;
+T3 :: 0b1110_0000;
+T4 :: 0b1111_0000;
+T5 :: 0b1111_1000;
+
+MASKX :: 0b0011_1111;
+MASK2 :: 0b0001_1111;
+MASK3 :: 0b0000_1111;
+MASK4 :: 0b0000_0111;
+
+RUNE1_MAX :: 1<<7 - 1;
+RUNE2_MAX :: 1<<11 - 1;
+RUNE3_MAX :: 1<<16 - 1;
+
+// The default lowest and highest continuation byte.
+LOCB :: 0b1000_0000;
+HICB :: 0b1011_1111;
+
 Accept_Range :: struct { lo, hi: u8 }
 
-accept_ranges := [5]Accept_Range{
+immutable accept_ranges := [5]Accept_Range{
 	{0x80, 0xbf},
 	{0xa0, 0xbf},
 	{0x80, 0x9f},
@@ -18,7 +38,7 @@ accept_ranges := [5]Accept_Range{
 	{0x80, 0x8f},
 };
 
-accept_sizes := [256]byte{
+immutable accept_sizes := [256]byte{
 	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f
 	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f
 	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f
@@ -68,7 +88,7 @@ encode_rune :: proc(r: rune) -> ([4]byte, int) {
 	buf[0] = 0xf0 | cast(byte)(r>>18);
 	buf[1] = 0x80 | cast(byte)(r>>12) & mask;
 	buf[2] = 0x80 | cast(byte)(r>>6)  & mask;
-	buf[3] = 0x80 | cast(byte)r     & mask;
+	buf[3] = 0x80 | cast(byte)r       & mask;
 	return buf, 4;
 }
 
@@ -77,43 +97,36 @@ decode_rune :: proc(s: string) -> (rune, int) {
 	if n < 1 {
 		return RUNE_ERROR, 0;
 	}
-	b0 := s[0];
-	x := accept_sizes[b0];
-	if x >= 0xf0 {
-		mask := (cast(rune)x << 31) >> 31; // all zeros or all ones
-		return cast(rune)b0 &~ mask | RUNE_ERROR&mask, 1;
+	s0 := s[0];
+	x := accept_sizes[s0];
+	if x >= 0xF0 {
+		mask := cast(rune)(x) << 31 >> 31; // NOTE(bill): Create 0x0000 or 0xffff.
+		return cast(rune)(s[0])&~mask | RUNE_ERROR&mask, 1;
 	}
-	size := x & 7;
-	ar := accept_ranges[x>>4];
-	if n < cast(int)size {
+	sz := x & 7;
+	accept := accept_ranges[x>>4];
+	if n < cast(int)sz {
 		return RUNE_ERROR, 1;
 	}
 	b1 := s[1];
-	if b1 < ar.lo || ar.hi < b1 {
+	if b1 < accept.lo || accept.hi < b1 {
 		return RUNE_ERROR, 1;
 	}
-
-	MASK_X :: 0b00111111;
-	MASK_2 :: 0b00011111;
-	MASK_3 :: 0b00001111;
-	MASK_4 :: 0b00000111;
-
-	if size == 2 {
-		return cast(rune)(b0&MASK_2)<<6 | cast(rune)(b1&MASK_X), 2;
+	if sz == 2 {
+		return cast(rune)(s0&MASK2)<<6 | cast(rune)(b1&MASKX), 2;
 	}
 	b2 := s[2];
-	if b2 < 0x80 || 0xbf < b2 {
+	if b2 < LOCB || HICB < b2 {
 		return RUNE_ERROR, 1;
 	}
-	if size == 3 {
-		return cast(rune)(b0&MASK_3)<<12 | cast(rune)(b1&MASK_X)<<6 | cast(rune)(b2&MASK_X), 3;
+	if sz == 3 {
+		return cast(rune)(s0&MASK3)<<12 | cast(rune)(b1&MASKX)<<6 | cast(rune)(b2&MASKX), 3;
 	}
 	b3 := s[3];
-	if b3 < 0x80 || 0xbf < b3 {
+	if b3 < LOCB || HICB < b3 {
 		return RUNE_ERROR, 1;
 	}
-	return cast(rune)(b0&MASK_4)<<18 | cast(rune)(b1&MASK_X)<<12 | cast(rune)(b3&MASK_X)<<6 | cast(rune)(b3&MASK_X), 4;
-
+	return cast(rune)(s0&MASK4)<<18 | cast(rune)(b1&MASKX)<<12 | cast(rune)(b2&MASKX)<<6 | cast(rune)(b3&MASKX), 4;
 }
 
 

+ 10 - 0
src/check_decl.c

@@ -477,9 +477,19 @@ void check_entity_decl(Checker *c, Entity *e, DeclInfo *d, Type *named_type) {
 void check_proc_body(Checker *c, Token token, DeclInfo *decl, Type *type, AstNode *body) {
 	GB_ASSERT(body->kind == AstNode_BlockStmt);
 
+	String proc_name = {0};
+	if (token.kind == Token_Ident) {
+		proc_name = token.string;
+	} else {
+		// TODO(bill): Better name
+		proc_name = str_lit("(anonymous-procedure)");
+	}
+
 	CheckerContext old_context = c->context;
 	c->context.scope = decl->scope;
 	c->context.decl = decl;
+	c->context.proc_name = proc_name;
+
 
 	GB_ASSERT(type->kind == Type_Proc);
 	if (type->Proc.param_count > 0) {

+ 18 - 2
src/check_expr.c

@@ -3892,6 +3892,16 @@ ExprKind check__expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint
 		} else if (str_eq(bd->name, str_lit("line"))) {
 			o->type = t_untyped_integer;
 			o->value = make_exact_value_integer(bd->token.pos.line);
+		} else if (str_eq(bd->name, str_lit("procedure"))) {
+			if (c->proc_stack.count == 0) {
+				error_node(node, "#procedure may only be used within procedures");
+				o->type = t_untyped_string;
+				o->value = make_exact_value_string(str_lit(""));
+			} else {
+				o->type = t_untyped_string;
+				o->value = make_exact_value_string(c->context.proc_name);
+			}
+
 		} else {
 			GB_PANIC("Unknown basic basic directive");
 		}
@@ -3962,9 +3972,11 @@ ExprKind check__expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint
 			goto error;
 		} else if (operands.count == 1) {
 			Operand operand = operands.e[0];
-			if (type_hint != NULL) {
-				convert_to_typed(c, &operand, type_hint, 0);
+			Type *th = type_hint != NULL ? type_hint : c->context.type_hint;
+			if (th != NULL) {
+				convert_to_typed(c, &operand, th, 0);
 			}
+			// IMPORTANT NOTE(bill): This type could be untyped!!!
 			o->type = default_type(operand.type);
 			o->mode = Addressing_Value;
 		} else {
@@ -4012,10 +4024,14 @@ ExprKind check__expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint
 			goto error;
 		}
 
+		CheckerContext prev_context = c->context;
+		c->context.type_hint = type_hint;
 		check_open_scope(c, node);
 		check_stmt_list(c, be->stmts, Stmt_GiveAllowed);
 		check_close_scope(c);
 
+		c->context = prev_context;
+
 		AstNode *give_node = NULL;
 		if (!check_is_giving(node, &give_node) || give_node == NULL) {
 			error_node(node, "Missing give statement at end of block expression");

+ 4 - 2
src/checker.c

@@ -251,6 +251,8 @@ typedef struct CheckerContext {
 	DeclInfo * decl;
 	u32        stmt_state_flags;
 	bool       in_defer; // TODO(bill): Actually handle correctly
+	String     proc_name;
+	Type *     type_hint;
 } CheckerContext;
 
 // NOTE(bill): Symbol tables
@@ -276,8 +278,8 @@ typedef struct Checker {
 	BaseTypeSizes          sizes;
 	Scope *                global_scope;
 	Array(ProcedureInfo)   procs; // NOTE(bill): Procedures to check
-	Array(DelayedDecl)   delayed_imports;
-	Array(DelayedDecl)   delayed_foreign_libraries;
+	Array(DelayedDecl)     delayed_imports;
+	Array(DelayedDecl)     delayed_foreign_libraries;
 
 
 	gbArena                arena;

+ 39 - 12
src/exact_value.c

@@ -54,30 +54,57 @@ ExactValue make_exact_value_string(String string) {
 	return result;
 }
 
+ExactValue make_exact_value_integer(i64 i) {
+	ExactValue result = {ExactValue_Integer};
+	result.value_integer = i;
+	return result;
+}
+
 ExactValue make_exact_value_integer_from_string(String string) {
 	// TODO(bill): Allow for numbers with underscores in them
-	ExactValue result = {ExactValue_Integer};
 	i32 base = 10;
+	bool has_prefix = false;
 	if (string.len > 2 && string.text[0] == '0') {
 		switch (string.text[1]) {
-		case 'b': base = 2;  break;
-		case 'o': base = 8;  break;
-		case 'd': base = 10; break;
-		case 'x': base = 16; break;
+		case 'b': base = 2;  has_prefix = true; break;
+		case 'o': base = 8;  has_prefix = true; break;
+		case 'd': base = 10; has_prefix = true; break;
+		case 'x': base = 16; has_prefix = true; break;
 		}
 	}
 
-	result.value_integer = gb_str_to_i64(cast(char *)string.text, NULL, base);
+	u8 *text = string.text;
+	isize len = string.len;
+	if (has_prefix) {
+		text += 2;
+		len -= 2;
+	}
 
-	return result;
-}
+	i64 result = 0;
+	for (isize i = 0; i < len; i++) {
+		Rune r = cast(Rune)text[i];
+		if (r == '_') {
+			continue;
+		}
+		i64 v = 0;
+		if (gb_char_is_digit(r)) {
+			v = r - '0';
+		} else if (gb_char_is_hex_digit(r)) {
+			v = gb_hex_digit_to_int(r);
+		} else {
+			break;
+		}
 
-ExactValue make_exact_value_integer(i64 i) {
-	ExactValue result = {ExactValue_Integer};
-	result.value_integer = i;
-	return result;
+		result *= base;
+		result += v;
+	}
+
+
+	return make_exact_value_integer(result);
 }
 
+
+
 ExactValue make_exact_value_float_from_string(String string) {
 	// TODO(bill): Allow for numbers with underscores in them
 	ExactValue result = {ExactValue_Float};

+ 4 - 5
src/parser.c

@@ -1770,11 +1770,7 @@ AstNode *parse_operand(AstFile *f, bool lhs) {
 	case Token_Hash: {
 		Token token = expect_token(f, Token_Hash);
 		Token name  = expect_token(f, Token_Ident);
-		if (str_eq(name.string, str_lit("file"))) {
-			return make_basic_directive(f, token, name.string);
-		} else if (str_eq(name.string, str_lit("line"))) {
-			return make_basic_directive(f, token, name.string);
-		} else if (str_eq(name.string, str_lit("run"))) {
+		if (str_eq(name.string, str_lit("run"))) {
 			AstNode *expr = parse_expr(f, false);
 			operand = make_run_expr(f, token, name, expr);
 			if (unparen_expr(expr)->kind != AstNode_CallExpr) {
@@ -1782,6 +1778,9 @@ AstNode *parse_operand(AstFile *f, bool lhs) {
 				operand = make_bad_expr(f, token, f->curr_token);
 			}
 			warning(token, "#run is not yet implemented");
+		} else if (str_eq(name.string, str_lit("file"))) { return make_basic_directive(f, token, name.string);
+		} else if (str_eq(name.string, str_lit("line"))) { return make_basic_directive(f, token, name.string);
+		} else if (str_eq(name.string, str_lit("procedure"))) { return make_basic_directive(f, token, name.string);
 		} else {
 			operand = make_tag_expr(f, token, name, parse_expr(f, false));
 		}

+ 18 - 15
src/tokenizer.c

@@ -462,12 +462,15 @@ gb_inline i32 digit_value(Rune r) {
 	return 16; // NOTE(bill): Larger than highest possible
 }
 
-gb_inline void scan_mantissa(Tokenizer *t, i32 base) {
-	// TODO(bill): Allow for underscores in numbers as a number separator
-	// TODO(bill): Is this a good idea?
-	// while (digit_value(t->curr_rune) < base || t->curr_rune == '_')
-	while (digit_value(t->curr_rune) < base) {
-		advance_to_next_rune(t);
+gb_inline void scan_mantissa(Tokenizer *t, i32 base, bool allow_underscore) {
+	if (allow_underscore) {
+		while (digit_value(t->curr_rune) < base || t->curr_rune == '_') {
+			advance_to_next_rune(t);
+		}
+	} else {
+		while (digit_value(t->curr_rune) < base) {
+			advance_to_next_rune(t);
+		}
 	}
 }
 
@@ -482,7 +485,7 @@ Token scan_number_to_token(Tokenizer *t, bool seen_decimal_point) {
 
 	if (seen_decimal_point) {
 		token.kind = Token_Float;
-		scan_mantissa(t, 10);
+		scan_mantissa(t, 10, true);
 		goto exponent;
 	}
 
@@ -491,31 +494,31 @@ Token scan_number_to_token(Tokenizer *t, bool seen_decimal_point) {
 		advance_to_next_rune(t);
 		if (t->curr_rune == 'b') { // Binary
 			advance_to_next_rune(t);
-			scan_mantissa(t, 2);
+			scan_mantissa(t, 2, true);
 			if (t->curr - prev <= 2) {
 				token.kind = Token_Invalid;
 			}
 		} else if (t->curr_rune == 'o') { // Octal
 			advance_to_next_rune(t);
-			scan_mantissa(t, 8);
+			scan_mantissa(t, 8, true);
 			if (t->curr - prev <= 2) {
 				token.kind = Token_Invalid;
 			}
 		} else if (t->curr_rune == 'd') { // Decimal
 			advance_to_next_rune(t);
-			scan_mantissa(t, 10);
+			scan_mantissa(t, 10, true);
 			if (t->curr - prev <= 2) {
 				token.kind = Token_Invalid;
 			}
 		} else if (t->curr_rune == 'x') { // Hexadecimal
 			advance_to_next_rune(t);
-			scan_mantissa(t, 16);
+			scan_mantissa(t, 16, true);
 			if (t->curr - prev <= 2) {
 				token.kind = Token_Invalid;
 			}
 		} else {
 			seen_decimal_point = false;
-			scan_mantissa(t, 10);
+			scan_mantissa(t, 10, true);
 
 			if (t->curr_rune == '.' || t->curr_rune == 'e' || t->curr_rune == 'E') {
 				seen_decimal_point = true;
@@ -527,7 +530,7 @@ Token scan_number_to_token(Tokenizer *t, bool seen_decimal_point) {
 		return token;
 	}
 
-	scan_mantissa(t, 10);
+	scan_mantissa(t, 10, true);
 
 fraction:
 	if (t->curr_rune == '.') {
@@ -540,7 +543,7 @@ fraction:
 			goto end;
 		}
 		token.kind = Token_Float;
-		scan_mantissa(t, 10);
+		scan_mantissa(t, 10, true);
 	}
 
 exponent:
@@ -550,7 +553,7 @@ exponent:
 		if (t->curr_rune == '-' || t->curr_rune == '+') {
 			advance_to_next_rune(t);
 		}
-		scan_mantissa(t, 10);
+		scan_mantissa(t, 10, false);
 	}
 
 end: