Skip to content
This repository has been archived by the owner on Mar 27, 2019. It is now read-only.

Commit

Permalink
Implemented extension types (fixes #33).
Browse files Browse the repository at this point in the history
An extension type is specified by preceeding a class name with a colon, eg: `:Foo\Bar`. The parser produces an `ExtensionType` instance with the class name set to `Foo\Bar`.

Attributes are supported, and are passed to the `ExtensionType` instance unmodified, eg: `:Foo\Bar { myAttribute: true }`.
Support for arbitrary attributes required modification of `Parser::parseAttributes()`, the `$supportedAttributes` parameter is now nullable to indicate that there is no restriction on the supported attribute names.

`ExtensionType` exposes two methods:
 * `className()` which returns a ClassName instance; and
 * `attributes()` which returns the attributes array.
  • Loading branch information
jmalloc committed Jan 23, 2013
1 parent 76d386b commit b46f13a
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 7 deletions.
40 changes: 36 additions & 4 deletions src/Eloquent/Typhax/Parser/Parser.php
Expand Up @@ -20,6 +20,7 @@
use Eloquent\Typhax\Type\BooleanType;
use Eloquent\Typhax\Type\CallableType;
use Eloquent\Typhax\Type\FloatType;
use Eloquent\Typhax\Type\ExtensionType;
use Eloquent\Typhax\Type\IntegerType;
use Eloquent\Typhax\Type\MixedType;
use Eloquent\Typhax\Type\NullType;
Expand Down Expand Up @@ -113,12 +114,16 @@ protected function parseType(array &$tokens)
Token::TOKEN_STRING,
Token::TOKEN_TYPE_NAME,
Token::TOKEN_NULL,
Token::TOKEN_COLON,
)
);

if (Token::TOKEN_STRING === $token->type()) {
$type = new ObjectType(ClassName::fromString($token->content()));
next($tokens);
} elseif (Token::TOKEN_COLON === $token->type()) {
next($tokens);
$type = $this->parseExtensionType($tokens);
} else {
$type = $this->parseTypeName($tokens);
}
Expand Down Expand Up @@ -245,6 +250,33 @@ protected function parseTypeName(array &$tokens)
return new MixedType;
}

/**
* array<integer,Token> &$tokens
*
* @return ExtensionType
*/
protected function parseExtensionType(array &$tokens)
{
$this->consumeWhitespace($tokens);

$token = $this->assert($tokens, Token::TOKEN_STRING);

next($tokens);

$this->consumeWhitespace($tokens);

if ($this->currentTokenIsType($tokens, Token::TOKEN_BRACE_OPEN)) {
$attributes = $this->parseAttributes($tokens, ':' . $token->content());
} else {
$attributes = array();
}

return new ExtensionType(
ClassName::fromString($token->content()),
$attributes
);
}

