number method
void
number()
Scans a number literal (integer or floating-point).
Implementation
void number() {
start = current; // Ensure start is set correctly before advancing
bool startsWithDot = peek() == '.';
bool isFloat = startsWithDot;
bool hasExponent = false;
bool hasFractionalDigits = false;
// 1. Consume leading dot if present
if (startsWithDot) {
advance(); // Consume '.'
// Must be followed by digits *or* 'e'/'E' eventually for validity check later
if (!isDigit(peek()) && peek().toLowerCase() != 'e') {
// This was just a DOT token
current = start + 1; // Reset position to only include the dot
addToken(TokenType.DOT);
return; // Not a number
}
}
// 2. Consume integer part (if not starting with dot)
if (!startsWithDot) {
// --- Handle Prefixed Integers (0x, 0b, 0o) ---
if (peek() == '0' && current == start && current + 1 < source.length) {
String nextChar = source[current + 1].toLowerCase();
if (nextChar == 'x' || nextChar == 'b' || nextChar == 'o') {
advance(); // Consume '0'
advance(); // Consume 'x', 'b', or 'o'
_scanPrefixedInteger(nextChar);
return; // Prefixed integer handled
}
// Fall through for '0.' or '0' followed by other digits (decimal)
}
// --- Consume Decimal Integer Part ---
while (isDigit(peek())) {
advance();
}
}
// 3. Consume optional fractional part
// This runs if we didn't start with a dot AND a dot follows the integer part,
// OR if we *did* start with a dot (because we need to consume the digits after it).
if (peek() == '.') {
if (startsWithDot) {
// We already consumed the starting dot. This would be a second dot.
throw LexerError(
line,
currentColumn(),
"Invalid syntax: multiple decimal points in number.",
);
}
isFloat = true;
advance(); // Consume '.'
}
if (isFloat) {
// Consume digits if it's a float (either startsWithDot or dotSeenAfterInteger)
int fractionStart = current;
while (isDigit(peek())) {
advance();
}
if (current > fractionStart) {
hasFractionalDigits = true;
}
// If startsWithDot is true, we MUST have found some fractional digits OR an exponent later
if (startsWithDot &&
!hasFractionalDigits &&
peek().toLowerCase() != 'e') {
// Example: "." followed by non-digit, non-'e'
throw LexerError(
line,
start - lineStart + 1,
"Invalid number format: lone decimal point.",
);
}
}
// 4. Consume optional exponent part
if (peek().toLowerCase() == 'e') {
// Ensure something came before 'e'
if (current == start || (startsWithDot && !hasFractionalDigits)) {
// Handles cases like "e5" or ".e5"
throw LexerError(
line,
start - lineStart + 1,
"Invalid number format: exponent must follow digits or decimal point with digits.",
);
}
isFloat = true;
hasExponent = true;
advance(); // Consume 'e' or 'E'
if (peek() == '+' || peek() == '-') {
advance();
} // Optional sign
int exponentStart = current;
while (isDigit(peek())) {
advance();
}
if (current == exponentStart) {
throw LexerError(
line,
currentColumn(),
"Invalid scientific notation: exponent lacks digits.",
);
}
}
// 5. Check for invalid characters after number
if (!isAtEnd() && !" \n\t\r+-*/%<>=&|~^!:()[]{},#".contains(peek())) {
throw LexerError(line, currentColumn(), "Invalid decimal literal.");
}
// --- Parse and validate the Literal Value ---
String numberString = source.substring(start, current);
// Python allows "1." or ".5" but not just "."
// Our logic now prevents just "." from reaching here.
// Validate ".e5" - should fail because no digits after initial dot before 'e'
if (startsWithDot && !hasFractionalDigits && !hasExponent) {
// This means we only parsed "." which should have been handled earlier.
// This code path likely indicates an error in the logic above.
// However, let's treat it as just a DOT token if it occurs.
current = start + 1; // Reset current to only include the dot
addToken(TokenType.DOT);
return;
}
Object literalValue;
try {
// If it has '.', 'e', or started with '.', treat as float
if (isFloat) {
literalValue = PyNum.double(double.parse(numberString));
} else {
literalValue = PyNum.bigInt(BigInt.parse(numberString));
}
} catch (e) {
String formatType = isFloat ? "floating-point" : "integer";
throw LexerError(
line,
start - lineStart + 1,
"Invalid $formatType number format: '$numberString'",
);
}
addToken(TokenType.NUMBER, literalValue);
}