|
14 | 14 |
|
15 | 15 | --- |
16 | 16 |
|
17 | | -Reference implementation for TypeLang Parser. |
| 17 | +The reference parser for **TypeLang** — a declarative type language inspired by |
| 18 | +static analyzers like [PHPStan](https://phpstan.org/) and [Psalm](https://psalm.dev/docs/). |
18 | 19 |
|
19 | | -**TypeLang** is a declarative type language inspired by static analyzers |
20 | | -like [PHPStan](https://phpstan.org/) and [Psalm](https://psalm.dev/docs/). |
| 20 | +It reads a type declaration string and builds an AST of `TypeLang\Type\*` nodes, |
| 21 | +checking the grammar along the way. The node classes themselves live in the |
| 22 | +separate [`type-lang/types`](https://packagist.org/packages/type-lang/types) package. |
21 | 23 |
|
22 | | -Read [documentation pages](https://typelang.dev) for more information. |
| 24 | +- Full documentation is [available at typelang.dev](https://typelang.dev). |
| 25 | +- Language specification is [available here](https://typelang.dev/static/spec.html). |
23 | 26 |
|
24 | 27 | ## Installation |
25 | 28 |
|
26 | | -TypeLang Parser is available as Composer repository and can be installed |
27 | | -using the following command in a root of your project: |
| 29 | +Install the package via [Composer](https://getcomposer.org): |
28 | 30 |
|
29 | 31 | ```sh |
30 | 32 | composer require type-lang/parser |
31 | 33 | ``` |
32 | 34 |
|
33 | | -## Quick Start |
| 35 | +**Requirements:** |
| 36 | +- PHP 8.4+ |
| 37 | + |
| 38 | +## Usage |
| 39 | + |
| 40 | +`TypeLang\Parser\TypeParser` is the entry point. Its `parse()` method turns a |
| 41 | +type declaration into a `TypeLang\Type\TypeNode`: |
34 | 42 |
|
35 | 43 | ```php |
36 | | -$parser = new \TypeLang\Parser\TypeParser(); |
| 44 | +$parser = new TypeLang\Parser\TypeParser(); |
37 | 45 |
|
38 | | -$type = $parser->parse(<<<'PHP' |
39 | | - array{ |
40 | | - key: callable(Example, int): mixed, |
41 | | - ... |
42 | | - } |
43 | | - PHP); |
| 46 | +$type = $parser->parse('array{ key: int }'); |
44 | 47 |
|
45 | 48 | var_dump($type); |
| 49 | +// object(TypeLang\Type\NamedTypeNode) { |
| 50 | +// ["name"] => "array" |
| 51 | +// ["fields"] => object(TypeLang\Type\Shape\FieldsListNode) { ... } |
| 52 | +// ... |
| 53 | +// } |
46 | 54 | ``` |
47 | 55 |
|
48 | | -Expected Output: |
| 56 | +Every node exposes an `$offset` (byte offset in the source) plus a handful of |
| 57 | +properties describing its kind. |
| 58 | + |
| 59 | +### Strict vs. Tolerant Parsing |
| 60 | + |
| 61 | +- `parse(): TypeNode` — strict mode; requires the whole input to be a valid |
| 62 | + type and throws a `ParserExceptionInterface` on the first error. |
| 63 | +- `parseTolerant(): ParsedResult` — parses as much as it can and reports how far |
| 64 | + it consumed the source. Useful for phpdoc, where a type is followed by a |
| 65 | + free-text description. |
49 | 66 |
|
50 | 67 | ```php |
51 | | -TypeLang\Type\NamedTypeNode { |
52 | | - +offset: 0 |
53 | | - +name: TypeLang\Type\Name { |
54 | | - +offset: 0 |
55 | | - -parts: array:1 [ |
56 | | - 0 => TypeLang\Type\Identifier { |
57 | | - +offset: 0 |
58 | | - +value: "array" |
59 | | - } |
60 | | - ] |
61 | | - } |
62 | | - +arguments: null |
63 | | - +fields: TypeLang\Type\Shape\FieldsListNode { |
64 | | - +offset: 11 |
65 | | - +items: array:1 [ |
66 | | - 0 => TypeLang\Type\Shape\NamedFieldNode { |
67 | | - +offset: 11 |
68 | | - +type: TypeLang\Type\CallableTypeNode { |
69 | | - +offset: 16 |
70 | | - +name: TypeLang\Type\Name { |
71 | | - +offset: 16 |
72 | | - -parts: array:1 [ |
73 | | - 0 => TypeLang\Type\Identifier { |
74 | | - +offset: 16 |
75 | | - +value: "callable" |
76 | | - } |
77 | | - ] |
78 | | - } |
79 | | - +parameters: TypeLang\Type\Callable\ParametersListNode { |
80 | | - +offset: 25 |
81 | | - +items: array:2 [ |
82 | | - 0 => TypeLang\Type\Callable\ParameterNode { |
83 | | - +offset: 25 |
84 | | - +type: TypeLang\Type\NamedTypeNode { |
85 | | - +offset: 25 |
86 | | - +name: TypeLang\Type\Name { |
87 | | - +offset: 25 |
88 | | - -parts: array:1 [ |
89 | | - 0 => TypeLang\Type\Identifier { |
90 | | - +offset: 25 |
91 | | - +value: "Example" |
92 | | - } |
93 | | - ] |
94 | | - } |
95 | | - +arguments: null |
96 | | - +fields: null |
97 | | - } |
98 | | - +name: null |
99 | | - +output: false |
100 | | - +variadic: false |
101 | | - +optional: false |
102 | | - } |
103 | | - 1 => TypeLang\Type\Callable\ParameterNode { |
104 | | - +offset: 34 |
105 | | - +type: TypeLang\Type\NamedTypeNode { |
106 | | - +offset: 34 |
107 | | - +name: TypeLang\Type\Name { |
108 | | - +offset: 34 |
109 | | - -parts: array:1 [ |
110 | | - 0 => TypeLang\Type\Identifier { |
111 | | - +offset: 34 |
112 | | - +value: "int" |
113 | | - } |
114 | | - ] |
115 | | - } |
116 | | - +arguments: null |
117 | | - +fields: null |
118 | | - } |
119 | | - +name: null |
120 | | - +output: false |
121 | | - +variadic: false |
122 | | - +optional: false |
123 | | - } |
124 | | - ] |
125 | | - } |
126 | | - +type: TypeLang\Type\NamedTypeNode { |
127 | | - +offset: 40 |
128 | | - +name: TypeLang\Type\Name { |
129 | | - +offset: 40 |
130 | | - -parts: array:1 [ |
131 | | - 0 => TypeLang\Type\Identifier { |
132 | | - +offset: 40 |
133 | | - +value: "mixed" |
134 | | - } |
135 | | - ] |
136 | | - } |
137 | | - +arguments: null |
138 | | - +fields: null |
139 | | - } |
140 | | - } |
141 | | - +optional: false |
142 | | - +key: TypeLang\Type\Identifier { |
143 | | - +offset: 11 |
144 | | - +value: "key" |
145 | | - } |
146 | | - } |
147 | | - ] |
148 | | - +sealed: false |
149 | | - } |
150 | | -} |
| 68 | +$result = $parser->parseTolerant('int and some trailing description'); |
| 69 | + |
| 70 | +$result->type; // TypeLang\Type\NamedTypeNode ("int") |
| 71 | +$result->offset; // offset where parsing stopped |
151 | 72 | ``` |
| 73 | + |
| 74 | +See the [documentation](https://typelang.dev) for feature toggling, visitors and |
| 75 | +name resolution. |
0 commit comments