Allow leading +/- in numeric literals and require fractional digits (#503)

This commit is contained in:
Nick Guerrera 2021-04-28 08:02:54 -07:00 коммит произвёл GitHub
Родитель 1da60d0730
Коммит b93b561fc4
4 изменённых файлов: 92 добавлений и 52 удалений

Просмотреть файл

@ -65,13 +65,16 @@ BooleanLiteral :
NumericLiteral :
DecimalLiteral
HexIntegerLiteral
BinaryIntegerLiteral
DecimalLiteral :
DecimalIntegerLiteral `.` DecimalDigits? ExponentPart?
DecimalIntegerLiteral `.` DecimalDigits ExponentPart?
DecimalIntegerLiteral ExponentPart?
DecimalIntegerLiteral :
DecimalDigits
`+` DecimalDigits
`-` DecimalDigits
DecimalDigits :
DecimalDigit
@ -81,9 +84,9 @@ DecimalDigit :
one of `0` `1` `2` `3` `4` `5` `6` `7` `8` `9`
ExponentPart :
`e` SignedInteger
`e` DecimalIntegerLiteral
SignedInteger :
DecimalIntegerInteger :
DecimalDigits
`+` DecimalDigits
`-` DecimalDigits

Просмотреть файл

@ -332,6 +332,10 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
return scanInvalidCharacter();
case CharCode.Plus:
case CharCode.Minus:
return isDigit(lookAhead(1)) ? scanSignedNumber() : scanInvalidCharacter();
case CharCode._0:
switch (lookAhead(1)) {
case CharCode.x:
@ -447,17 +451,24 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
return (token = Token.Whitespace);
}
function scanSignedNumber() {
position++; // consume '+/-'
return scanNumber();
}
function scanNumber() {
scanKnownDigits();
if (!eof()) {
switch (input.charCodeAt(position)) {
case CharCode.Dot:
scanFractionAndExponent();
break;
case CharCode.e:
scanExponent();
break;
if (!eof() && input.charCodeAt(position) === CharCode.Dot) {
position++;
scanRequiredDigits();
}
if (!eof() && input.charCodeAt(position) === CharCode.e) {
position++;
const ch = input.charCodeAt(position);
if (ch === CharCode.Plus || ch === CharCode.Minus) {
position++;
}
scanRequiredDigits();
}
return (token = Token.NumericLiteral);
}
@ -468,12 +479,6 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
} while (!eof() && isDigit(input.charCodeAt(position)));
}
function scanOptionalDigits() {
if (!eof() && isDigit(input.charCodeAt(position))) {
scanKnownDigits();
}
}
function scanRequiredDigits() {
if (eof() || !isDigit(input.charCodeAt(position))) {
error(Message.DigitExpected);
@ -482,27 +487,6 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
scanKnownDigits();
}
function scanFractionAndExponent() {
position++; // consume '.'
scanOptionalDigits();
if (!eof() && input.charCodeAt(position) === CharCode.e) {
scanExponent();
}
}
function scanExponent() {
position++; // consume 'e'
if (eof()) {
error(Message.DigitExpected);
return;
}
const ch = input.charCodeAt(position);
if (ch === CharCode.Plus || ch === CharCode.Minus) {
position++;
}
scanRequiredDigits();
}
function scanHexNumber() {
position += 2; // consume '0x'

Просмотреть файл

@ -226,11 +226,6 @@ describe("syntax", () => {
['model X = """\nbanana', [/Unterminated string literal/]],
['model X = """\nbanana\\', [/Unterminated string literal/]],
["/* Yada yada yada", [/Unterminated comment/]],
["123.0e", [/Digit expected/]],
["123.e", [/Digit expected/]],
["123e", [/Digit expected/]],
["0b", [/Binary digit expected/]],
["0x", [/Hexadecimal digit expected/]],
]);
});
@ -239,7 +234,6 @@ describe("syntax", () => {
["model X = 0x10101", [/';' expected/]],
["model X = 0xBEEF", [/';' expected/]],
["model X = 123", [/';' expected/]],
["model X = 123.", [/';' expected/]],
["model X = 123e45", [/';' expected/]],
["model X = 123.45", [/';' expected/]],
["model X = 123.45e2", [/';' expected/]],
@ -249,6 +243,65 @@ describe("syntax", () => {
]);
});
describe("numeric literals", () => {
const good: [string, number][] = [
// Some questions remain here: https://github.com/Azure/adl/issues/506
["-0", -0],
["1e9999", Infinity],
["1e-9999", 0],
["-1e-9999", -0],
["-1e9999", -Infinity],
// NOTE: No octal in ADL
["077", 77],
["+077", 77],
["-077", -77],
["0xABCD", 0xabcd],
["0xabcd", 0xabcd],
["0x1010", 0x1010],
["0b1010", 0b1010],
["0", 0],
["+0", 0],
["0.0", 0.0],
["+0.0", 0],
["-0.0", -0.0],
["123", 123],
["+123", 123],
["-123", -123],
["123.123", 123.123],
["+123.123", 123.123],
["-123.123", -123.123],
["789e42", 789e42],
["+789e42", 789e42],
["-789e42", -789e42],
["654.321e9", 654.321e9],
["+654.321e9", 654.321e9],
["-654.321e9", -654.321e9],
];
const bad: [string, RegExp][] = [
["123.", /Digit expected/],
["123.0e", /Digit expected/],
["123e", /Digit expected/],
["0b", /Binary digit expected/],
["0b2", /Binary digit expected/],
["0x", /Hexadecimal digit expected/],
["0xG", /Hexadecimal digit expected/],
];
parseEach(good.map((c) => [`model M = ${c[0]};`, (node) => isNumericLiteral(node, c[1])]));
parseErrorEach(bad.map((c) => [`model M = ${c[0]};`, [c[1]]]));
function isNumericLiteral(node: ADLScriptNode, value: number) {
const statement = node.statements[0];
assert(statement.kind === SyntaxKind.ModelStatement, "model statement expected");
const assignment = statement.assignment;
assert(assignment?.kind === SyntaxKind.NumericLiteral, "numeric literal expected");
assert.strictEqual(assignment.value, value);
}
});
describe("non-ascii identifiers", () => {
parseEach([
"model Incompréhensible {}",
@ -261,14 +314,19 @@ describe("syntax", () => {
});
});
function parseEach(cases: string[]) {
for (const code of cases) {
function parseEach(cases: (string | [string, (node: ADLScriptNode) => void])[]) {
for (const each of cases) {
const code = typeof each === "string" ? each : each[0];
const callback = typeof each === "string" ? undefined : each[1];
it("parses `" + shorten(code) + "`", () => {
logVerboseTestOutput("=== Source ===");
logVerboseTestOutput(code);
logVerboseTestOutput("\n=== Parse Result ===");
const astNode = parse(code);
if (callback) {
callback(astNode);
}
dumpAST(astNode);
logVerboseTestOutput("\n=== Diagnostics ===");

Просмотреть файл

@ -159,7 +159,7 @@ describe("scanner", () => {
});
it("scans numeric literals", () => {
const all = tokens("42 0xBEEF 0b1010 1.5e4 314.0e-2 1e+1000 3. 2.e3");
const all = tokens("42 0xBEEF 0b1010 1.5e4 314.0e-2 1e+1000");
verify(all, [
[Token.NumericLiteral, "42"],
[Token.Whitespace],
@ -172,11 +172,6 @@ describe("scanner", () => {
[Token.NumericLiteral, "314.0e-2"],
[Token.Whitespace],
[Token.NumericLiteral, "1e+1000"],
[Token.Whitespace],
// https://github.com/Azure/adl/issues/488 - we may want to disallow these
[Token.NumericLiteral, "3."],
[Token.Whitespace],
[Token.NumericLiteral, "2.e3"],
]);
});