Skip to content

Commit 800a703

Browse files
committed
fix lexing nested sequences/mappings
1 parent c72f853 commit 800a703

File tree

2 files changed

+196
-41
lines changed

2 files changed

+196
-41
lines changed

src/Symfony/Component/Yaml/Parser.php

Lines changed: 123 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;
@@ -1204,60 +1208,138 @@ private function parseQuotedString(string $yaml): ?string
12041208
}
12051209
}
12061210

1207-
private function lexInlineMapping(string $yaml): string
1211+
private function lexInlineQuotedString(int &$cursor): string
12081212
{
1209-
if ('' === $yaml || '{' !== $yaml[0]) {
1210-
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
1211-
}
1212-
1213-
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) {
1214-
}
1215-
1216-
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
1217-
return $yaml;
1218-
}
1213+
$quotedStringOffset = $cursor;
1214+
$cursor += strcspn($this->currentLine, $this->currentLine[$cursor], $cursor + 1) + 2;
12191215

1220-
$lines = [$yaml];
1216+
return substr($this->currentLine, $quotedStringOffset, $cursor - $quotedStringOffset);
1217+
}
12211218

1222-
while ($this->moveToNextLine()) {
1223-
$lines[] = $this->currentLine;
1224-
}
1219+
private function lexUnquotedString(int &$cursor): string
1220+
{
1221+
$offset = $cursor;
1222+
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
12251223

1226-
return implode("\n", $lines);
1224+
return substr($this->currentLine, $offset, $cursor - $offset);
12271225
}
12281226

1229-
private function lexInlineSequence(string $yaml): string
1227+
private function lexInlineMapping(int &$cursor = 0): string
12301228
{
1231-
if ('' === $yaml || '[' !== $yaml[0]) {
1232-
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
1233-
}
1229+
$value = $this->currentLine[$cursor];
1230+
++$cursor;
12341231

1235-
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
1236-
}
1232+
do {
1233+
$this->consumeWhitespaces($cursor);
1234+
1235+
while (isset($this->currentLine[$cursor])) {
1236+
switch ($this->currentLine[$cursor]) {
1237+
case '"':
1238+
case "'":
1239+
$value .= $this->lexInlineQuotedString($cursor);
1240+
1241+
break;
1242+
case ':':
1243+
case ',':
1244+
$value .= $this->currentLine[$cursor];
1245+
++$cursor;
1246+
1247+
break;
1248+
case '{':
1249+
$value .= $this->lexInlineMapping($cursor);
1250+
1251+
break;
1252+
case '}':
1253+
$value .= '}';
1254+
++$cursor;
1255+
1256+
return $value;
1257+
case '[':
1258+
$value .= $this->lexInlineSequence($cursor);
1259+
1260+
break;
1261+
default:
1262+
$value .= $this->lexUnquotedString($cursor);
1263+
}
12371264

1238-
if (isset($yaml[$i]) && ']' === $yaml[$i]) {
1239-
return $yaml;
1240-
}
1265+
if ($this->consumeWhitespaces($cursor)) {
1266+
$value .= ' ';
1267+
}
1268+
}
1269+
1270+
$cursor = 0;
1271+
} while ($this->moveToNextLine());
12411272

1242-
$value = $yaml;
1273+
return $value;
1274+
}
12431275

1244-
while ($this->moveToNextLine()) {
1245-
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
1246-
}
1276+
private function lexInlineSequence(int &$cursor = 0): string
1277+
{
1278+
$value = $this->currentLine[$cursor];
1279+
++$cursor;
12471280

1248-
$trimmedValue = trim($this->currentLine);
1281+
do {
1282+
$this->consumeWhitespaces($cursor);
1283+
1284+
while (isset($this->currentLine[$cursor])) {
1285+
switch ($this->currentLine[$cursor]) {
1286+
case '"':
1287+
case "'":
1288+
$value .= $this->lexInlineQuotedString($cursor);
1289+
1290+
break;
1291+
case ':':
1292+
case ',':
1293+
$value .= $this->currentLine[$cursor];
1294+
++$cursor;
1295+
1296+
break;
1297+
case '[':
1298+
$value .= $this->lexInlineSequence($cursor);
1299+
1300+
break;
1301+
case ']':
1302+
$value .= ']';
1303+
++$cursor;
1304+
1305+
return $value;
1306+
case '{':
1307+
$value .= $this->lexInlineMapping($cursor);
1308+
1309+
break;
1310+
case '#':
1311+
break 2;
1312+
default:
1313+
$value .= $this->lexUnquotedString($cursor);
1314+
}
12491315

1250-
if ('' !== $trimmedValue && '#' === $trimmedValue[0]) {
1251-
continue;
1316+
if ($this->consumeWhitespaces($cursor)) {
1317+
$value .= ' ';
1318+
}
12521319
}
12531320

1254-
$value .= $trimmedValue;
1321+
$cursor = 0;
1322+
} while ($this->moveToNextLine());
12551323

1256-
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
1257-
break;
1324+
return $value;
1325+
}
1326+
1327+
private function consumeWhitespaces(int &$cursor): bool
1328+
{
1329+
$whitespacesConsumed = 0;
1330+
1331+
do {
1332+
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
1333+
$whitespacesConsumed += $whitespaceOnlyTokenLength;
1334+
$cursor += $whitespaceOnlyTokenLength;
1335+
1336+
if (isset($this->currentLine[$cursor])) {
1337+
return 0 < $whitespacesConsumed;
12581338
}
1259-
}
12601339

1261-
return $value;
1340+
$cursor = 0;
1341+
} while ($this->moveToNextLine());
1342+
1343+
return 0 < $whitespacesConsumed;
12621344
}
12631345
}

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

Lines changed: 73 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' => [

0 commit comments

Comments
 (0)