Skip to content
Browse files

Implemented extension types (fixes #33).

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...
1 parent 76d386b commit 40517a0d0e3f9bc8eb70e418e271e893e4bd7f96 @jmalloc jmalloc committed
View
40 src/Eloquent/Typhax/Parser/Parser.php
@@ -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;
@@ -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);
}
@@ -248,6 +253,33 @@ protected function parseTypeName(array &$tokens)
/**
* 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
+ *
* @return ResourceType
*/
protected function parseResourceType(array &$tokens)
@@ -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);
@@ -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,
View
47 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;
+}
View
30 test/suite/Eloquent/Typhax/Lexer/LexerTest.php
@@ -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;
}
View
17 test/suite/Eloquent/Typhax/Parser/ParserTest.php
@@ -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;
@@ -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;
}
@@ -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
View
29 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 40517a0

Please sign in to comment.
Something went wrong with that request. Please try again.