1 //===- AsmLexer.cpp - Lexer for Assembly Files ----------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This class implements the lexer for assembly files. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "llvm/MC/MCParser/AsmLexer.h" 15 #include "llvm/MC/MCAsmInfo.h" 16 #include "llvm/Support/MemoryBuffer.h" 17 #include "llvm/Support/SMLoc.h" 18 #include <cctype> 19 #include <cerrno> 20 #include <cstdio> 21 #include <cstdlib> 22 using namespace llvm; 23 24 AsmLexer::AsmLexer(const MCAsmInfo &MAI) : MAI(MAI) { 25 CurPtr = nullptr; 26 isAtStartOfLine = true; 27 AllowAtInIdentifier = !StringRef(MAI.getCommentString()).startswith("@"); 28 } 29 30 AsmLexer::~AsmLexer() { 31 } 32 33 void AsmLexer::setBuffer(StringRef Buf, const char *ptr) { 34 CurBuf = Buf; 35 36 if (ptr) 37 CurPtr = ptr; 38 else 39 CurPtr = CurBuf.begin(); 40 41 TokStart = nullptr; 42 } 43 44 /// ReturnError - Set the error to the specified string at the specified 45 /// location. This is defined to always return AsmToken::Error. 46 AsmToken AsmLexer::ReturnError(const char *Loc, const std::string &Msg) { 47 SetError(SMLoc::getFromPointer(Loc), Msg); 48 49 return AsmToken(AsmToken::Error, StringRef(Loc, 0)); 50 } 51 52 int AsmLexer::getNextChar() { 53 char CurChar = *CurPtr++; 54 switch (CurChar) { 55 default: 56 return (unsigned char)CurChar; 57 case 0: 58 // A nul character in the stream is either the end of the current buffer or 59 // a random nul in the file. Disambiguate that here. 60 if (CurPtr - 1 != CurBuf.end()) 61 return 0; // Just whitespace. 62 63 // Otherwise, return end of file. 64 --CurPtr; // Another call to lex will return EOF again. 65 return EOF; 66 } 67 } 68 69 /// LexFloatLiteral: [0-9]*[.][0-9]*([eE][+-]?[0-9]*)? 70 /// 71 /// The leading integral digit sequence and dot should have already been 72 /// consumed, some or all of the fractional digit sequence *can* have been 73 /// consumed. 74 AsmToken AsmLexer::LexFloatLiteral() { 75 // Skip the fractional digit sequence. 76 while (isdigit(*CurPtr)) 77 ++CurPtr; 78 79 // Check for exponent; we intentionally accept a slighlty wider set of 80 // literals here and rely on the upstream client to reject invalid ones (e.g., 81 // "1e+"). 82 if (*CurPtr == 'e' || *CurPtr == 'E') { 83 ++CurPtr; 84 if (*CurPtr == '-' || *CurPtr == '+') 85 ++CurPtr; 86 while (isdigit(*CurPtr)) 87 ++CurPtr; 88 } 89 90 return AsmToken(AsmToken::Real, 91 StringRef(TokStart, CurPtr - TokStart)); 92 } 93 94 /// LexHexFloatLiteral matches essentially (.[0-9a-fA-F]*)?[pP][+-]?[0-9a-fA-F]+ 95 /// while making sure there are enough actual digits around for the constant to 96 /// be valid. 97 /// 98 /// The leading "0x[0-9a-fA-F]*" (i.e. integer part) has already been consumed 99 /// before we get here. 100 AsmToken AsmLexer::LexHexFloatLiteral(bool NoIntDigits) { 101 assert((*CurPtr == 'p' || *CurPtr == 'P' || *CurPtr == '.') && 102 "unexpected parse state in floating hex"); 103 bool NoFracDigits = true; 104 105 // Skip the fractional part if there is one 106 if (*CurPtr == '.') { 107 ++CurPtr; 108 109 const char *FracStart = CurPtr; 110 while (isxdigit(*CurPtr)) 111 ++CurPtr; 112 113 NoFracDigits = CurPtr == FracStart; 114 } 115 116 if (NoIntDigits && NoFracDigits) 117 return ReturnError(TokStart, "invalid hexadecimal floating-point constant: " 118 "expected at least one significand digit"); 119 120 // Make sure we do have some kind of proper exponent part 121 if (*CurPtr != 'p' && *CurPtr != 'P') 122 return ReturnError(TokStart, "invalid hexadecimal floating-point constant: " 123 "expected exponent part 'p'"); 124 ++CurPtr; 125 126 if (*CurPtr == '+' || *CurPtr == '-') 127 ++CurPtr; 128 129 // N.b. exponent digits are *not* hex 130 const char *ExpStart = CurPtr; 131 while (isdigit(*CurPtr)) 132 ++CurPtr; 133 134 if (CurPtr == ExpStart) 135 return ReturnError(TokStart, "invalid hexadecimal floating-point constant: " 136 "expected at least one exponent digit"); 137 138 return AsmToken(AsmToken::Real, StringRef(TokStart, CurPtr - TokStart)); 139 } 140 141 /// LexIdentifier: [a-zA-Z_.][a-zA-Z0-9_$.@?]* 142 static bool IsIdentifierChar(char c, bool AllowAt) { 143 return isalnum(c) || c == '_' || c == '$' || c == '.' || 144 (c == '@' && AllowAt) || c == '?'; 145 } 146 AsmToken AsmLexer::LexIdentifier() { 147 // Check for floating point literals. 148 if (CurPtr[-1] == '.' && isdigit(*CurPtr)) { 149 // Disambiguate a .1243foo identifier from a floating literal. 150 while (isdigit(*CurPtr)) 151 ++CurPtr; 152 if (*CurPtr == 'e' || *CurPtr == 'E' || 153 !IsIdentifierChar(*CurPtr, AllowAtInIdentifier)) 154 return LexFloatLiteral(); 155 } 156 157 while (IsIdentifierChar(*CurPtr, AllowAtInIdentifier)) 158 ++CurPtr; 159 160 // Handle . as a special case. 161 if (CurPtr == TokStart+1 && TokStart[0] == '.') 162 return AsmToken(AsmToken::Dot, StringRef(TokStart, 1)); 163 164 return AsmToken(AsmToken::Identifier, StringRef(TokStart, CurPtr - TokStart)); 165 } 166 167 /// LexSlash: Slash: / 168 /// C-Style Comment: /* ... */ 169 AsmToken AsmLexer::LexSlash() { 170 switch (*CurPtr) { 171 case '*': 172 break; // C style comment. 173 case '/': 174 ++CurPtr; 175 return LexLineComment(); 176 default: 177 return AsmToken(AsmToken::Slash, StringRef(CurPtr - 1, 1)); 178 } 179 180 // C Style comment. 181 ++CurPtr; // skip the star. 182 while (1) { 183 int CurChar = getNextChar(); 184 switch (CurChar) { 185 case EOF: 186 return ReturnError(TokStart, "unterminated comment"); 187 case '*': 188 // End of the comment? 189 if (CurPtr[0] != '/') break; 190 191 ++CurPtr; // End the */. 192 return LexToken(); 193 } 194 } 195 } 196 197 /// LexLineComment: Comment: #[^\n]* 198 /// : //[^\n]* 199 AsmToken AsmLexer::LexLineComment() { 200 // FIXME: This is broken if we happen to a comment at the end of a file, which 201 // was .included, and which doesn't end with a newline. 202 int CurChar = getNextChar(); 203 while (CurChar != '\n' && CurChar != '\r' && CurChar != EOF) 204 CurChar = getNextChar(); 205 206 if (CurChar == EOF) 207 return AsmToken(AsmToken::Eof, StringRef(TokStart, 0)); 208 return AsmToken(AsmToken::EndOfStatement, StringRef(TokStart, 0)); 209 } 210 211 static void SkipIgnoredIntegerSuffix(const char *&CurPtr) { 212 // Skip ULL, UL, U, L and LL suffices. 213 if (CurPtr[0] == 'U') 214 ++CurPtr; 215 if (CurPtr[0] == 'L') 216 ++CurPtr; 217 if (CurPtr[0] == 'L') 218 ++CurPtr; 219 } 220 221 // Look ahead to search for first non-hex digit, if it's [hH], then we treat the 222 // integer as a hexadecimal, possibly with leading zeroes. 223 static unsigned doLookAhead(const char *&CurPtr, unsigned DefaultRadix) { 224 const char *FirstHex = nullptr; 225 const char *LookAhead = CurPtr; 226 while (1) { 227 if (isdigit(*LookAhead)) { 228 ++LookAhead; 229 } else if (isxdigit(*LookAhead)) { 230 if (!FirstHex) 231 FirstHex = LookAhead; 232 ++LookAhead; 233 } else { 234 break; 235 } 236 } 237 bool isHex = *LookAhead == 'h' || *LookAhead == 'H'; 238 CurPtr = isHex || !FirstHex ? LookAhead : FirstHex; 239 if (isHex) 240 return 16; 241 return DefaultRadix; 242 } 243 244 static AsmToken intToken(StringRef Ref, APInt &Value) 245 { 246 if (Value.isIntN(64)) 247 return AsmToken(AsmToken::Integer, Ref, Value); 248 return AsmToken(AsmToken::BigNum, Ref, Value); 249 } 250 251 /// LexDigit: First character is [0-9]. 252 /// Local Label: [0-9][:] 253 /// Forward/Backward Label: [0-9][fb] 254 /// Binary integer: 0b[01]+ 255 /// Octal integer: 0[0-7]+ 256 /// Hex integer: 0x[0-9a-fA-F]+ or [0x]?[0-9][0-9a-fA-F]*[hH] 257 /// Decimal integer: [1-9][0-9]* 258 AsmToken AsmLexer::LexDigit() { 259 // Decimal integer: [1-9][0-9]* 260 if (CurPtr[-1] != '0' || CurPtr[0] == '.') { 261 unsigned Radix = doLookAhead(CurPtr, 10); 262 bool isHex = Radix == 16; 263 // Check for floating point literals. 264 if (!isHex && (*CurPtr == '.' || *CurPtr == 'e')) { 265 ++CurPtr; 266 return LexFloatLiteral(); 267 } 268 269 StringRef Result(TokStart, CurPtr - TokStart); 270 271 APInt Value(128, 0, true); 272 if (Result.getAsInteger(Radix, Value)) 273 return ReturnError(TokStart, !isHex ? "invalid decimal number" : 274 "invalid hexdecimal number"); 275 276 // Consume the [bB][hH]. 277 if (Radix == 2 || Radix == 16) 278 ++CurPtr; 279 280 // The darwin/x86 (and x86-64) assembler accepts and ignores type 281 // suffices on integer literals. 282 SkipIgnoredIntegerSuffix(CurPtr); 283 284 return intToken(Result, Value); 285 } 286 287 if ((*CurPtr == 'b') || (*CurPtr == 'B')) { 288 ++CurPtr; 289 // See if we actually have "0b" as part of something like "jmp 0b\n" 290 if (!isdigit(CurPtr[0])) { 291 --CurPtr; 292 StringRef Result(TokStart, CurPtr - TokStart); 293 return AsmToken(AsmToken::Integer, Result, 0); 294 } 295 const char *NumStart = CurPtr; 296 while (CurPtr[0] == '0' || CurPtr[0] == '1') 297 ++CurPtr; 298 299 // Requires at least one binary digit. 300 if (CurPtr == NumStart) 301 return ReturnError(TokStart, "invalid binary number"); 302 303 StringRef Result(TokStart, CurPtr - TokStart); 304 305 APInt Value(128, 0, true); 306 if (Result.substr(2).getAsInteger(2, Value)) 307 return ReturnError(TokStart, "invalid binary number"); 308 309 // The darwin/x86 (and x86-64) assembler accepts and ignores ULL and LL 310 // suffixes on integer literals. 311 SkipIgnoredIntegerSuffix(CurPtr); 312 313 return intToken(Result, Value); 314 } 315 316 if ((*CurPtr == 'x') || (*CurPtr == 'X')) { 317 ++CurPtr; 318 const char *NumStart = CurPtr; 319 while (isxdigit(CurPtr[0])) 320 ++CurPtr; 321 322 // "0x.0p0" is valid, and "0x0p0" (but not "0xp0" for example, which will be 323 // diagnosed by LexHexFloatLiteral). 324 if (CurPtr[0] == '.' || CurPtr[0] == 'p' || CurPtr[0] == 'P') 325 return LexHexFloatLiteral(NumStart == CurPtr); 326 327 // Otherwise requires at least one hex digit. 328 if (CurPtr == NumStart) 329 return ReturnError(CurPtr-2, "invalid hexadecimal number"); 330 331 APInt Result(128, 0); 332 if (StringRef(TokStart, CurPtr - TokStart).getAsInteger(0, Result)) 333 return ReturnError(TokStart, "invalid hexadecimal number"); 334 335 // Consume the optional [hH]. 336 if (*CurPtr == 'h' || *CurPtr == 'H') 337 ++CurPtr; 338 339 // The darwin/x86 (and x86-64) assembler accepts and ignores ULL and LL 340 // suffixes on integer literals. 341 SkipIgnoredIntegerSuffix(CurPtr); 342 343 return intToken(StringRef(TokStart, CurPtr - TokStart), Result); 344 } 345 346 // Either octal or hexadecimal. 347 APInt Value(128, 0, true); 348 unsigned Radix = doLookAhead(CurPtr, 8); 349 bool isHex = Radix == 16; 350 StringRef Result(TokStart, CurPtr - TokStart); 351 if (Result.getAsInteger(Radix, Value)) 352 return ReturnError(TokStart, !isHex ? "invalid octal number" : 353 "invalid hexdecimal number"); 354 355 // Consume the [hH]. 356 if (Radix == 16) 357 ++CurPtr; 358 359 // The darwin/x86 (and x86-64) assembler accepts and ignores ULL and LL 360 // suffixes on integer literals. 361 SkipIgnoredIntegerSuffix(CurPtr); 362 363 return intToken(Result, Value); 364 } 365 366 /// LexSingleQuote: Integer: 'b' 367 AsmToken AsmLexer::LexSingleQuote() { 368 int CurChar = getNextChar(); 369 370 if (CurChar == '\\') 371 CurChar = getNextChar(); 372 373 if (CurChar == EOF) 374 return ReturnError(TokStart, "unterminated single quote"); 375 376 CurChar = getNextChar(); 377 378 if (CurChar != '\'') 379 return ReturnError(TokStart, "single quote way too long"); 380 381 // The idea here being that 'c' is basically just an integral 382 // constant. 383 StringRef Res = StringRef(TokStart,CurPtr - TokStart); 384 long long Value; 385 386 if (Res.startswith("\'\\")) { 387 char theChar = Res[2]; 388 switch (theChar) { 389 default: Value = theChar; break; 390 case '\'': Value = '\''; break; 391 case 't': Value = '\t'; break; 392 case 'n': Value = '\n'; break; 393 case 'b': Value = '\b'; break; 394 } 395 } else 396 Value = TokStart[1]; 397 398 return AsmToken(AsmToken::Integer, Res, Value); 399 } 400 401 402 /// LexQuote: String: "..." 403 AsmToken AsmLexer::LexQuote() { 404 int CurChar = getNextChar(); 405 // TODO: does gas allow multiline string constants? 406 while (CurChar != '"') { 407 if (CurChar == '\\') { 408 // Allow \", etc. 409 CurChar = getNextChar(); 410 } 411 412 if (CurChar == EOF) 413 return ReturnError(TokStart, "unterminated string constant"); 414 415 CurChar = getNextChar(); 416 } 417 418 return AsmToken(AsmToken::String, StringRef(TokStart, CurPtr - TokStart)); 419 } 420 421 StringRef AsmLexer::LexUntilEndOfStatement() { 422 TokStart = CurPtr; 423 424 while (!isAtStartOfComment(CurPtr) && // Start of line comment. 425 !isAtStatementSeparator(CurPtr) && // End of statement marker. 426 *CurPtr != '\n' && *CurPtr != '\r' && 427 (*CurPtr != 0 || CurPtr != CurBuf.end())) { 428 ++CurPtr; 429 } 430 return StringRef(TokStart, CurPtr-TokStart); 431 } 432 433 StringRef AsmLexer::LexUntilEndOfLine() { 434 TokStart = CurPtr; 435 436 while (*CurPtr != '\n' && *CurPtr != '\r' && 437 (*CurPtr != 0 || CurPtr != CurBuf.end())) { 438 ++CurPtr; 439 } 440 return StringRef(TokStart, CurPtr-TokStart); 441 } 442 443 size_t AsmLexer::peekTokens(MutableArrayRef<AsmToken> Buf, 444 bool ShouldSkipSpace) { 445 const char *SavedTokStart = TokStart; 446 const char *SavedCurPtr = CurPtr; 447 bool SavedAtStartOfLine = isAtStartOfLine; 448 bool SavedSkipSpace = SkipSpace; 449 450 std::string SavedErr = getErr(); 451 SMLoc SavedErrLoc = getErrLoc(); 452 453 SkipSpace = ShouldSkipSpace; 454 455 size_t ReadCount; 456 for (ReadCount = 0; ReadCount < Buf.size(); ++ReadCount) { 457 AsmToken Token = LexToken(); 458 459 Buf[ReadCount] = Token; 460 461 if (Token.is(AsmToken::Eof)) 462 break; 463 } 464 465 SetError(SavedErrLoc, SavedErr); 466 467 SkipSpace = SavedSkipSpace; 468 isAtStartOfLine = SavedAtStartOfLine; 469 CurPtr = SavedCurPtr; 470 TokStart = SavedTokStart; 471 472 return ReadCount; 473 } 474 475 bool AsmLexer::isAtStartOfComment(const char *Ptr) { 476 const char *CommentString = MAI.getCommentString(); 477 478 if (CommentString[1] == '\0') 479 return CommentString[0] == Ptr[0]; 480 481 // FIXME: special case for the bogus "##" comment string in X86MCAsmInfoDarwin 482 if (CommentString[1] == '#') 483 return CommentString[0] == Ptr[0]; 484 485 return strncmp(Ptr, CommentString, strlen(CommentString)) == 0; 486 } 487 488 bool AsmLexer::isAtStatementSeparator(const char *Ptr) { 489 return strncmp(Ptr, MAI.getSeparatorString(), 490 strlen(MAI.getSeparatorString())) == 0; 491 } 492 493 AsmToken AsmLexer::LexToken() { 494 TokStart = CurPtr; 495 // This always consumes at least one character. 496 int CurChar = getNextChar(); 497 498 if (isAtStartOfComment(TokStart)) { 499 // If this comment starts with a '#', then return the Hash token and let 500 // the assembler parser see if it can be parsed as a cpp line filename 501 // comment. We do this only if we are at the start of a line. 502 if (CurChar == '#' && isAtStartOfLine) 503 return AsmToken(AsmToken::Hash, StringRef(TokStart, 1)); 504 isAtStartOfLine = true; 505 return LexLineComment(); 506 } 507 if (isAtStatementSeparator(TokStart)) { 508 CurPtr += strlen(MAI.getSeparatorString()) - 1; 509 return AsmToken(AsmToken::EndOfStatement, 510 StringRef(TokStart, strlen(MAI.getSeparatorString()))); 511 } 512 513 // If we're missing a newline at EOF, make sure we still get an 514 // EndOfStatement token before the Eof token. 515 if (CurChar == EOF && !isAtStartOfLine) { 516 isAtStartOfLine = true; 517 return AsmToken(AsmToken::EndOfStatement, StringRef(TokStart, 1)); 518 } 519 520 isAtStartOfLine = false; 521 switch (CurChar) { 522 default: 523 // Handle identifier: [a-zA-Z_.][a-zA-Z0-9_$.@]* 524 if (isalpha(CurChar) || CurChar == '_' || CurChar == '.') 525 return LexIdentifier(); 526 527 // Unknown character, emit an error. 528 return ReturnError(TokStart, "invalid character in input"); 529 case EOF: return AsmToken(AsmToken::Eof, StringRef(TokStart, 0)); 530 case 0: 531 case ' ': 532 case '\t': 533 if (SkipSpace) { 534 // Ignore whitespace. 535 return LexToken(); 536 } else { 537 int len = 1; 538 while (*CurPtr==' ' || *CurPtr=='\t') { 539 CurPtr++; 540 len++; 541 } 542 return AsmToken(AsmToken::Space, StringRef(TokStart, len)); 543 } 544 case '\n': // FALL THROUGH. 545 case '\r': 546 isAtStartOfLine = true; 547 return AsmToken(AsmToken::EndOfStatement, StringRef(TokStart, 1)); 548 case ':': return AsmToken(AsmToken::Colon, StringRef(TokStart, 1)); 549 case '+': return AsmToken(AsmToken::Plus, StringRef(TokStart, 1)); 550 case '-': return AsmToken(AsmToken::Minus, StringRef(TokStart, 1)); 551 case '~': return AsmToken(AsmToken::Tilde, StringRef(TokStart, 1)); 552 case '(': return AsmToken(AsmToken::LParen, StringRef(TokStart, 1)); 553 case ')': return AsmToken(AsmToken::RParen, StringRef(TokStart, 1)); 554 case '[': return AsmToken(AsmToken::LBrac, StringRef(TokStart, 1)); 555 case ']': return AsmToken(AsmToken::RBrac, StringRef(TokStart, 1)); 556 case '{': return AsmToken(AsmToken::LCurly, StringRef(TokStart, 1)); 557 case '}': return AsmToken(AsmToken::RCurly, StringRef(TokStart, 1)); 558 case '*': return AsmToken(AsmToken::Star, StringRef(TokStart, 1)); 559 case ',': return AsmToken(AsmToken::Comma, StringRef(TokStart, 1)); 560 case '$': return AsmToken(AsmToken::Dollar, StringRef(TokStart, 1)); 561 case '@': return AsmToken(AsmToken::At, StringRef(TokStart, 1)); 562 case '\\': return AsmToken(AsmToken::BackSlash, StringRef(TokStart, 1)); 563 case '=': 564 if (*CurPtr == '=') { 565 ++CurPtr; 566 return AsmToken(AsmToken::EqualEqual, StringRef(TokStart, 2)); 567 } 568 return AsmToken(AsmToken::Equal, StringRef(TokStart, 1)); 569 case '|': 570 if (*CurPtr == '|') { 571 ++CurPtr; 572 return AsmToken(AsmToken::PipePipe, StringRef(TokStart, 2)); 573 } 574 return AsmToken(AsmToken::Pipe, StringRef(TokStart, 1)); 575 case '^': return AsmToken(AsmToken::Caret, StringRef(TokStart, 1)); 576 case '&': 577 if (*CurPtr == '&') { 578 ++CurPtr; 579 return AsmToken(AsmToken::AmpAmp, StringRef(TokStart, 2)); 580 } 581 return AsmToken(AsmToken::Amp, StringRef(TokStart, 1)); 582 case '!': 583 if (*CurPtr == '=') { 584 ++CurPtr; 585 return AsmToken(AsmToken::ExclaimEqual, StringRef(TokStart, 2)); 586 } 587 return AsmToken(AsmToken::Exclaim, StringRef(TokStart, 1)); 588 case '%': return AsmToken(AsmToken::Percent, StringRef(TokStart, 1)); 589 case '/': return LexSlash(); 590 case '#': return AsmToken(AsmToken::Hash, StringRef(TokStart, 1)); 591 case '\'': return LexSingleQuote(); 592 case '"': return LexQuote(); 593 case '0': case '1': case '2': case '3': case '4': 594 case '5': case '6': case '7': case '8': case '9': 595 return LexDigit(); 596 case '<': 597 switch (*CurPtr) { 598 case '<': 599 ++CurPtr; 600 return AsmToken(AsmToken::LessLess, StringRef(TokStart, 2)); 601 case '=': 602 ++CurPtr; 603 return AsmToken(AsmToken::LessEqual, StringRef(TokStart, 2)); 604 case '>': 605 ++CurPtr; 606 return AsmToken(AsmToken::LessGreater, StringRef(TokStart, 2)); 607 default: 608 return AsmToken(AsmToken::Less, StringRef(TokStart, 1)); 609 } 610 case '>': 611 switch (*CurPtr) { 612 case '>': 613 ++CurPtr; 614 return AsmToken(AsmToken::GreaterGreater, StringRef(TokStart, 2)); 615 case '=': 616 ++CurPtr; 617 return AsmToken(AsmToken::GreaterEqual, StringRef(TokStart, 2)); 618 default: 619 return AsmToken(AsmToken::Greater, StringRef(TokStart, 1)); 620 } 621 622 // TODO: Quoted identifiers (objc methods etc) 623 // local labels: [0-9][:] 624 // Forward/backward labels: [0-9][fb] 625 // Integers, fp constants, character constants. 626 } 627 } 628