Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 41 additions & 45 deletions toml/_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,32 +261,6 @@ function or<T extends readonly ParserComponent<any>[]>(
};
}

/** Join the parse results of the given parser into an array.
*
* If the parser fails at the first attempt, it will return an empty array.
*/
function join<T>(
parser: ParserComponent<T>,
separator: string,
): ParserComponent<T[]> {
const Separator = character(separator);
return (scanner: Scanner): ParseResult<T[]> => {
const out: T[] = [];
const first = parser(scanner);
if (!first.ok) return success(out);
out.push(first.body);
while (!scanner.eof()) {
if (!Separator(scanner).ok) break;
const result = parser(scanner);
if (!result.ok) {
throw new SyntaxError(`Invalid token after "${separator}"`);
}
out.push(result.body);
}
return success(out);
};
}

/** Join the parse results of the given parser into an array.
*
* This requires the parser to succeed at least once.
Expand Down Expand Up @@ -421,8 +395,9 @@ export function bareKey(scanner: Scanner): ParseResult<string> {
function escapeSequence(scanner: Scanner): ParseResult<string> {
if (scanner.char() !== "\\") return failure();
scanner.next();
// See https://toml.io/en/v1.0.0-rc.3#string
switch (scanner.char()) {
// See https://toml.io/en/v1.1.0#string
const char = scanner.char();
switch (char) {
case "b":
scanner.next();
return success("\b");
Expand All @@ -438,11 +413,15 @@ function escapeSequence(scanner: Scanner): ParseResult<string> {
case "r":
scanner.next();
return success("\r");
case "e":
scanner.next();
return success("\x1B");
case "x":
case "u":
case "U": {
// Unicode character
const codePointLen = scanner.char() === "u" ? 4 : 6;
const codePoint = parseInt("0x" + scanner.slice(1, 1 + codePointLen), 16);
const codePointLen = { "x": 2, "u": 4, "U": 8 }[char];
const codePoint = Number("0x" + scanner.slice(1, 1 + codePointLen));
const str = String.fromCodePoint(codePoint);
scanner.next(codePointLen + 1);
return success(str);
Expand All @@ -455,7 +434,7 @@ function escapeSequence(scanner: Scanner): ParseResult<string> {
return success("\\");
default:
throw new SyntaxError(
`Invalid escape sequence: \\${scanner.char()}`,
`Invalid escape sequence: \\${char}`,
);
}
}
Expand Down Expand Up @@ -712,14 +691,18 @@ export function dateTime(scanner: Scanner): ParseResult<Date> {
return success(date);
}

const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2}):(\d{2})(?:\.[0-9]+)?\b/y;
const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2})(:(\d{2})(?:\.[0-9]+)?)?\b/y;
export function localTime(scanner: Scanner): ParseResult<string> {
scanner.skipWhitespaces();

const match = scanner.match(LOCAL_TIME_REGEXP)?.[0];
if (!match) return failure();
scanner.next(match.length);
return success(match);
const string = scanner.match(LOCAL_TIME_REGEXP)?.[0];
if (!string) return failure();
scanner.next(string.length);
// seconds are omitted
if (string.length == 5) {
return success(`${string}:00`);
}
return success(string);
}

export function arrayValue(scanner: Scanner): ParseResult<unknown[]> {
Expand Down Expand Up @@ -750,17 +733,30 @@ export function arrayValue(scanner: Scanner): ParseResult<unknown[]> {
export function inlineTable(
scanner: Scanner,
): ParseResult<Record<string, unknown>> {
scanner.nextUntilChar();
if (scanner.char(1) === "}") {
scanner.next(2);
return success({ __proto__: null });
}
const pairs = surround("{", join(pair, ","), "}")(scanner);
if (!pairs.ok) return failure();
scanner.skipWhitespaces();

if (scanner.char() !== "{") return failure();
scanner.next();

let table = { __proto__: null } as Record<string, unknown>;
for (const pair of pairs.body) {
table = deepMerge(table, pair);
while (!scanner.eof()) {
scanner.nextUntilChar();
const result = pair(scanner);
if (!result.ok) break;
table = deepMerge(table, result.body);
scanner.skipWhitespaces();
// may have a next item, but trailing comma is allowed in inline tables
if (scanner.char() !== ",") break;
scanner.next();
}

scanner.nextUntilChar();

if (scanner.char() !== "}") {
throw new SyntaxError("Inline Table is not closed");
}
scanner.next();

return success(table);
}

Expand Down
27 changes: 22 additions & 5 deletions toml/_parser_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ Deno.test({
fn() {
const parse = parserFactory(basicString);
assertEquals(
parse('"a\\"\\n\\t\\b\\\\\\u3042\\U01F995"'),
'a"\n\t\b\\\あ🦕',
parse('"a\\"\\n\\t\\b\\e\\\\\\xAE\\u3042\\U0001F995"'),
'a"\n\t\b\x1B\\\®あ🦕',
);
assertEquals(parse('""'), "");
assertEquals(parse('"a\\n"'), "a\n");
Expand Down Expand Up @@ -366,6 +366,10 @@ Deno.test({
name: "parse() handles date and date time",
fn() {
const parse = parserFactory(dateTime);
assertEquals(
parse("1979-05-27T07:32Z"),
new Date("1979-05-27T07:32:00Z"),
);
assertEquals(
parse("1979-05-27T07:32:00Z"),
new Date("1979-05-27T07:32:00Z"),
Expand All @@ -382,11 +386,22 @@ Deno.test({
parse("1979-05-27T00:32:00.999999-07:00"),
new Date("1979-05-27T07:32:00.999Z"),
);
assertEquals(
parse("1979-05-27 07:32Z"),
new Date("1979-05-27T07:32:00Z"),
);
assertEquals(
parse("1979-05-27 07:32:00Z"),
new Date("1979-05-27T07:32:00Z"),
);
assertEquals(parse("1979-05-27T07:32:00"), new Date("1979-05-27T07:32:00"));
assertEquals(
parse("1979-05-27T07:32"),
new Date("1979-05-27T07:32:00"),
);
assertEquals(
parse("1979-05-27T07:32:00"),
new Date("1979-05-27T07:32:00"),
);
assertEquals(
parse("1979-05-27T00:32:00.999999"),
new Date("1979-05-27T00:32:00.999999"),
Expand All @@ -406,6 +421,7 @@ Deno.test({
name: "parse() handles local time",
fn() {
const parse = parserFactory(localTime);
assertEquals(parse("07:32"), "07:32:00");
assertEquals(parse("07:32:00"), "07:32:00");
assertEquals(parse("07:32:00.999"), "07:32:00.999");
assertThrows(() => parse(""));
Expand All @@ -419,6 +435,7 @@ Deno.test({
assertEquals(parse("1"), 1);
assertEquals(parse("1.2"), 1.2);
assertEquals(parse("1979-05-27"), new Date("1979-05-27"));
assertEquals(parse("07:32"), "07:32:00");
assertEquals(parse("07:32:00"), "07:32:00");
assertEquals(parse(`"foo.com"`), "foo.com");
assertEquals(parse(`'foo.com'`), "foo.com");
Expand Down Expand Up @@ -480,9 +497,9 @@ Deno.test({
last: "Preston-Werner",
});
assertEquals(parse(`{ type.name = "pug" }`), { type: { name: "pug" } });
assertEquals(parse(`{ x = 1,\n y = 2 }`), { x: 1, y: 2 });
assertEquals(parse(`{ x = 1, }`), { x: 1 });
assertThrows(() => parse(`{ x = 1`));
assertThrows(() => parse(`{ x = 1,\n y = 2 }`));
assertThrows(() => parse(`{ x = 1, }`));
},
});

Expand Down
2 changes: 1 addition & 1 deletion toml/official_parse_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { convertTestCase, type TestCase } from "./_test_utils.ts";
import { parse } from "./parse.ts";

const testCases = await Deno.readTextFile(
new URL("./testdata/files-toml-1.0.0", import.meta.url),
new URL("./testdata/files-toml-1.1.0", import.meta.url),
);

const ignored = [
Expand Down
2 changes: 1 addition & 1 deletion toml/parse_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ withApostrophe = "What if it's not?"
withSemicolon = "const message = 'hello world';"
withHexNumberLiteral = "Prevent bug from stripping string here ->0xabcdef"
withUnicodeChar1 = "\\u3042"
withUnicodeChar2 = "Deno\\U01F995"
withUnicodeChar2 = "Deno\\U0001F995"
`);
assertEquals(actual, expected);
},
Expand Down
4 changes: 2 additions & 2 deletions toml/testdata/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
This directory contains the test cases copied from the official
[toml-lang/toml-test](https://github.com/toml-lang/toml-test) repository at
[`1d35870ef6783d86366ba55d7df703f3f60b3b55`](https://github.com/toml-lang/toml-test/tree/1d35870ef6783d86366ba55d7df703f3f60b3b55).
[`v2.1.0`](https://github.com/toml-lang/toml-test/tree/v2.1.0).

The license of the copied data is as follows.

https://github.com/toml-lang/toml-test/blob/1d35870ef6783d86366ba55d7df703f3f60b3b55/LICENSE
https://github.com/toml-lang/toml-test/blob/v2.1.0/LICENSE

```
The MIT License (MIT)
Expand Down
Loading
Loading