/** * Parse the operand. * * @param Grammar $grammar The grammar of the parser. * @param TokenStream $stream The token stream to parse. * @return OperandNode The operand node. */ public function parse(Grammar $grammar, TokenStream $stream) { /* @var \com\mohiva\pyramid\Token $token */ $token = $stream->current(); $node = new OperandNode($token->getValue()); return $node; }
/** * Test if the parse method returns the `OperandNode` with the correct number. */ public function testParseReturnsNodeWithCorrectNumber() { $number = mt_rand(1, 100); $tokenStream = new TokenStream(); $tokenStream->push(new Token(Lexer::T_NUMBER, $number, 1)); $tokenStream->rewind(); $operand = new NumberOperand(); $node = $operand->parse(new Grammar(), $tokenStream); $this->assertSame($number, $node->evaluate()); }
/** * Create an array from the token stream which contains only the tokens and the operators/values. * * @param TokenStream $stream The stream containing the lexer tokens. * @return array The actual list with tokens and operators/values. */ private function buildActualTokens(TokenStream $stream) { $actual = array(); while ($stream->valid()) { /* @var \com\mohiva\pyramid\Token $current */ $current = $stream->current(); $stream->next(); $actual[] = array($current->getCode() => $current->getValue()); } return $actual; }
/** * Test if the `parse` method throws an exception if the closing parentheses is missing * and the end of the stream is reached. * * @expectedException \com\mohiva\common\exceptions\SyntaxErrorException */ public function testParseThrowsExceptionIfEndOfStreamIsReached() { $tokenStream = new TokenStream(); $tokenStream->push(new Token(Lexer::T_OPEN_PARENTHESIS, '(', 1)); $tokenStream->push(new Token(Lexer::T_NUMBER, 100, 1)); $tokenStream->rewind(); $grammar = new Grammar(); $grammar->addOperand(new NumberOperand()); $operand = new ParenthesesOperand(); $operand->parse($grammar, $tokenStream); }
/** * Parse the operand. * * This example shows how you should parse sub expressions. You must only create a * new parser with the passed grammar and token stream. * * @param Grammar $grammar The grammar of the parser. * @param TokenStream $stream The token stream to parse. * @return Node The node between the parentheses. * @throws SyntaxErrorException if an unexpected token was found. */ public function parse(Grammar $grammar, TokenStream $stream) { $stream->next(); $parser = new Parser($grammar); $node = $parser->parse($stream); $stream->expect(array(Lexer::T_CLOSE_PARENTHESIS), function (Token $current = null) { if ($current) { $message = "Expected `)`; got `{$current->getValue()}`"; } else { $message = "Expected `)` but end of stream reached"; } throw new SyntaxErrorException($message); }); return $node; }
/** * Parse the operand. * * @return Node The node object for the operand. * @throws SyntaxErrorException if no operand parser can be found for * the current token. */ private function parseOperand() { /* @var Token $token */ $token = $this->stream->current(); try { /* @var Operand $operand */ $operand = $this->operandTable->getOperand($token); } catch (InvalidIdentifierException $e) { $message = "Cannot find operand parser for token `{$token->getValue()}`"; throw new SyntaxErrorException($message, 0, $e); } return $operand->parse($this->grammar, $this->stream); }
/** * Throw a syntax error exception. * * @param array $tokens A list with expected tokens. * @param AnnotationToken $lookahead The lookahead token. * @throws SyntaxErrorException if a syntax error occurs. */ private function syntaxError(array $tokens, AnnotationToken $lookahead = null) { $refObject = new ReflectionClass(__NAMESPACE__ . '\\AnnotationLexer'); $tokens = array_map(array($refObject, 'getConstantByValue'), $tokens); $tokens = implode('`,`', $tokens); if (!$lookahead) { $message = "Syntax error in DocBlock for: {$this->context->getLocation()}, "; $message .= "expected tokens: `{$tokens}` but end of string reached"; } else { $snippet = preg_replace('/[\\r\\n\\t]*/', '', substr($this->stream->getSource(), $lookahead->getOffset(), 50)); $message = "Syntax error in DocBlock for: {$this->context->getLocation()}, "; $message .= "expected tokens: `{$tokens}` but found `{$lookahead->getValue()}` "; $message .= "at offset `{$lookahead->getOffset()}` near: `{$snippet}`"; } throw new SyntaxErrorException($message); }
/** * Transform the input string into a token stream. * * @param string $input The string input to tokenize. * @return TokenStream The resulting token stream. */ private function tokenize($input) { $stream = new TokenStream(); $stream->setSource($input); $pattern = '/' . implode('|', $this->lexemes) . '/'; $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($pattern, $input, -1, $flags); foreach ($matches as $match) { $value = strtolower($match[0]); if ($value[0] == "'" || $value[0] == '"' || $value == 'true' || $value == 'false' || $value == 'null' || is_numeric($value)) { $code = self::T_VALUE; } else { if (isset($this->constMap[$value])) { $code = $this->constMap[$value]; } else { if (preg_match('/[a-z0-9_]+/', $value)) { $code = self::T_NAME; } else { if (ctype_space($value)) { continue; } else { $code = self::T_NONE; } } } } $stream->push(new AnnotationToken($code, $match[0], $match[1])); } return $stream; }
/** * Data provider which returns a TokenStream instance. * * @return array An array containing a TokenStream instance. */ public function tokenStreamProvider() { // user.name.split(" ").join("-") $stream = new TokenStream(); $stream->push(new TestToken(self::T_NAME)); $stream->push(new TestToken(self::T_POINT)); $stream->push(new TestToken(self::T_NAME)); $stream->push(new TestToken(self::T_POINT)); $stream->push(new TestToken(self::T_NAME)); $stream->push(new TestToken(self::T_OPEN_PARENTHESIS)); $stream->push(new TestToken(self::T_VALUE)); $stream->push(new TestToken(self::T_CLOSE_PARENTHESIS)); $stream->push(new TestToken(self::T_POINT)); $stream->push(new TestToken(self::T_NAME)); $stream->push(new TestToken(self::T_OPEN_PARENTHESIS)); $stream->push(new TestToken(self::T_VALUE)); $stream->push(new TestToken(self::T_CLOSE_PARENTHESIS)); $stream->rewind(); return array(array($stream)); }
/** * Transform the input string into a token stream. * * @param string $input The string input to tokenize. * @return TokenStream The resulting token stream. */ private function tokenize($input) { $stream = new TokenStream(); $stream->setSource($input); $pattern = '/' . implode('|', $this->lexemes) . '/'; $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($pattern, $input, -1, $flags); foreach ($matches as $match) { $value = strtolower($match[0]); if (is_numeric($value)) { $code = self::T_NUMBER; } else { if (isset($this->constTokenMap[$value])) { $code = $this->constTokenMap[$value]; } else { if (ctype_space($value)) { continue; } else { $code = self::T_NONE; } } } $stream->push(new Token($code, $match[0], $match[1])); } return $stream; }