Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f21bd46

Browse filesBrowse files
committed
fix lexing nested sequences/mappings
1 parent 1f46250 commit f21bd46
Copy full SHA for f21bd46

File tree

2 files changed

+240
-41
lines changed
Filter options

2 files changed

+240
-41
lines changed

‎src/Symfony/Component/Yaml/Parser.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Yaml/Parser.php
+84-41Lines changed: 84 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ private function doParse(string $value, int $flags)
368368
}
369369

370370
try {
371-
$parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs);
371+
$parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs);
372372

373373
while ($this->moveToNextLine()) {
374374
if (!$this->isCurrentLineEmpty()) {
@@ -389,7 +389,7 @@ private function doParse(string $value, int $flags)
389389
}
390390

391391
try {
392-
$parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs);
392+
$parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs);
393393

394394
while ($this->moveToNextLine()) {
395395
if (!$this->isCurrentLineEmpty()) {
@@ -736,9 +736,13 @@ private function parseValue(string $value, int $flags, string $context)
736736

737737
try {
738738
if ('' !== $value && '{' === $value[0]) {
739-
return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs);
739+
$cursor = \strlen($this->currentLine) - \strlen($value);
740+
741+
return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs);
740742
} elseif ('' !== $value && '[' === $value[0]) {
741-
return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs);
743+
$cursor = \strlen($this->currentLine) - \strlen($value);
744+
745+
return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs);
742746
}
743747

744748
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
@@ -1183,60 +1187,99 @@ private function parseQuotedString(string $yaml): ?string
11831187
return $value;
11841188
}
11851189

1186-
private function lexInlineMapping(string $yaml): string
1190+
private function lexInlineQuotedString(int &$cursor): string
11871191
{
1188-
if ('' === $yaml || '{' !== $yaml[0]) {
1189-
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
1190-
}
1191-
1192-
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) {
1193-
}
1192+
$quotedStringOffset = $cursor;
1193+
$cursor += strcspn($this->currentLine, $this->currentLine[$cursor], $cursor + 1) + 2;
11941194

1195-
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
1196-
return $yaml;
1197-
}
1198-
1199-
$lines = [$yaml];
1195+
return substr($this->currentLine, $quotedStringOffset, $cursor - $quotedStringOffset);
1196+
}
12001197

1201-
while ($this->moveToNextLine()) {
1202-
$lines[] = $this->currentLine;
1203-
}
1198+
private function lexUnquotedString(int &$cursor): string
1199+
{
1200+
$offset = $cursor;
1201+
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
12041202

1205-
return implode("\n", $lines);
1203+
return substr($this->currentLine, $offset, $cursor - $offset);
12061204
}
12071205

1208-
private function lexInlineSequence(string $yaml): string
1206+
private function lexInlineMapping(int &$cursor = 0): string
12091207
{
1210-
if ('' === $yaml || '[' !== $yaml[0]) {
1211-
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
1212-
}
1208+
return $this->lexInlineStructure($cursor, '}');
1209+
}
12131210

1214-
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
1215-
}
1211+
private function lexInlineSequence(int &$cursor = 0): string
1212+
{
1213+
return $this->lexInlineStructure($cursor, ']');
1214+
}
12161215

1217-
if (isset($yaml[$i]) && ']' === $yaml[$i]) {
1218-
return $yaml;
1219-
}
1216+
private function lexInlineStructure(int &$cursor, string $closingTag): string
1217+
{
1218+
$value = $this->currentLine[$cursor];
1219+
++$cursor;
12201220

1221-
$value = $yaml;
1221+
do {
1222+
$this->consumeWhitespaces($cursor);
1223+
1224+
while (isset($this->currentLine[$cursor])) {
1225+
switch ($this->currentLine[$cursor]) {
1226+
case '"':
1227+
case "'":
1228+
$value .= $this->lexInlineQuotedString($cursor);
1229+
1230+
break;
1231+
case ':':
1232+
case ',':
1233+
$value .= $this->currentLine[$cursor];
1234+
++$cursor;
1235+
1236+
break;
1237+
case '{':
1238+
$value .= $this->lexInlineMapping($cursor);
1239+
1240+
break;
1241+
case '[':
1242+
$value .= $this->lexInlineSequence($cursor);
1243+
1244+
break;
1245+
case $closingTag:
1246+
$value .= $this->currentLine[$cursor];
1247+
++$cursor;
1248+
1249+
return $value;
1250+
case '#':
1251+
break 2;
1252+
default:
1253+
$value .= $this->lexUnquotedString($cursor);
1254+
}
12221255

1223-
while ($this->moveToNextLine()) {
1224-
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
1256+
if ($this->consumeWhitespaces($cursor)) {
1257+
$value .= ' ';
1258+
}
12251259
}
12261260

1227-
$trimmedValue = trim($this->currentLine);
1261+
$cursor = 0;
1262+
} while ($this->moveToNextLine());
12281263

1229-
if ('' !== $trimmedValue && '#' === $trimmedValue[0]) {
1230-
continue;
1231-
}
1264+
return $value;
1265+
}
12321266

1233-
$value .= $trimmedValue;
1267+
private function consumeWhitespaces(int &$cursor): bool
1268+
{
1269+
$whitespacesConsumed = 0;
12341270

1235-
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
1236-
break;
1271+
do {
1272+
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
1273+
$whitespacesConsumed += $whitespaceOnlyTokenLength;
1274+
$cursor += $whitespaceOnlyTokenLength;
1275+
1276+
if (isset($this->currentLine[$cursor])) {
1277+
return 0 < $whitespacesConsumed;
12371278
}
1238-
}
12391279

1240-
return $value;
1280+
$cursor = 0;
1281+
} while ($this->moveToNextLine());
1282+
1283+
return 0 < $whitespacesConsumed;
12411284
}
12421285
}

