Skip to content

Commit

Permalink
Support for URI in attribute path fixes #3
Browse files Browse the repository at this point in the history
  • Loading branch information
tmilos committed Jan 19, 2017
1 parent 4aee024 commit cfd9ba1
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 78 deletions.
9 changes: 8 additions & 1 deletion src/Ast/AttributePath.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

class AttributePath extends Node
{
/** @var string */
public $schema;

/** @var string[] */
public $attributeNames = [];

Expand All @@ -23,13 +26,17 @@ public function add($attributeName)

public function __toString()
{
if ($this->schema) {
return $this->schema.' : '.implode('.', $this->attributeNames);
}

return implode('.', $this->attributeNames);
}

public function dump()
{
return [
'AttributePath' => implode('.', $this->attributeNames),
'AttributePath' => $this->__toString(),
];
}
}
82 changes: 42 additions & 40 deletions src/Ast/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,53 @@
abstract class Tokens
{
const T_SP = 'SP';
const T_TRUE = 'TRUE';
const T_FALSE = 'FALSE';
const T_NULL = 'NULL';
//const T_TRUE = 'TRUE';
//const T_FALSE = 'FALSE';
//const T_NULL = 'NULL';
const T_NUMBER = 'NUMBER';
const T_STRING = 'STRING';
const T_ATTR_NAME = 'ATTR_NAME';
const T_NAME = 'NAME';
const T_DOT = 'DOT';
const T_COLON = 'COLON';
const T_SLASH = 'SLASH';
const T_PAREN_OPEN = 'PAREN_OPEN';
const T_PAREN_CLOSE = 'PAREN_CLOSE';
const T_BRACKET_OPEN = ' BRACKET_OPEN';
const T_BRACKET_CLOSE = ' BRACKET_CLOSE';
const T_NOT = 'NOT';
const T_AND = 'AND';
const T_OR = 'OR';

const T_PR = 'PR';

const T_EQ = 'EQ';
const T_NE = 'NE';
const T_CO = 'CO';
const T_SW = 'SW';
const T_EW = 'EW';
const T_GT = 'GT';
const T_LT = 'LT';
const T_GE = 'GE';
const T_LE = 'LE';

private static $_compareValue = [self::T_TRUE, self::T_FALSE, self::T_NULL, self::T_NUMBER, self::T_STRING];

private static $_compareUnaryOperators = [self::T_PR];
private static $_compareBinaryOperators = [self::T_EQ, self::T_NE, self::T_CO, self::T_SW, self::T_EW, self::T_GT, self::T_LT, self::T_GE, self::T_LE];

/**
* @return array
*/
public static function compareValues()
{
return static::$_compareValue;
}

/**
* @return array
*/
public static function compareOperators()
{
return array_merge(static::$_compareUnaryOperators, static::$_compareBinaryOperators);
}
// const T_NOT = 'NOT';
// const T_AND = 'AND';
// const T_OR = 'OR';
//
// const T_PR = 'PR';
//
// const T_EQ = 'EQ';
// const T_NE = 'NE';
// const T_CO = 'CO';
// const T_SW = 'SW';
// const T_EW = 'EW';
// const T_GT = 'GT';
// const T_LT = 'LT';
// const T_GE = 'GE';
// const T_LE = 'LE';
//
// private static $_compareValue = [self::T_TRUE, self::T_FALSE, self::T_NULL, self::T_NUMBER, self::T_STRING];
//
// private static $_compareUnaryOperators = [self::T_PR];
// private static $_compareBinaryOperators = [self::T_EQ, self::T_NE, self::T_CO, self::T_SW, self::T_EW, self::T_GT, self::T_LT, self::T_GE, self::T_LE];
//
// /**
// * @return array
// */
// public static function _compareValues()
// {
// return ['true', 'false', 'null'];
// }
//
// /**
// * @return array
// */
// public static function _compareOperators()
// {
// return array_merge(static::$_compareUnaryOperators, static::$_compareBinaryOperators);
// }
}
36 changes: 19 additions & 17 deletions src/LexerConfigFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,30 @@ public static function getConfig()
{
return [
'\\s+' => Tokens::T_SP,
'true' => Tokens::T_TRUE,
'false' => Tokens::T_FALSE,
'null' => Tokens::T_NULL,
//'true' => Tokens::T_TRUE,
//'false' => Tokens::T_FALSE,
//'null' => Tokens::T_NULL,
'\\d+(\\.\\d+)?' => Tokens::T_NUMBER,
'"(?:[^"\\\\]|\\\\.)*"' => Tokens::T_STRING,
'\\.' => Tokens::T_DOT,
':' => Tokens::T_COLON,
'/' => Tokens::T_SLASH,
'\\(' => Tokens::T_PAREN_OPEN,
'\\)' => Tokens::T_PAREN_CLOSE,
'not' => Tokens::T_NOT,
'and' => Tokens::T_AND,
'or' => Tokens::T_OR,
'pr' => Tokens::T_PR,
'eq' => Tokens::T_EQ,
'ne' => Tokens::T_NE,
'co' => Tokens::T_CO,
'sw' => Tokens::T_SW,
'ew' => Tokens::T_EW,
'gt' => Tokens::T_GT,
'lt' => Tokens::T_LT,
'ge' => Tokens::T_GE,
'le' => Tokens::T_LE,
'[a-z][a-z0-9-_]*' => Tokens::T_ATTR_NAME,
// 'not' => Tokens::T_NOT,
// 'and' => Tokens::T_AND,
// 'or' => Tokens::T_OR,
// 'pr' => Tokens::T_PR,
// 'eq' => Tokens::T_EQ,
// 'ne' => Tokens::T_NE,
// 'co' => Tokens::T_CO,
// 'sw' => Tokens::T_SW,
// 'ew' => Tokens::T_EW,
// 'gt' => Tokens::T_GT,
// 'lt' => Tokens::T_LT,
// 'ge' => Tokens::T_GE,
// 'le' => Tokens::T_LE,
'[a-z0-9-_]+' => Tokens::T_NAME,
'\\[' => Tokens::T_BRACKET_OPEN,
'\\]' => Tokens::T_BRACKET_CLOSE,
];
Expand Down
98 changes: 79 additions & 19 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ private function disjunction()

if ($this->lexer->isNextToken(Tokens::T_SP)) {
$nextToken = $this->lexer->glimpse();
if ($nextToken && $nextToken->is(Tokens::T_OR)) {
if ($this->isName('or', $nextToken)) {
$this->match(Tokens::T_SP);
$this->match(Tokens::T_OR);
$this->match(Tokens::T_NAME);
$this->match(Tokens::T_SP);
$terms[] = $this->conjunction();
}
Expand All @@ -154,9 +154,9 @@ private function conjunction()

if ($this->lexer->isNextToken(Tokens::T_SP)) {
$nextToken = $this->lexer->glimpse();
if ($nextToken && $nextToken->is(Tokens::T_AND)) {
if ($this->isName('and', $nextToken)) {
$this->match(Tokens::T_SP);
$this->match(Tokens::T_AND);
$this->match(Tokens::T_NAME);
$this->match(Tokens::T_SP);
$factors[] = $this->factor();
}
Expand All @@ -174,9 +174,9 @@ private function conjunction()
*/
private function factor()
{
if ($this->lexer->isNextToken(Tokens::T_NOT)) {
if ($this->isName('not', $this->lexer->getLookahead())) {
// not ( filter )
$this->match(Tokens::T_NOT);
$this->match(Tokens::T_NAME);
$this->match(Tokens::T_SP);
$this->match(Tokens::T_PAREN_OPEN);
$filter = $this->disjunction();
Expand Down Expand Up @@ -236,22 +236,49 @@ private function comparisionExpression()
}

/**
* @param Ast\AttributePath $attributePath
*
* @return Ast\AttributePath
*/
private function attributePath(Ast\AttributePath $attributePath = null)
private function attributePath()
{
$this->match(Tokens::T_ATTR_NAME);
$string = '';
$valid = [Tokens::T_NUMBER, Tokens::T_NAME, Tokens::T_COLON, Tokens::T_SLASH, Tokens::T_DOT];
$stopping = [Tokens::T_SP, Tokens::T_BRACKET_OPEN];

while (true) {
$token = $this->lexer->getLookahead();
if (!$token) {
break;
}
$isValid = in_array($token->getName(), $valid);
$isStopping = in_array($token->getName(), $stopping);
if ($isStopping) {
break;
}
if (!$isValid) {
$this->syntaxError('attribute path');
}
$string .= $token->getValue();
$this->lexer->moveNext();
}

if (!$string) {
$this->syntaxError('attribute path');
}

if (!$attributePath) {
$attributePath = new Ast\AttributePath();
$colonPos = strrpos($string, ':');
if ($colonPos !== false) {
$schema = substr($string, 0, $colonPos);
$path = substr($string, $colonPos + 1);
} else {
$schema = null;
$path = $string;
}
$attributePath->add($this->lexer->getToken()->getValue());

if ($this->lexer->isNextToken(Tokens::T_DOT)) {
$this->match(Tokens::T_DOT);
$this->attributePath($attributePath);
$parts = explode('.', $path);
$attributePath = new Ast\AttributePath();
$attributePath->schema = $schema;
foreach ($parts as $part) {
$attributePath->add($part);
}

return $attributePath;
Expand All @@ -262,9 +289,10 @@ private function attributePath(Ast\AttributePath $attributePath = null)
*/
private function comparisonOperator()
{
if (!$this->lexer->isNextTokenAny(Tokens::compareOperators())) {
if (!$this->isName(['pr', 'eq', 'ne', 'co', 'sw', 'ew', 'gt', 'lt', 'ge', 'le'], $this->lexer->getLookahead())) {
$this->syntaxError('comparision operator');
}

$this->match($this->lexer->getLookahead()->getName());

return $this->lexer->getToken()->getValue();
Expand All @@ -275,9 +303,13 @@ private function comparisonOperator()
*/
private function compareValue()
{
if (!$this->lexer->isNextTokenAny(Tokens::compareValues())) {
if (!$this->lexer->isNextTokenAny([Tokens::T_NAME, Tokens::T_NUMBER, Tokens::T_STRING])) {
$this->syntaxError('comparison value');
}
if ($this->lexer->getLookahead()->is(Tokens::T_NAME) && !$this->isName(['true', 'false', 'null'], $this->lexer->getLookahead())) {
$this->syntaxError('comparision value');
}

$this->match($this->lexer->getLookahead()->getName());

$value = json_decode($this->lexer->getToken()->getValue());
Expand Down Expand Up @@ -306,12 +338,40 @@ private function compareValue()
*/
private function isValuePathIncoming()
{
$tokenAfterAttributePath = $this->lexer->peekWhileTokens([Tokens::T_ATTR_NAME, Tokens::T_DOT]);
$tokenAfterAttributePath = $this->lexer->peekWhileTokens([Tokens::T_NAME, Tokens::T_DOT]);
$this->lexer->resetPeek();

return $tokenAfterAttributePath ? $tokenAfterAttributePath->is(Tokens::T_BRACKET_OPEN) : false;
}

/**
* @param string|string[] $value
* @param Token|null $token
*
* @return bool
*/
private function isName($value, $token)
{
if (!$token) {
return false;
}
if (!$token->is(Tokens::T_NAME)) {
return false;
}

if (is_array($value)) {
foreach ($value as $v) {
if (strcasecmp($token->getValue(), $v) === 0) {
return true;
}
}

return false;
}

return strcasecmp($token->getValue(), $value) === 0;
}

private function match($tokenName)
{
if (null === $tokenName) {
Expand Down
7 changes: 6 additions & 1 deletion tests/ParserFilterModeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public function parser_provider_v2()
['ComparisonExpression' => 'userName sw J'],
],

[
'urn:ietf:params:scim:schemas:core:2.0:User:userName sw "J"',
['ComparisonExpression' => 'urn:ietf:params:scim:schemas:core:2.0:User : userName sw J'],
],

[
'title pr',
['ComparisonExpression' => 'title pr'],
Expand Down Expand Up @@ -246,7 +251,7 @@ public function test_parser_v2($filterString, array $expectedDump)
public function error_provider_v2()
{
return [
['not a valid filter', "[Syntax Error] line 0, col 4: Error: Expected PAREN_OPEN, got 'a'"],
['none a valid filter', "[Syntax Error] line 0, col 5: Error: Expected comparision operator, got 'a'"],
['username xx "mike"', "[Syntax Error] line 0, col 9: Error: Expected comparision operator, got 'xx'"],
['username eq', "[Syntax Error] line 0, col 9: Error: Expected SP, got end of string."],
['username eq ', "[Syntax Error] line 0, col 11: Error: Expected comparison value, got end of string."],
Expand Down

0 comments on commit cfd9ba1

Please sign in to comment.