| ... | ... | @@ -68,6 +68,7 @@ pub const Token = struct { |
| 68 | 68 | Invalid, |
| 69 | 69 | Identifier, |
| 70 | 70 | StringLiteral: StrLitKind, |
| 71 | StringIdentifier, |
| 71 | 72 | Eof, |
| 72 | 73 | Builtin, |
| 73 | 74 | Bang, |
| ... | ... | @@ -205,6 +206,7 @@ pub const Tokenizer = struct { |
| 205 | 206 | Ampersand, |
| 206 | 207 | Period, |
| 207 | 208 | Period2, |
| 209 | SawAtSign, |
| 208 | 210 | }; |
| 209 | 211 | |
| 210 | 212 | pub fn next(self: &Tokenizer) Token { |
| ... | ... | @@ -238,8 +240,7 @@ pub const Tokenizer = struct { |
| 238 | 240 | result.id = Token.Id.Identifier; |
| 239 | 241 | }, |
| 240 | 242 | '@' => { |
| 241 | | state = State.Builtin; |
| 242 | | result.id = Token.Id.Builtin; |
| 243 | state = State.SawAtSign; |
| 243 | 244 | }, |
| 244 | 245 | '=' => { |
| 245 | 246 | state = State.Equal; |
| ... | ... | @@ -313,6 +314,20 @@ pub const Tokenizer = struct { |
| 313 | 314 | break; |
| 314 | 315 | }, |
| 315 | 316 | }, |
| 317 | |
| 318 | State.SawAtSign => switch (c) { |
| 319 | '"' => { |
| 320 | result.id = Token.Id.StringIdentifier; |
| 321 | state = State.StringLiteral; |
| 322 | }, |
| 323 | else => { |
| 324 | // reinterpret as a builtin |
| 325 | self.index -= 1; |
| 326 | state = State.Builtin; |
| 327 | result.id = Token.Id.Builtin; |
| 328 | }, |
| 329 | }, |
| 330 | |
| 316 | 331 | State.Ampersand => switch (c) { |
| 317 | 332 | '=' => { |
| 318 | 333 | result.id = Token.Id.AmpersandEqual; |
| ... | ... | @@ -512,7 +527,59 @@ pub const Tokenizer = struct { |
| 512 | 527 | } |
| 513 | 528 | } |
| 514 | 529 | result.end = self.index; |
| 530 | if (self.index == self.buffer.len) { |
| 531 | switch (state) { |
| 532 | State.Start, |
| 533 | State.C, |
| 534 | State.IntegerLiteral, |
| 535 | State.IntegerLiteralWithRadix, |
| 536 | State.FloatFraction, |
| 537 | State.FloatExponentNumber, |
| 538 | State.StringLiteral, // find this error later |
| 539 | State.Builtin => {}, |
| 540 | |
| 541 | State.Identifier => { |
| 542 | if (Token.getKeyword(self.buffer[result.start..self.index])) |id| { |
| 543 | result.id = id; |
| 544 | } |
| 545 | }, |
| 546 | State.LineComment => { |
| 547 | result.id = Token.Id.Eof; |
| 548 | }, |
| 549 | |
| 550 | State.NumberDot, |
| 551 | State.FloatExponentUnsigned, |
| 552 | State.SawAtSign, |
| 553 | State.StringLiteralBackslash => { |
| 554 | result.id = Token.Id.Invalid; |
| 555 | }, |
| 515 | 556 | |
| 557 | State.Equal => { |
| 558 | result.id = Token.Id.Equal; |
| 559 | }, |
| 560 | State.Bang => { |
| 561 | result.id = Token.Id.Bang; |
| 562 | }, |
| 563 | State.Minus => { |
| 564 | result.id = Token.Id.Minus; |
| 565 | }, |
| 566 | State.Slash => { |
| 567 | result.id = Token.Id.Slash; |
| 568 | }, |
| 569 | State.Zero => { |
| 570 | result.id = Token.Id.IntegerLiteral; |
| 571 | }, |
| 572 | State.Ampersand => { |
| 573 | result.id = Token.Id.Ampersand; |
| 574 | }, |
| 575 | State.Period => { |
| 576 | result.id = Token.Id.Period; |
| 577 | }, |
| 578 | State.Period2 => { |
| 579 | result.id = Token.Id.Ellipsis2; |
| 580 | }, |
| 581 | } |
| 582 | } |
| 516 | 583 | if (result.id == Token.Id.Eof) { |
| 517 | 584 | if (self.pending_invalid_token) |token| { |
| 518 | 585 | self.pending_invalid_token = null; |
| ... | ... | @@ -551,7 +618,7 @@ pub const Tokenizer = struct { |
| 551 | 618 | } else { |
| 552 | 619 | // check utf8-encoded character. |
| 553 | 620 | const length = std.unicode.utf8ByteSequenceLength(c0) catch return 1; |
| 554 | | if (self.index + length >= self.buffer.len) { |
| 621 | if (self.index + length > self.buffer.len) { |
| 555 | 622 | return u3(self.buffer.len - self.index); |
| 556 | 623 | } |
| 557 | 624 | const bytes = self.buffer[self.index..self.index + length]; |
| ... | ... | @@ -632,15 +699,25 @@ test "tokenizer - illegal unicode codepoints" { |
| 632 | 699 | testTokenize("//\xe2\x80\xaa", []Token.Id{}); |
| 633 | 700 | } |
| 634 | 701 | |
| 702 | test "tokenizer - string identifier and builtin fns" { |
| 703 | testTokenize( |
| 704 | \\const @"if" = @import("std"); |
| 705 | , |
| 706 | []Token.Id{ |
| 707 | Token.Id.Keyword_const, |
| 708 | Token.Id.StringIdentifier, |
| 709 | Token.Id.Equal, |
| 710 | Token.Id.Builtin, |
| 711 | Token.Id.LParen, |
| 712 | Token.Id {.StringLiteral = Token.StrLitKind.Normal}, |
| 713 | Token.Id.RParen, |
| 714 | Token.Id.Semicolon, |
| 715 | } |
| 716 | ); |
| 717 | } |
| 718 | |
| 635 | 719 | fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) void { |
| 636 | | // (test authors, just make this bigger if you need it) |
| 637 | | var padded_source: [0x100]u8 = undefined; |
| 638 | | std.mem.copy(u8, padded_source[0..source.len], source); |
| 639 | | padded_source[source.len + 0] = '\n'; |
| 640 | | padded_source[source.len + 1] = '\n'; |
| 641 | | padded_source[source.len + 2] = '\n'; |
| 642 | | |
| 643 | | var tokenizer = Tokenizer.init(padded_source[0..source.len + 3]); |
| 720 | var tokenizer = Tokenizer.init(source); |
| 644 | 721 | for (expected_tokens) |expected_token_id| { |
| 645 | 722 | const token = tokenizer.next(); |
| 646 | 723 | std.debug.assert(@TagType(Token.Id)(token.id) == @TagType(Token.Id)(expected_token_id)); |
| ... | ... | @@ -651,5 +728,6 @@ fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) void { |
| 651 | 728 | else => {}, |
| 652 | 729 | } |
| 653 | 730 | } |
| 654 | | std.debug.assert(tokenizer.next().id == Token.Id.Eof); |
| 731 | const last_token = tokenizer.next(); |
| 732 | std.debug.assert(last_token.id == Token.Id.Eof); |
| 655 | 733 | } |