Skip to content

Commit 0e38c33

Browse files
committed
fix lexing nested sequences/mappings
1 parent 1f46250 commit 0e38c33

File tree

2 files changed

+291
-68
lines changed

2 files changed

+291
-68
lines changed

src/Symfony/Component/Yaml/Parser.php

Lines changed: 114 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ private function doParse(string $value, int $flags)
355355
}
356356

357357
try {
358-
return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs);
358+
return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs);
359359
} catch (ParseException $e) {
360360
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
361361
$e->setSnippet($this->currentLine);
@@ -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;
@@ -1137,106 +1141,148 @@ private function getLineTag(string $value, int $flags, bool $nextLineCheck = tru
11371141
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
11381142
}
11391143

1140-
private function parseQuotedString(string $yaml): ?string
1144+
private function lexInlineQuotedString(int &$cursor = 0): string
11411145
{
1142-
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) {
1143-
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml));
1144-
}
1145-
1146-
$lines = [$yaml];
1146+
$quotation = $this->currentLine[$cursor];
1147+
$value = $quotation;
1148+
++$cursor;
11471149

1148-
while ($this->moveToNextLine()) {
1149-
$lines[] = $this->currentLine;
1150+
$previousLineWasNewline = true;
1151+
$previousLineWasTerminatedWithBackslash = false;
11501152

1151-
if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) {
1152-
break;
1153-
}
1154-
}
1155-
1156-
$value = '';
1157-
1158-
for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) {
1159-
if ('' === trim($lines[$i])) {
1153+
do {
1154+
if ($this->isCurrentLineBlank()) {
11601155
$value .= "\n";
11611156
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
11621157
$value .= ' ';
11631158
}
11641159

1165-
if ('' !== trim($lines[$i]) && '\\' === substr($lines[$i], -1)) {
1166-
$value .= ltrim(substr($lines[$i], 0, -1));
1167-
} elseif ('' !== trim($lines[$i])) {
1168-
$value .= trim($lines[$i]);
1160+
for (; \strlen($this->currentLine) > $cursor; ++$cursor) {
1161+
switch ($this->currentLine[$cursor]) {
1162+
case '\\':
1163+
if (isset($this->currentLine[++$cursor])) {
1164+
$value .= '\\'.$this->currentLine[$cursor];
1165+
}
1166+
1167+
break;
1168+
case $quotation:
1169+
++$cursor;
1170+
1171+
if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) {
1172+
$value .= "''";
1173+
break;
1174+
}
1175+
1176+
return $value.$quotation;
1177+
default:
1178+
$value .= $this->currentLine[$cursor];
1179+
}
11691180
}
11701181

1171-
if ('' === trim($lines[$i])) {
1182+
if (isset($this->currentLine[$cursor]) && $this->currentLine[$cursor] === $quotation) {
1183+
++$cursor;
1184+
1185+
return $value.$quotation;
1186+
}
1187+
1188+
if ($this->isCurrentLineBlank()) {
11721189
$previousLineWasNewline = true;
11731190
$previousLineWasTerminatedWithBackslash = false;
1174-
} elseif ('\\' === substr($lines[$i], -1)) {
1191+
} elseif ('\\' === $this->currentLine[-1]) {
11751192
$previousLineWasNewline = false;
11761193
$previousLineWasTerminatedWithBackslash = true;
11771194
} else {
11781195
$previousLineWasNewline = false;
11791196
$previousLineWasTerminatedWithBackslash = false;
11801197
}
1181-
}
1198+
1199+
$cursor = 0;
1200+
} while ($this->moveToNextLine());
11821201

11831202
return $value;
11841203
}
11851204

1186-
private function lexInlineMapping(string $yaml): string
1205+
private function lexUnquotedString(int &$cursor): string
11871206
{
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-
}
1194-
1195-
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
1196-
return $yaml;
1197-
}
1198-
1199-
$lines = [$yaml];
1200-
1201-
while ($this->moveToNextLine()) {
1202-
$lines[] = $this->currentLine;
1203-
}
1207+
$offset = $cursor;
1208+
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
12041209

1205-
return implode("\n", $lines);
1210+
return substr($this->currentLine, $offset, $cursor - $offset);
12061211
}
12071212

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

1214-
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
1215-
}
1218+
private function lexInlineSequence(int &$cursor = 0): string
1219+
{
1220+
return $this->lexInlineStructure($cursor, ']');
1221+
}
12161222

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

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

1223-
while ($this->moveToNextLine()) {
1224-
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
1259+
if ($this->consumeWhitespaces($cursor)) {
1260+
$value .= ' ';
1261+
}
12251262
}
12261263

1227-
$trimmedValue = trim($this->currentLine);
1264+
$cursor = 0;
1265+
} while ($this->moveToNextLine());
12281266

1229-
if ('' !== $trimmedValue && '#' === $trimmedValue[0]) {
1230-
continue;
1231-
}
1267+
return $value;
1268+
}
1269+
1270+
private function consumeWhitespaces(int &$cursor): bool
1271+
{
1272+
$whitespacesConsumed = 0;
12321273

1233-
$value .= $trimmedValue;
1274+
do {
1275+
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
1276+
$whitespacesConsumed += $whitespaceOnlyTokenLength;
1277+
$cursor += $whitespaceOnlyTokenLength;
12341278

1235-
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
1236-
break;
1279+
if (isset($this->currentLine[$cursor])) {
1280+
return 0 < $whitespacesConsumed;
12371281
}
1238-
}
12391282

1240-
return $value;
1283+
$cursor = 0;
1284+
} while ($this->moveToNextLine());
1285+
1286+
return 0 < $whitespacesConsumed;
12411287
}
12421288
}

0 commit comments

Comments
 (0)