eventDispatcher = $eventDispatcher; } /** * @param array $flatConstants */ public function build(LineStream $lineStream, RootNode $ast, array $flatConstants = []): RootNode { $this->flatConstants = $flatConstants; $currentObjectPath = new CurrentObjectPath($ast); $currentObjectPathStack = new CurrentObjectPathStack(); $currentObjectPathStack->push($currentObjectPath); $previousLineComments = []; while ($line = $lineStream->getNext()) { $node = null; if ($line instanceof IdentifierAssignmentLine) { // "foo = bar" and "foo ( bar )": Single and multi line assignments $node = $this->handleIdentifierAssignmentLine($line, $currentObjectPath); if ($previousLineComments) { foreach ($previousLineComments as $previousLineComment) { $node->addComment($previousLineComment); } $previousLineComments = []; } } elseif ($line instanceof IdentifierBlockOpenLine) { // "foo {": Opening a block - push to object path stack $node = $this->getOrAddNodeFromIdentifierStream($currentObjectPath, $line->getIdentifierTokenStream()); if ($previousLineComments) { foreach ($previousLineComments as $previousLineComment) { $node->addComment($previousLineComment); } $previousLineComments = []; } $currentObjectPath = (new CurrentObjectPath($node)); $currentObjectPathStack->push($currentObjectPath); } elseif ($line instanceof BlockCloseLine) { // "}": Closing a block - pop from object path stack $currentObjectPath = $currentObjectPathStack->pop(); } elseif ($line instanceof IdentifierUnsetLine) { // "foo >": Remove a path $this->handleIdentifierUnsetLine($line, $currentObjectPath); } elseif ($line instanceof IdentifierCopyLine) { // "foo < bar": Copy a node source path to a target path $node = $this->handleIdentifierCopyLine($line, $ast, $currentObjectPath); if ($node && $previousLineComments) { foreach ($previousLineComments as $previousLineComment) { $node->addComment($previousLineComment); } $previousLineComments = []; } } elseif ($line instanceof IdentifierFunctionLine) { // "foo := addToList(42)": Evaluate functions $node = $this->getOrAddNodeFromIdentifierStream($currentObjectPath, $line->getIdentifierTokenStream()); $functionValueTokenStream = $line->getFunctionValueTokenStream(); $node->setValue($this->evaluateValueModifier($line->getFunctionNameToken(), clone $functionValueTokenStream, $node->getValue())); if ($functionValueTokenStream instanceof ConstantAwareTokenStream) { // @todo: This is a bit unfortunate. When multiple functions manipulate a value after each other, // only the stream of the last one is preserved, previous ones are lost. This way, the BE modules // can not reflect when previous functions used constants. One idea to solve this is to turn existing // nodes into special "function" nodes that park single operations, which are then executed lazy when // a node value is string'ified. The BE modules could then render full lists of value manipulations and // show how values evolve. Another idea is to change setOriginalValueTokenStream() to gather multiple // streams - probably together with the current value at this point in time, which would also allow // rendering how a value evolves over time as well. $node->setOriginalValueTokenStream($functionValueTokenStream); } if ($previousLineComments) { foreach ($previousLineComments as $previousLineComment) { $node->addComment($previousLineComment); } $previousLineComments = []; } } elseif ($line instanceof IdentifierReferenceLine) { // "foo =< bar": Prepare a reference resolving $node = $this->handleIdentifierReferenceLine($line, $currentObjectPath); if ($previousLineComments) { foreach ($previousLineComments as $previousLineComment) { $node->addComment($previousLineComment); } $previousLineComments = []; } } elseif ($line instanceof CommentLine) { $nextLine = $lineStream->peekNext(); if ($currentObjectPath->getLast() instanceof RootNode && ($nextLine === null || $nextLine instanceof EmptyLine)) { $previousLineComments[] = $line->getTokenStream(); foreach ($previousLineComments as $commentLineTokenStream) { $ast->addComment($commentLineTokenStream); } $previousLineComments = []; } if ($nextLine instanceof CommentLine) { $previousLineComments[] = $line->getTokenStream(); } if ($nextLine instanceof IdentifierAssignmentLine || $nextLine instanceof IdentifierBlockOpenLine || $nextLine instanceof IdentifierCopyLine || $nextLine instanceof IdentifierFunctionLine || $nextLine instanceof IdentifierReferenceLine ) { $previousLineComments[] = $line->getTokenStream(); } } } return $ast; } /** * Slightly different from AstBuilder since it sets 'previousValue' */ private function handleIdentifierAssignmentLine(IdentifierAssignmentLine $line, CurrentObjectPath $currentObjectPath): NodeInterface { $node = $this->getOrAddNodeFromIdentifierStream($currentObjectPath, $line->getIdentifierTokenStream()); $valueTokenStream = $line->getValueTokenStream(); if ($valueTokenStream instanceof ConstantAwareTokenStream) { $node->setOriginalValueTokenStream($valueTokenStream); $valueTokenStream = clone $valueTokenStream; $valueTokenStream->setFlatConstants($this->flatConstants); $node->setPreviousValue($node->getValue()); $node->setValue((string)$valueTokenStream); return $node; } $node->setPreviousValue($node->getValue()); $node->setValue((string)$valueTokenStream); return $node; } }