/**
* array<integer,Token> &$tokens
*
Expand Down Expand Up @@ -356,12 +388,12 @@ protected function parseTypeList(array &$tokens, Closure $commaCallback = null)

/**
* @param array<integer,Token> &$tokens
* @param string $typeName
* @param array<string> $supportedAttributes
* @param string $typeName
* @param array<string>|null $supportedAttributes
*
* @return array
*/
protected function parseAttributes(array &$tokens, $typeName, array $supportedAttributes)
protected function parseAttributes(array &$tokens, $typeName, array $supportedAttributes = null)
{
$this->consumeWhitespace($tokens);
$this->assert($tokens, Token::TOKEN_BRACE_OPEN);
Expand All @@ -371,7 +403,7 @@ protected function parseAttributes(array &$tokens, $typeName, array $supportedAt
$attributes = $this->parseHashContents(
$tokens,
function($attribute) use (&$tokens, $typeName, $supportedAttributes) {
if (!in_array($attribute, $supportedAttributes)) {
if ($supportedAttributes !== null && !in_array($attribute, $supportedAttributes)) {
throw new Exception\UnsupportedAttributeException(
$typeName,
$attribute,
Expand Down
47 changes: 47 additions & 0 deletions src/Eloquent/Typhax/Type/ExtensionType.php
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the Typhax package.
*
* Copyright © 2012 Erin Millard
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Eloquent\Typhax\Type;

use Eloquent\Cosmos\ClassName;
use Icecave\Visita\Host;

class ExtensionType extends Host implements Type
{
/**
* @param ClassName $className
* @param array $attributes
*/
public function __construct(ClassName $className, array $attributes)
{
$this->className = $className;
$this->attributes = $attributes;
}

/**
* @return ClassName
*/
public function className()
{
return $this->className;
}

/**
* @return array
*/
public function attributes()
{
return $this->attributes;
}

private $className;
private $attributes;
}
30 changes: 30 additions & 0 deletions test/suite/Eloquent/Typhax/Lexer/LexerTest.php
Expand Up @@ -382,6 +382,36 @@ public function tokenData()
);
$data[] = array($expected, $source);

// #17: Basic extension type
$source = ':Foo';
$expected = array(
new Token(Token::TOKEN_COLON, ':'),
new Token(Token::TOKEN_STRING, 'Foo'),
);
$data[] = array($expected, $source);

// #18: Namespaced extension type
$source = ':Foo\Bar\Baz';
$expected = array(
new Token(Token::TOKEN_COLON, ':'),
new Token(Token::TOKEN_STRING, 'Foo\Bar\Baz'),
);
$data[] = array($expected, $source);

// #19: Namespaced extension type with attributes
$source = ':Foo\Bar\Baz{foo: bar}';
$expected = array(
new Token(Token::TOKEN_COLON, ':'),
new Token(Token::TOKEN_STRING, 'Foo\Bar\Baz'),
new Token(Token::TOKEN_BRACE_OPEN, '{'),
new Token(Token::TOKEN_STRING, 'foo'),
new Token(Token::TOKEN_COLON, ':'),
new Token(Token::TOKEN_WHITESPACE, ' '),
new Token(Token::TOKEN_STRING, 'bar'),
new Token(Token::TOKEN_BRACE_CLOSE, '}'),
);
$data[] = array($expected, $source);

return $data;
}

Expand Down
17 changes: 14 additions & 3 deletions test/suite/Eloquent/Typhax/Parser/ParserTest.php
Expand Up @@ -17,6 +17,7 @@
use Eloquent\Typhax\Type\ArrayType;
use Eloquent\Typhax\Type\BooleanType;
use Eloquent\Typhax\Type\CallableType;
use Eloquent\Typhax\Type\ExtensionType;
use Eloquent\Typhax\Type\FloatType;
use Eloquent\Typhax\Type\IntegerType;
use Eloquent\Typhax\Type\MixedType;
Expand Down Expand Up @@ -251,6 +252,16 @@ public function parserData()
$expected = new ObjectType(ClassName::fromString('foo'));
$data["Don't parse past end of type"] = array($expected, $position, $source);

$source = ' : Foo\Bar ';
$position = 12;
$expected = new ExtensionType(ClassName::fromString('Foo\Bar'), array());
$data["Parse extension type"] = array($expected, $position, $source);

$source = ' : Foo\Bar { foo: bar }';
$position = 24;
$expected = new ExtensionType(ClassName::fromString('Foo\Bar'), array('foo' => 'bar'));
$data["Parse extension type with attributes"] = array($expected, $position, $source);

return $data;
}

Expand All @@ -273,19 +284,19 @@ public function parserFailureData()
// #0: Empty string
$source = '';
$expectedClass = __NAMESPACE__.'\Exception\UnexpectedTokenException';
$expectedMessage = 'Unexpected END at position 0. Expected one of STRING, TYPE_NAME, NULL.';
$expectedMessage = 'Unexpected END at position 0. Expected one of STRING, TYPE_NAME, NULL, COLON.';
$data[] = array($expectedClass, $expectedMessage, $source);

// #1: Whitespace string
$source = ' ';
$expectedClass = __NAMESPACE__.'\Exception\UnexpectedTokenException';
$expectedMessage = 'Unexpected END at position 2. Expected one of STRING, TYPE_NAME, NULL.';
$expectedMessage = 'Unexpected END at position 2. Expected one of STRING, TYPE_NAME, NULL, COLON.';
$data[] = array($expectedClass, $expectedMessage, $source);

// #2: Empty type list
$source = ' foo < > ';
$expectedClass = __NAMESPACE__.'\Exception\UnexpectedTokenException';
$expectedMessage = 'Unexpected GREATER_THAN at position 8. Expected one of STRING, TYPE_NAME, NULL.';
$expectedMessage = 'Unexpected GREATER_THAN at position 8. Expected one of STRING, TYPE_NAME, NULL, COLON.';
$data[] = array($expectedClass, $expectedMessage, $source);

// #3: Empty attributes
Expand Down
29 changes: 29 additions & 0 deletions test/suite/Eloquent/Typhax/Type/ExtensionTypeTest.php
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the Typhax package.
*
* Copyright © 2012 Erin Millard
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Eloquent\Typhax\Type;

use Eloquent\Cosmos\ClassName;
use PHPUnit_Framework_TestCase;

class ExtensionTypeTest extends PHPUnit_Framework_TestCase
{
public function testExtensionType()
{
$className = ClassName::fromString('foo');
$attributes = array('foo' => 'bar');

$type = new ExtensionType($className, $attributes);

$this->assertSame($className, $type->className());
$this->assertSame($attributes, $type->attributes());
}
}

0 comments on commit b46f13a

Please sign in to comment.