diff --git a/src/NodeInterface.php b/src/NodeInterface.php index 8fe28eb..973cd3d 100644 --- a/src/NodeInterface.php +++ b/src/NodeInterface.php @@ -8,6 +8,13 @@ * indicate they support the Node API. */ interface NodeInterface { + + /** + * Get the filename of the node. + * @return string + */ + public function getFilename(); + /** * Get the line number of the node. * @return int diff --git a/src/ParentNode.php b/src/ParentNode.php index 91f7507..1626916 100644 --- a/src/ParentNode.php +++ b/src/ParentNode.php @@ -404,6 +404,14 @@ public function acceptVisitor(VisitorInterface $visitor) { $visitor->visitEnd($this); } + public function getFilename() { + if ($this->head === NULL) { + return $this->parent->getFilename(); + } + $child = $this->head; + return $child->getFilename(); + } + public function getLineNumber() { if ($this->head === NULL) { return $this->parent->getLineNumber(); diff --git a/src/Parser.php b/src/Parser.php index 1b7b442..53faefe 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -97,12 +97,6 @@ class Parser { */ private static $visibilityTypes = [T_PUBLIC, T_PROTECTED, T_PRIVATE]; - /** - * Filename being parsed. - * @var string - */ - private $filename; - /** * Iterator over PHP tokens. * @var TokenIterator @@ -235,8 +229,8 @@ public static function parseSource($source, $filename = NULL) { $tokenizer = new Tokenizer(); $parser = new self(); } + $tokenizer->setFileName($filename); $tokens = $tokenizer->getAll($source); - $parser->filename = $filename; return $parser->buildTree(new TokenIterator($tokens)); } @@ -285,7 +279,7 @@ private function templateStatementList(ParentNode $node) { } else { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected PHP opening tag, but got ' . $this->iterator->current()->getText()); @@ -723,7 +717,7 @@ private function caseStatement($terminator) { $node->addChild($this->expr(), 'matchOn'); if (!$this->tryMatch(':', $node, NULL, TRUE, TRUE) && !$this->tryMatch(';', $node, NULL, TRUE, TRUE)) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected :'); @@ -739,7 +733,7 @@ private function caseStatement($terminator) { $this->mustMatch(T_DEFAULT, $node); if (!$this->tryMatch(':', $node, NULL, TRUE, TRUE) && !$this->tryMatch(';', $node, NULL, TRUE, TRUE)) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected :'); @@ -751,7 +745,7 @@ private function caseStatement($terminator) { return $node; } throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "expected case or default"); @@ -886,7 +880,7 @@ private function globalVar() { } } throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected a global variable (eg. T_VARIABLE)'); @@ -1229,13 +1223,13 @@ private function expr($static = FALSE) { } else { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "invalid expression"); } } - return $this->expressionParser->parse($expression_nodes, $this->filename); + return $this->expressionParser->parse($expression_nodes, $this->iterator->getFilename()); } /** @@ -1459,7 +1453,7 @@ private function exprOperand() { return $this->backtick(); } throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "excepted expression operand but got " . $this->current->getTypeName()); @@ -1721,7 +1715,7 @@ private function encapsVar() { if ($this->tryMatch('[', $node)) { if (!in_array($this->currentType, $offset_types)) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected encaps_var_offset (T_STRING or T_NUM_STRING or T_VARIABLE)'); @@ -1735,7 +1729,7 @@ private function encapsVar() { return $node; } throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected encaps variable'); @@ -1897,7 +1891,7 @@ private function variable() { } } throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "expected variable"); @@ -2272,7 +2266,7 @@ private function innerStatement() { switch ($this->currentType) { case T_HALT_COMPILER: throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "__halt_compiler can only be used from the outermost scope"); @@ -2491,7 +2485,7 @@ private function classStatement($is_abstract) { case T_PRIVATE: if ($modifiers->getVisibility()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "can only have one visibility modifier on class member/method." @@ -2502,7 +2496,7 @@ private function classStatement($is_abstract) { case T_STATIC: if ($modifiers->getStatic()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "duplicate modifier"); @@ -2512,14 +2506,14 @@ private function classStatement($is_abstract) { case T_FINAL: if ($modifiers->getFinal()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "duplicate modifier"); } if ($modifiers->getAbstract()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "can not use final modifier on abstract method"); @@ -2529,21 +2523,21 @@ private function classStatement($is_abstract) { case T_ABSTRACT: if ($modifiers->getAbstract()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "duplicate modifier"); } if ($modifiers->getFinal()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "can not use abstract modifier on final method"); } if (!$is_abstract) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "can not use abstract modifier in non-abstract class"); @@ -2556,14 +2550,14 @@ private function classStatement($is_abstract) { return $this->classMemberList($doc_comment, $modifiers); default: throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "invalid class statement"); } } throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "invalid class statement"); @@ -2580,14 +2574,14 @@ private function classMemberList($doc_comment, ModifiersNode $modifiers) { // Modifier checks if ($modifiers->getAbstract()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "members can not be declared abstract"); } if ($modifiers->getFinal()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "members can not be declared final"); @@ -2766,7 +2760,7 @@ private function interfaceMethod() { while (in_array($this->currentType, $visibility_keyword_types)) { if ($node->getVisibility()) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), "can only have one visibility modifier on interface method." @@ -2797,7 +2791,7 @@ private function traitDeclaration() { $node->addChild($name_node, 'name'); if ($this->currentType === T_EXTENDS) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'Traits can only be composed from other traits with the \'use\' keyword.' @@ -2805,7 +2799,7 @@ private function traitDeclaration() { } if ($this->currentType === T_IMPLEMENTS) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'Traits can not implement interfaces.' @@ -2947,7 +2941,7 @@ private function matchDocComment(ParentNode $parent, $property_name = 'docCommen private function mustMatch($expected_type, ParentNode $parent, $property_name = NULL, $maybe_last = FALSE, $capture_doc_comment = FALSE) { if ($this->currentType !== $expected_type) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected ' . TokenNode::typeName($expected_type)); @@ -3022,7 +3016,7 @@ private function tryMatchToken($expected_types) { private function mustMatchToken($expected_type) { if ($this->currentType !== $expected_type) { throw new ParserException( - $this->filename, + $this->iterator->getFilename(), $this->iterator->getLineNumber(), $this->iterator->getColumnNumber(), 'expected ' . TokenNode::typeName($expected_type)); diff --git a/src/TokenIterator.php b/src/TokenIterator.php index 91ea18f..f27ab10 100644 --- a/src/TokenIterator.php +++ b/src/TokenIterator.php @@ -76,6 +76,17 @@ public function hasNext() { return $this->position + 1 < $this->length; } + /** + * @return string + */ + public function getFilename() { + $token = $this->current(); + if ($token === NULL) { + $token = $this->tokens[$this->length - 1]; + } + return $token->getFilename(); + } + /** * @return int */ diff --git a/src/TokenNode.php b/src/TokenNode.php index 20230f7..18e5c01 100644 --- a/src/TokenNode.php +++ b/src/TokenNode.php @@ -20,6 +20,11 @@ class TokenNode extends Node { */ protected $lineNo; + /** + * @var string + */ + protected $filename; + /** * @var int */ @@ -39,20 +44,29 @@ class TokenNode extends Node { * Construct token. * @param int $type * @param string $text + * @param string $filename * @param int $lineNo * @param int $newlineCount * @param int $colNo * @param int $byteOffset */ - public function __construct($type, $text, $lineNo = -1, $newlineCount = -1, $colNo = -1, $byteOffset = -1) { + public function __construct($type, $text, $filename = '', $lineNo = -1, $newlineCount = -1, $colNo = -1, $byteOffset = -1) { $this->type = $type; $this->text = $text; + $this->filename = $filename; $this->lineNo = $lineNo; $this->newlineCount = $newlineCount; $this->colNo = $colNo; $this->byteOffset = $byteOffset; } + /** + * @return string + */ + public function getFilename() { + return $this->filename; + } + /** * @return int */ diff --git a/src/Tokenizer.php b/src/Tokenizer.php index 7987679..8a0699c 100644 --- a/src/Tokenizer.php +++ b/src/Tokenizer.php @@ -18,6 +18,7 @@ * Convert PHP source into an array of tokens. */ class Tokenizer { + private $filename; private $lineNo; private $colNo; private $byteOffset; @@ -30,6 +31,7 @@ private function parseToken($token) { $type = $token; $text = $token; } + $filename = $this->filename; $lineNo = $this->lineNo; $colNo = $this->colNo; $byteOffset = $this->byteOffset; @@ -42,43 +44,43 @@ private function parseToken($token) { $this->colNo += $length; } $this->byteOffset += $length; - return $this->createToken($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return $this->createToken($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); } - private function createToken($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset) { + private function createToken($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset) { switch ($type) { case T_VARIABLE: - return new VariableNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new VariableNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_LNUMBER: - return new IntegerNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new IntegerNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_DNUMBER: - return new FloatNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new FloatNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_CONSTANT_ENCAPSED_STRING: - return new StringNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new StringNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_LINE: - return new LineMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new LineMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_FILE: - return new FileMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new FileMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_DIR: - return new DirMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new DirMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_FUNC_C: - return new FunctionMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new FunctionMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_CLASS_C: - return new ClassMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new ClassMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_TRAIT_C: - return new TraitMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new TraitMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_METHOD_C: - return new MethodMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new MethodMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_NS_C: - return new NamespaceMagicConstantNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new NamespaceMagicConstantNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_COMMENT: - return new CommentNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new CommentNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_DOC_COMMENT: - return new DocCommentNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new DocCommentNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); case T_WHITESPACE: - return new WhitespaceNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new WhitespaceNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); default: - return new TokenNode($type, $text, $lineNo, $newlineCount, $colNo, $byteOffset); + return new TokenNode($type, $text, $filename, $lineNo, $newlineCount, $colNo, $byteOffset); } } @@ -98,4 +100,13 @@ public function getAll($source) { } return $tokens; } + + /** + * Sets the filename. + * + * @param string $filename + */ + public function setFileName($filename) { + $this->filename = $filename; + } } diff --git a/tests/ParentNodeTest.php b/tests/ParentNodeTest.php index c11b0c9..f6d6b8e 100644 --- a/tests/ParentNodeTest.php +++ b/tests/ParentNodeTest.php @@ -148,7 +148,7 @@ public function testWalkAbort() { } public function testSourcePosition() { - $token = new TokenNode(T_STRING, 'test', 4, 0, 2); + $token = new TokenNode(T_STRING, 'test', '', 4, 0, 2); $grandparent = $this->createParentNode(); $grandparent->append($token); $parent = $this->createParentNode(); diff --git a/tests/TokenIteratorTest.php b/tests/TokenIteratorTest.php index c03b19f..fc0f671 100644 --- a/tests/TokenIteratorTest.php +++ b/tests/TokenIteratorTest.php @@ -3,7 +3,7 @@ class TokenIteratorTest extends \PHPUnit_Framework_TestCase { public function testSingle() { - $test = new TokenNode(T_STRING, 'test', 1, 0, 1, 0); + $test = new TokenNode(T_STRING, 'test', '', 1, 0, 1, 0); $iterator = new TokenIterator([$test]); $peek = $iterator->peek(0); $this->assertSame($test, $peek);