Security update for permissions_by_term
[yaffs-website] / vendor / behat / gherkin / src / Behat / Gherkin / Parser.php
diff --git a/vendor/behat/gherkin/src/Behat/Gherkin/Parser.php b/vendor/behat/gherkin/src/Behat/Gherkin/Parser.php
new file mode 100644 (file)
index 0000000..5cc8542
--- /dev/null
@@ -0,0 +1,699 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin;
+
+use Behat\Gherkin\Exception\LexerException;
+use Behat\Gherkin\Exception\ParserException;
+use Behat\Gherkin\Node\BackgroundNode;
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\PyStringNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+use Behat\Gherkin\Node\ScenarioNode;
+use Behat\Gherkin\Node\StepNode;
+use Behat\Gherkin\Node\TableNode;
+
+/**
+ * Gherkin parser.
+ *
+ * $lexer  = new Behat\Gherkin\Lexer($keywords);
+ * $parser = new Behat\Gherkin\Parser($lexer);
+ * $featuresArray = $parser->parse('/path/to/feature.feature');
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class Parser
+{
+    private $lexer;
+    private $input;
+    private $file;
+    private $tags = array();
+    private $languageSpecifierLine;
+
+    /**
+     * Initializes parser.
+     *
+     * @param Lexer $lexer Lexer instance
+     */
+    public function __construct(Lexer $lexer)
+    {
+        $this->lexer = $lexer;
+    }
+
+    /**
+     * Parses input & returns features array.
+     *
+     * @param string $input Gherkin string document
+     * @param string $file  File name
+     *
+     * @return FeatureNode|null
+     *
+     * @throws ParserException
+     */
+    public function parse($input, $file = null)
+    {
+        $this->languageSpecifierLine = null;
+        $this->input = $input;
+        $this->file = $file;
+        $this->tags = array();
+
+        try {
+            $this->lexer->analyse($this->input, 'en');
+        } catch (LexerException $e) {
+            throw new ParserException(
+                sprintf('Lexer exception "%s" thrown for file %s', $e->getMessage(), $file),
+                0,
+                $e
+            );
+        }
+
+        $feature = null;
+        while ('EOS' !== ($predicted = $this->predictTokenType())) {
+            $node = $this->parseExpression();
+
+            if (null === $node || "\n" === $node) {
+                continue;
+            }
+
+            if (!$feature && $node instanceof FeatureNode) {
+                $feature = $node;
+                continue;
+            }
+
+            if ($feature && $node instanceof FeatureNode) {
+                throw new ParserException(sprintf(
+                    'Only one feature is allowed per feature file. But %s got multiple.',
+                    $this->file
+                ));
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Feature, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof FeatureNode) {
+                throw new ParserException(sprintf(
+                    'Expected Feature, but got %s on line: %d%s',
+                    $node->getKeyword(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return $feature;
+    }
+
+    /**
+     * Returns next token if it's type equals to expected.
+     *
+     * @param string $type Token type
+     *
+     * @return array
+     *
+     * @throws Exception\ParserException
+     */
+    protected function expectTokenType($type)
+    {
+        $types = (array) $type;
+        if (in_array($this->predictTokenType(), $types)) {
+            return $this->lexer->getAdvancedToken();
+        }
+
+        $token = $this->lexer->predictToken();
+
+        throw new ParserException(sprintf(
+            'Expected %s token, but got %s on line: %d%s',
+            implode(' or ', $types),
+            $this->predictTokenType(),
+            $token['line'],
+            $this->file ? ' in file: ' . $this->file : ''
+        ));
+    }
+
+    /**
+     * Returns next token if it's type equals to expected.
+     *
+     * @param string $type Token type
+     *
+     * @return null|array
+     */
+    protected function acceptTokenType($type)
+    {
+        if ($type !== $this->predictTokenType()) {
+            return null;
+        }
+
+        return $this->lexer->getAdvancedToken();
+    }
+
+    /**
+     * Returns next token type without real input reading (prediction).
+     *
+     * @return string
+     */
+    protected function predictTokenType()
+    {
+        $token = $this->lexer->predictToken();
+
+        return $token['type'];
+    }
+
+    /**
+     * Parses current expression & returns Node.
+     *
+     * @return string|FeatureNode|BackgroundNode|ScenarioNode|OutlineNode|TableNode|StepNode
+     *
+     * @throws ParserException
+     */
+    protected function parseExpression()
+    {
+        switch ($type = $this->predictTokenType()) {
+            case 'Feature':
+                return $this->parseFeature();
+            case 'Background':
+                return $this->parseBackground();
+            case 'Scenario':
+                return $this->parseScenario();
+            case 'Outline':
+                return $this->parseOutline();
+            case 'Examples':
+                return $this->parseExamples();
+            case 'TableRow':
+                return $this->parseTable();
+            case 'PyStringOp':
+                return $this->parsePyString();
+            case 'Step':
+                return $this->parseStep();
+            case 'Text':
+                return $this->parseText();
+            case 'Newline':
+                return $this->parseNewline();
+            case 'Tag':
+                return $this->parseTags();
+            case 'Comment':
+                return $this->parseComment();
+            case 'Language':
+                return $this->parseLanguage();
+            case 'EOS':
+                return '';
+        }
+
+        throw new ParserException(sprintf('Unknown token type: %s', $type));
+    }
+
+    /**
+     * Parses feature token & returns it's node.
+     *
+     * @return FeatureNode
+     *
+     * @throws ParserException
+     */
+    protected function parseFeature()
+    {
+        $token = $this->expectTokenType('Feature');
+
+        $title = trim($token['value']) ?: null;
+        $description = null;
+        $tags = $this->popTags();
+        $background = null;
+        $scenarios = array();
+        $keyword = $token['keyword'];
+        $language = $this->lexer->getLanguage();
+        $file = $this->file;
+        $line = $token['line'];
+
+        // Parse description, background, scenarios & outlines
+        while ('EOS' !== $this->predictTokenType()) {
+            $node = $this->parseExpression();
+
+            if (is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $description .= (null !== $description ? "\n" : '') . $text;
+                continue;
+            }
+
+            if (!$background && $node instanceof BackgroundNode) {
+                $background = $node;
+                continue;
+            }
+
+            if ($node instanceof ScenarioInterface) {
+                $scenarios[] = $node;
+                continue;
+            }
+
+            if ($background instanceof BackgroundNode && $node instanceof BackgroundNode) {
+                throw new ParserException(sprintf(
+                    'Each Feature could have only one Background, but found multiple on lines %d and %d%s',
+                    $background->getLine(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof ScenarioNode) {
+                throw new ParserException(sprintf(
+                    'Expected Scenario, Outline or Background, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return new FeatureNode(
+            rtrim($title) ?: null,
+            rtrim($description) ?: null,
+            $tags,
+            $background,
+            $scenarios,
+            $keyword,
+            $language,
+            $file,
+            $line
+        );
+    }
+
+    /**
+     * Parses background token & returns it's node.
+     *
+     * @return BackgroundNode
+     *
+     * @throws ParserException
+     */
+    protected function parseBackground()
+    {
+        $token = $this->expectTokenType('Background');
+
+        $title = trim($token['value']);
+        $keyword = $token['keyword'];
+        $line = $token['line'];
+
+        if (count($this->popTags())) {
+            throw new ParserException(sprintf(
+                'Background can not be tagged, but it is on line: %d%s',
+                $line,
+                $this->file ? ' in file: ' . $this->file : ''
+            ));
+        }
+
+        // Parse description and steps
+        $steps = array();
+        $allowedTokenTypes = array('Step', 'Newline', 'Text', 'Comment');
+        while (in_array($this->predictTokenType(), $allowedTokenTypes)) {
+            $node = $this->parseExpression();
+
+            if ($node instanceof StepNode) {
+                $steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
+                continue;
+            }
+
+            if (!count($steps) && is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $title .= "\n" . $text;
+                continue;
+            }
+
+            if ("\n" === $node) {
+                continue;
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof StepNode) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return new BackgroundNode(rtrim($title) ?: null, $steps, $keyword, $line);
+    }
+
+    /**
+     * Parses scenario token & returns it's node.
+     *
+     * @return ScenarioNode
+     *
+     * @throws ParserException
+     */
+    protected function parseScenario()
+    {
+        $token = $this->expectTokenType('Scenario');
+
+        $title = trim($token['value']);
+        $tags = $this->popTags();
+        $keyword = $token['keyword'];
+        $line = $token['line'];
+
+        // Parse description and steps
+        $steps = array();
+        while (in_array($this->predictTokenType(), array('Step', 'Newline', 'Text', 'Comment'))) {
+            $node = $this->parseExpression();
+
+            if ($node instanceof StepNode) {
+                $steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
+                continue;
+            }
+
+            if (!count($steps) && is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $title .= "\n" . $text;
+                continue;
+            }
+
+            if ("\n" === $node) {
+                continue;
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof StepNode) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return new ScenarioNode(rtrim($title) ?: null, $tags, $steps, $keyword, $line);
+    }
+
+    /**
+     * Parses scenario outline token & returns it's node.
+     *
+     * @return OutlineNode
+     *
+     * @throws ParserException
+     */
+    protected function parseOutline()
+    {
+        $token = $this->expectTokenType('Outline');
+
+        $title = trim($token['value']);
+        $tags = $this->popTags();
+        $keyword = $token['keyword'];
+        $examples = null;
+        $line = $token['line'];
+
+        // Parse description, steps and examples
+        $steps = array();
+        while (in_array($this->predictTokenType(), array('Step', 'Examples', 'Newline', 'Text', 'Comment'))) {
+            $node = $this->parseExpression();
+
+            if ($node instanceof StepNode) {
+                $steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
+                continue;
+            }
+
+            if ($node instanceof ExampleTableNode) {
+                $examples = $node;
+                continue;
+            }
+
+            if (!count($steps) && is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $title .= "\n" . $text;
+                continue;
+            }
+
+            if ("\n" === $node) {
+                continue;
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Step or Examples table, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof StepNode) {
+                throw new ParserException(sprintf(
+                    'Expected Step or Examples table, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        if (null === $examples) {
+            throw new ParserException(sprintf(
+                'Outline should have examples table, but got none for outline "%s" on line: %d%s',
+                rtrim($title),
+                $line,
+                $this->file ? ' in file: ' . $this->file : ''
+            ));
+        }
+
+        return new OutlineNode(rtrim($title) ?: null, $tags, $steps, $examples, $keyword, $line);
+    }
+
+    /**
+     * Parses step token & returns it's node.
+     *
+     * @return StepNode
+     */
+    protected function parseStep()
+    {
+        $token = $this->expectTokenType('Step');
+
+        $keyword = $token['value'];
+        $keywordType = $token['keyword_type'];
+        $text = trim($token['text']);
+        $line = $token['line'];
+
+        $arguments = array();
+        while (in_array($predicted = $this->predictTokenType(), array('PyStringOp', 'TableRow', 'Newline', 'Comment'))) {
+            if ('Comment' === $predicted || 'Newline' === $predicted) {
+                $this->acceptTokenType($predicted);
+                continue;
+            }
+
+            $node = $this->parseExpression();
+
+            if ($node instanceof PyStringNode || $node instanceof TableNode) {
+                $arguments[] = $node;
+            }
+        }
+
+        return new StepNode($keyword, $text, $arguments, $line, $keywordType);
+    }
+
+    /**
+     * Parses examples table node.
+     *
+     * @return ExampleTableNode
+     */
+    protected function parseExamples()
+    {
+        $token = $this->expectTokenType('Examples');
+
+        $keyword = $token['keyword'];
+
+        return new ExampleTableNode($this->parseTableRows(), $keyword);
+    }
+
+    /**
+     * Parses table token & returns it's node.
+     *
+     * @return TableNode
+     */
+    protected function parseTable()
+    {
+        return new TableNode($this->parseTableRows());
+    }
+
+    /**
+     * Parses PyString token & returns it's node.
+     *
+     * @return PyStringNode
+     */
+    protected function parsePyString()
+    {
+        $token = $this->expectTokenType('PyStringOp');
+
+        $line = $token['line'];
+
+        $strings = array();
+        while ('PyStringOp' !== ($predicted = $this->predictTokenType()) && 'Text' === $predicted) {
+            $token = $this->expectTokenType('Text');
+
+            $strings[] = $token['value'];
+        }
+
+        $this->expectTokenType('PyStringOp');
+
+        return new PyStringNode($strings, $line);
+    }
+
+    /**
+     * Parses tags.
+     *
+     * @return BackgroundNode|FeatureNode|OutlineNode|ScenarioNode|StepNode|TableNode|string
+     */
+    protected function parseTags()
+    {
+        $token = $this->expectTokenType('Tag');
+        $this->tags = array_merge($this->tags, $token['tags']);
+
+        return $this->parseExpression();
+    }
+
+    /**
+     * Returns current set of tags and clears tag buffer.
+     *
+     * @return array
+     */
+    protected function popTags()
+    {
+        $tags = $this->tags;
+        $this->tags = array();
+
+        return $tags;
+    }
+
+    /**
+     * Parses next text line & returns it.
+     *
+     * @return string
+     */
+    protected function parseText()
+    {
+        $token = $this->expectTokenType('Text');
+
+        return $token['value'];
+    }
+
+    /**
+     * Parses next newline & returns \n.
+     *
+     * @return string
+     */
+    protected function parseNewline()
+    {
+        $this->expectTokenType('Newline');
+
+        return "\n";
+    }
+
+    /**
+     * Parses next comment token & returns it's string content.
+     *
+     * @return BackgroundNode|FeatureNode|OutlineNode|ScenarioNode|StepNode|TableNode|string
+     */
+    protected function parseComment()
+    {
+        $this->expectTokenType('Comment');
+
+        return $this->parseExpression();
+    }
+
+    /**
+     * Parses language block and updates lexer configuration based on it.
+     *
+     * @return BackgroundNode|FeatureNode|OutlineNode|ScenarioNode|StepNode|TableNode|string
+     *
+     * @throws ParserException
+     */
+    protected function parseLanguage()
+    {
+        $token = $this->expectTokenType('Language');
+
+        if (null === $this->languageSpecifierLine) {
+            $this->lexer->analyse($this->input, $token['value']);
+            $this->languageSpecifierLine = $token['line'];
+        } elseif ($token['line'] !== $this->languageSpecifierLine) {
+            throw new ParserException(sprintf(
+                'Ambiguous language specifiers on lines: %d and %d%s',
+                $this->languageSpecifierLine,
+                $token['line'],
+                $this->file ? ' in file: ' . $this->file : ''
+            ));
+        }
+
+        return $this->parseExpression();
+    }
+
+    /**
+     * Parses the rows of a table
+     *
+     * @return string[][]
+     */
+    private function parseTableRows()
+    {
+        $table = array();
+        while (in_array($predicted = $this->predictTokenType(), array('TableRow', 'Newline', 'Comment'))) {
+            if ('Comment' === $predicted || 'Newline' === $predicted) {
+                $this->acceptTokenType($predicted);
+                continue;
+            }
+
+            $token = $this->expectTokenType('TableRow');
+
+            $table[$token['line']] = $token['columns'];
+        }
+
+        return $table;
+    }
+
+    /**
+     * Changes step node type for types But, And to type of previous step if it exists else sets to Given
+     *
+     * @param StepNode   $node
+     * @param StepNode[] $steps
+     * @return StepNode
+     */
+    private function normalizeStepNodeKeywordType(StepNode $node, array $steps = array())
+    {
+        if (in_array($node->getKeywordType(), array('And', 'But'))) {
+            if (($prev = end($steps))) {
+                $keywordType = $prev->getKeywordType();
+            } else {
+                $keywordType = 'Given';
+            }
+
+            $node = new StepNode(
+                $node->getKeyword(),
+                $node->getText(),
+                $node->getArguments(),
+                $node->getLine(),
+                $keywordType
+            );
+        }
+        return $node;
+    }
+}