Allow leading +/- in numeric literals and require fractional digits (#503)
This commit is contained in:
Родитель
1da60d0730
Коммит
b93b561fc4
|
@ -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"],
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче