diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 160eb3a5c..661c46751 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -12,6 +12,10 @@ Parser :: struct { file: ^ast.File, tok: tokenizer.Tokenizer, + // If optional_semicolons is true, semicolons are completely as statement terminators + // different to .Insert_Semicolon in tok.flags + optional_semicolons: bool, + warn: Warning_Handler, err: Error_Handler, @@ -128,6 +132,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { p.line_comment = nil; } + if p.optional_semicolons { + p.tok.flags += {.Insert_Semicolon}; + } + p.file = file; tokenizer.init(&p.tok, file.src, file.fullpath, p.err); if p.tok.ch <= 0 { @@ -400,6 +408,11 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { if node == nil { return false; } + + if p.optional_semicolons { + return true; + } + switch n in node.derived { case ast.Empty_Stmt, ast.Block_Stmt: return true; @@ -439,14 +452,34 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { return false; } +expect_semicolon_newline_error :: proc(p: ^Parser, token: tokenizer.Token, s: ^ast.Node) { + if !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 { if allow_token(p, .Semicolon) { + expect_semicolon_newline_error(p, p.prev_tok, node); return true; } prev := p.prev_tok; if prev.kind == .Semicolon { + expect_semicolon_newline_error(p, p.prev_tok, node); return true; } @@ -615,7 +648,7 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt { cond = parse_expr(p, false); } else { init = parse_simple_stmt(p, nil); - if allow_token(p, .Semicolon) { + if parse_control_statement_semicolon_separator(p) { cond = parse_expr(p, false); } else { cond = convert_stmt_to_expr(p, init, "boolean expression"); @@ -668,6 +701,18 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt { return if_stmt; } +parse_control_statement_semicolon_separator :: proc(p: ^Parser) -> bool { + tok := peek_token(p); + if tok.kind != .Open_Brace { + return allow_token(p, .Semicolon); + } + if tok.text == ";" { + return allow_token(p, .Semicolon); + } + return false; + +} + parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt { if p.curr_proc == nil { error(p, p.curr_tok.pos, "you cannot use a for statement in the file scope"); @@ -716,7 +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; cond = nil; 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}); if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In { is_type_switch = true; - } else if allow_token(p, .Semicolon) { + } else if parse_control_statement_semicolon_separator(p) { init = tag; tag = nil; if p.curr_tok.kind != .Open_Brace { @@ -831,6 +876,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt { } + skip_possible_newline(p); open := expect_token(p, .Open_Brace); for p.curr_tok.kind == .Case { @@ -958,6 +1004,7 @@ parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Bl defer p.in_foreign_block = prev_in_foreign_block; p.in_foreign_block = true; + skip_possible_newline_for_literal(p); open := expect_token(p, .Open_Brace); for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { decl := parse_foreign_block_decl(p); @@ -1287,7 +1334,7 @@ token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int { #partial switch kind { case .Question, .If, .When: return 1; - case .Ellipsis, .Range_Half: + case .Ellipsis, .Range_Half, .Range_Full: if !p.allow_range { return 0; } @@ -2233,6 +2280,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } body: ^ast.Stmt; + skip_possible_newline_for_literal(p); + if allow_token(p, .Undef) { body = nil; if where_token.kind != .Invalid { @@ -2405,6 +2454,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { p.expr_level = where_prev_level; } + skip_possible_newline_for_literal(p); expect_token(p, .Open_Brace); fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct); close := expect_token(p, .Close_Brace); @@ -2473,6 +2523,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { variants: [dynamic]^ast.Expr; + skip_possible_newline_for_literal(p); expect_token_after(p, .Open_Brace, "union"); for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { @@ -2503,6 +2554,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { if p.curr_tok.kind != .Open_Brace { base_type = parse_type(p); } + + skip_possible_newline_for_literal(p); open := expect_token(p, .Open_Brace); fields := parse_elem_list(p); close := expect_token(p, .Close_Brace); @@ -2601,6 +2654,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } } + skip_possible_newline_for_literal(p); open := expect_token(p, .Open_Brace); asm_string := parse_expr(p, false); expect_token(p, .Comma); @@ -2811,7 +2865,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a open := expect_token(p, .Open_Bracket); #partial switch p.curr_tok.kind { - case .Colon, .Ellipsis, .Range_Half: + case .Colon, .Ellipsis, .Range_Half, .Range_Full: // NOTE(bill): Do not err yet break; case: @@ -2819,7 +2873,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a } #partial switch p.curr_tok.kind { - case .Ellipsis, .Range_Half: + case .Ellipsis, .Range_Half, .Range_Full: error(p, p.curr_tok.pos, "expected a colon, not a range"); fallthrough; case .Colon: diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index 1b37bae23..88908d7f8 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -107,6 +107,7 @@ Token_Kind :: enum u32 { Comma, // , Ellipsis, // .. Range_Half, // ..< + Range_Full, // ..= Back_Slash, // \ B_Operator_End, @@ -233,6 +234,7 @@ tokens := [Token_Kind.COUNT]string { ",", "..", "..<", + "..=", "\\", "", diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 58f546191..297f42e3d 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -623,6 +623,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '<' { advance_rune(t); kind = .Range_Half; + } else if t.ch == '=' { + advance_rune(t); + kind = .Range_Full; } } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fb3a51415..61cfd7d6e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5940,8 +5940,9 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu TokenKind op = Token_Lt; switch (ie->op.kind) { - case Token_Ellipsis: op = Token_LtEq; break; - case Token_RangeHalf: op = Token_Lt; break; + case Token_Ellipsis: op = Token_LtEq; break; // .. + case Token_RangeFull: op = Token_LtEq; break; // ..= + case Token_RangeHalf: op = Token_Lt; break; // ..< default: error(ie->op, "Invalid range operator"); break; } bool ok = compare_exact_values(op, a, b); @@ -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); - if (ie->op.kind == Token_Ellipsis) { + if (ie->op.kind != Token_RangeHalf) { inline_for_depth = exact_value_increment_one(inline_for_depth); } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index a954b44b6..2c9d8e00e 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -939,6 +939,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { TokenKind upper_op = Token_Invalid; switch (be->op.kind) { case Token_Ellipsis: upper_op = Token_GtEq; break; + case Token_RangeFull: upper_op = Token_GtEq; break; case Token_RangeHalf: upper_op = Token_Gt; break; default: GB_PANIC("Invalid range operator"); break; } diff --git a/src/check_type.cpp b/src/check_type.cpp index cdce6dae8..24a3e0a59 100644 --- a/src/check_type.cpp +++ b/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) { case Token_Ellipsis: + case Token_RangeFull: if (upper - lower >= bits) { error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, (upper-lower+1)); } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7bdc8b04a..41b1e8fc9 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -4041,6 +4041,7 @@ void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, TokenKind op = Token_Lt; switch (node->op.kind) { case Token_Ellipsis: op = Token_LtEq; break; + case Token_RangeFull: op = Token_LtEq; break; case Token_RangeHalf: op = Token_Lt; 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 end = end_expr->tav.value; - if (op == Token_Ellipsis) { // .. [start, end] + if (op != Token_RangeHalf) { // .. [start, end] (or ..=) ExactValue index = exact_value_i64(0); for (ExactValue val = start; 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); } - } else if (op == Token_RangeHalf) { // ..< [start, end) + } else { // ..< [start, end) ExactValue index = exact_value_i64(0); for (ExactValue val = start; 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; switch (ie->op.kind) { case Token_Ellipsis: op = Token_LtEq; break; + case Token_RangeFull: op = Token_LtEq; break; case Token_RangeHalf: op = Token_Lt; break; default: GB_PANIC("Invalid interval operator"); break; } diff --git a/src/parser.cpp b/src/parser.cpp index 0dae732e5..2e27b8698 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1344,6 +1344,7 @@ Token expect_token_after(AstFile *f, TokenKind kind, char const *msg) { bool is_token_range(TokenKind kind) { switch (kind) { case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: return true; } @@ -1574,6 +1575,10 @@ void expect_semicolon(AstFile *f, Ast *s) { return; } + if (f->curr_token.kind == Token_EOF) { + return; + } + if (s != nullptr) { bool insert_semi = (f->tokenizer.flags & TokenizerFlag_InsertSemicolon) != 0; if (insert_semi) { @@ -2315,7 +2320,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { f->expr_level = prev_level; } - + skip_possible_newline_for_literal(f); Token open = expect_token_after(f, Token_OpenBrace, "struct"); isize name_count = 0; @@ -2675,6 +2680,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { switch (f->curr_token.kind) { case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: // NOTE(bill): Do not err yet case Token_Colon: @@ -2686,6 +2692,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { switch (f->curr_token.kind) { case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: syntax_error(f->curr_token, "Expected a colon, not a range"); /* fallthrough */ @@ -2812,6 +2819,7 @@ i32 token_precedence(AstFile *f, TokenKind t) { case Token_when: return 1; case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: if (!f->allow_range) { return 0; @@ -3926,12 +3934,6 @@ Ast *parse_return_stmt(AstFile *f) { while (f->curr_token.kind != Token_Semicolon) { Ast *arg = parse_expr(f, false); - // if (f->curr_token.kind == Token_Eq) { - // Token eq = expect_token(f, Token_Eq); - // Ast *value = parse_value(f); - // arg = ast_field_value(f, arg, value, eq); - // } - array_add(&results, arg); if (f->curr_token.kind != Token_Comma || f->curr_token.kind == Token_EOF) { @@ -4052,7 +4054,7 @@ Ast *parse_case_clause(AstFile *f, bool is_type) { } f->allow_range = prev_allow_range; f->allow_in_expr = prev_allow_in_expr; - expect_token(f, Token_Colon); // TODO(bill): Is this the best syntax? + expect_token(f, Token_Colon); Array stmts = parse_stmt_list(f); return ast_case_clause(f, token, list, stmts); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 5936ac3fe..d1310b56e 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -76,6 +76,7 @@ TOKEN_KIND(Token__ComparisonEnd, ""), \ TOKEN_KIND(Token_Period, "."), \ TOKEN_KIND(Token_Comma, ","), \ TOKEN_KIND(Token_Ellipsis, ".."), \ + TOKEN_KIND(Token_RangeFull, "..="), \ TOKEN_KIND(Token_RangeHalf, "..<"), \ TOKEN_KIND(Token_BackSlash, "\\"), \ TOKEN_KIND(Token__OperatorEnd, ""), \ @@ -1204,6 +1205,9 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { if (t->curr_rune == '<') { advance_to_next_rune(t); token->kind = Token_RangeHalf; + } else if (t->curr_rune == '=') { + advance_to_next_rune(t); + token->kind = Token_RangeFull; } } else if ('0' <= t->curr_rune && t->curr_rune <= '9') { scan_number_to_token(t, token, true);