‎src/Symfony/Component/Yaml/Tests/ParserTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Yaml/Tests/ParserTest.php
+156Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,16 @@ public function inlineNotationSpanningMultipleLinesProvider(): array
16601660
'foo': 'bar',
16611661
'bar': 'baz'
16621662
}
1663+
YAML
1664+
,
1665+
],
1666+
'mapping with unquoted strings and values' => [
1667+
['foo' => 'bar', 'bar' => 'baz'],
1668+
<<<YAML
1669+
{
1670+
foo: bar,
1671+
bar: baz
1672+
}
16631673
YAML
16641674
,
16651675
],
@@ -1673,6 +1683,53 @@ public function inlineNotationSpanningMultipleLinesProvider(): array
16731683
YAML
16741684
,
16751685
],
1686+
'sequence with unquoted items' => [
1687+
['foo', 'bar'],
1688+
<<<YAML
1689+
[
1690+
foo,
1691+
bar
1692+
]
1693+
YAML
1694+
,
1695+
],
1696+
'nested mapping terminating at end of line' => [
1697+
[
1698+
'foo' => [
1699+
'bar' => 'foobar',
1700+
],
1701+
],
1702+
<<<YAML
1703+
{ foo: { bar: foobar }
1704+
}
1705+
YAML
1706+
,
1707+
],
1708+
'nested sequence terminating at end of line' => [
1709+
[
1710+
'foo',
1711+
[
1712+
'bar',
1713+
'baz',
1714+
],
1715+
],
1716+
<<<YAML
1717+
[ foo, [bar, baz]
1718+
]
1719+
YAML
1720+
],
1721+
'nested sequence spanning multiple lines' => [
1722+
[
1723+
['entry1', []],
1724+
['entry2'],
1725+
],
1726+
<<<YAML
1727+
[
1728+
['entry1', {}],
1729+
['entry2']
1730+
]
1731+
YAML
1732+
],
16761733
'sequence nested in mapping' => [
16771734
['foo' => ['bar', 'foobar'], 'bar' => ['baz']],
16781735
<<<YAML
@@ -1699,6 +1756,22 @@ public function inlineNotationSpanningMultipleLinesProvider(): array
16991756
YAML
17001757
,
17011758
],
1759+
'sequence spanning multiple lines nested in mapping with a following mapping' => [
1760+
[
1761+
'foobar' => [
1762+
'foo',
1763+
'bar',
1764+
],
1765+
'bar' => 'baz',
1766+
],
1767+
<<<YAML
1768+
foobar: [
1769+
foo,
1770+
bar,
1771+
]
1772+
bar: baz
1773+
YAML
1774+
],
17021775
'nested sequence nested in mapping starting on the same line' => [
17031776
[
17041777
'foo' => [
@@ -1824,6 +1897,89 @@ public function inlineNotationSpanningMultipleLinesProvider(): array
18241897
foo: 'bar
18251898
18261899
baz'
1900+
YAML
1901+
],
1902+
'mixed mapping with inline notation having separated lines' => [
1903+
[
1904+
'map' => [
1905+
'key' => 'value',
1906+
'a' => 'b',
1907+
],
1908+
'param' => 'some',
1909+
],
1910+
<<<YAML
1911+
map: {
1912+
key: "value",
1913+
a: "b"
1914+
}
1915+
param: "some"
1916+
YAML
1917+
],
1918+
'mixed mapping with inline notation on one line' => [
1919+
[
1920+
'map' => [
1921+
'key' => 'value',
1922+
'a' => 'b',
1923+
],
1924+
'param' => 'some',
1925+
],
1926+
<<<YAML
1927+
map: {key: "value", a: "b"}
1928+
param: "some"
1929+
YAML
1930+
],
1931+
'mixed mapping with compact inline notation on one line' => [
1932+
[
1933+
'map' => [
1934+
'key' => 'value',
1935+
'a' => 'b',
1936+
],
1937+
'param' => 'some',
1938+
],
1939+
<<<YAML
1940+
map: {key: "value",
1941+
a: "b"}
1942+
param: "some"
1943+
YAML
1944+
],
1945+
'nested collections containing strings with bracket chars' => [
1946+
[
1947+
[']'],
1948+
['}'],
1949+
['ba[r'],
1950+
['[ba]r'],
1951+
['bar]'],
1952+
['foo' => 'bar{'],
1953+
['foo' => 'b{ar}'],
1954+
['foo' => 'bar}'],
1955+
],
1956+
<<<YAML
1957+
[
1958+
[
1959+
"]"
1960+
],
1961+
[
1962+
"}"
1963+
],
1964+
[
1965+
"ba[r"
1966+
],
1967+
[
1968+
'[ba]r'
1969+
],
1970+
[
1971+
"bar]"
1972+
],
1973+
{
1974+
foo: "bar{"
1975+
},
1976+
{
1977+
foo: "b{ar}"
1978+
},
1979+
{
1980+
foo: 'bar}'
1981+
}
1982+
]
18271983
YAML
18281984
],
18291985
];

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.