Browse Source

Allow `..=` alongside `..` as a "full range" operator; Update `core:odin/parser` etc

gingerBill 4 years ago
parent
commit
ce08e832f7

+ 60 - 6
core/odin/parser/parser.odin

@@ -12,6 +12,10 @@ 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
+	optional_semicolons: bool,
+
 	warn: Warning_Handler,
 	warn: Warning_Handler,
 	err:  Error_Handler,
 	err:  Error_Handler,
 
 
@@ -128,6 +132,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
 		p.line_comment     = nil;
 		p.line_comment     = nil;
 	}
 	}
 
 
+	if p.optional_semicolons {
+		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 +408,11 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 	if node == nil {
 	if node == nil {
 		return false;
 		return false;
 	}
 	}
+
+	if p.optional_semicolons {
+		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 +452,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 !p.optional_semicolons && .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 +648,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 +701,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 +761,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 +865,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 +876,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 +1004,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 +1334,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;
 		}
 		}
@@ -2233,6 +2280,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 {
@@ -2405,6 +2454,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);
@@ -2473,6 +2523,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 +2554,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 +2654,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 +2865,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 +2873,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:

+ 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 {
 	",",
 	",",
 	"..",
 	"..",
 	"..<",
 	"..<",
+	"..=",
 	"\\",
 	"\\",
 	"",
 	"",
 
 

+ 3 - 0
core/odin/tokenizer/tokenizer.odin

@@ -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;
 					}
 					}
 				}
 				}
 			}
 			}

+ 4 - 3
src/check_expr.cpp

@@ -5940,8 +5940,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);
@@ -5952,7 +5953,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);
 		}
 		}
 
 

+ 1 - 0
src/check_stmt.cpp

@@ -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;
 				}
 				}

+ 1 - 0
src/check_type.cpp

@@ -931,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));
 			}
 			}

+ 4 - 2
src/llvm_backend.cpp

@@ -4041,6 +4041,7 @@ void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node,
 	TokenKind op = Token_Lt;
 	TokenKind op = Token_Lt;
 	switch (node->op.kind) {
 	switch (node->op.kind) {
 	case Token_Ellipsis:  op = Token_LtEq; break;
 	case Token_Ellipsis:  op = Token_LtEq; break;
+	case Token_RangeFull: op = Token_LtEq; break;
 	case Token_RangeHalf: op = Token_Lt;  break;
 	case Token_RangeHalf: op = Token_Lt;  break;
 	default: GB_PANIC("Invalid interval operator"); break;
 	default: GB_PANIC("Invalid interval operator"); break;
 	}
 	}
@@ -4454,7 +4455,7 @@ void lb_build_inline_range_stmt(lbProcedure *p, AstUnrollRangeStmt *rs, Scope *s
 
 
 		ExactValue start = start_expr->tav.value;
 		ExactValue start = start_expr->tav.value;
 		ExactValue end   = end_expr->tav.value;
 		ExactValue end   = end_expr->tav.value;
-		if (op == Token_Ellipsis) { // .. [start, end]
+		if (op != Token_RangeHalf) { // .. [start, end] (or ..=)
 			ExactValue index = exact_value_i64(0);
 			ExactValue index = exact_value_i64(0);
 			for (ExactValue val = start;
 			for (ExactValue val = start;
 			     compare_exact_values(Token_LtEq, val, end);
 			     compare_exact_values(Token_LtEq, val, end);
@@ -4465,7 +4466,7 @@ void lb_build_inline_range_stmt(lbProcedure *p, AstUnrollRangeStmt *rs, Scope *s
 
 
 				lb_build_stmt(p, rs->body);
 				lb_build_stmt(p, rs->body);
 			}
 			}
-		} else if (op == Token_RangeHalf) { // ..< [start, end)
+		} else { // ..< [start, end)
 			ExactValue index = exact_value_i64(0);
 			ExactValue index = exact_value_i64(0);
 			for (ExactValue val = start;
 			for (ExactValue val = start;
 			     compare_exact_values(Token_Lt, val, end);
 			     compare_exact_values(Token_Lt, val, end);
@@ -4632,6 +4633,7 @@ void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope *scope) {
 				TokenKind op = Token_Invalid;
 				TokenKind op = Token_Invalid;
 				switch (ie->op.kind) {
 				switch (ie->op.kind) {
 				case Token_Ellipsis:  op = Token_LtEq; break;
 				case Token_Ellipsis:  op = Token_LtEq; break;
+				case Token_RangeFull: op = Token_LtEq; break;
 				case Token_RangeHalf: op = Token_Lt;   break;
 				case Token_RangeHalf: op = Token_Lt;   break;
 				default: GB_PANIC("Invalid interval operator"); break;
 				default: GB_PANIC("Invalid interval operator"); break;
 				}
 				}

+ 10 - 8
src/parser.cpp

@@ -1344,6 +1344,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 +1575,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) {
@@ -2315,7 +2320,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;
@@ -2675,6 +2680,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:
@@ -2686,6 +2692,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 */
@@ -2812,6 +2819,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;
@@ -3926,12 +3934,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) {
@@ -4052,7 +4054,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);

+ 4 - 0
src/tokenizer.cpp

@@ -76,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, ""), \
@@ -1204,6 +1205,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);