|
1 | 1 | # maml-php |
2 | 2 |
|
3 | | -A modern, well‑tested implementation of the [MAML](https://maml.dev) data format for PHP. |
| 3 | +[MAML](https://maml.dev) parser for PHP. Includes a full AST with source positions, comment preservation, and pretty printing. |
4 | 4 |
|
5 | | -- Spec‑accurate parser and pretty serializer |
| 5 | +- Spec-accurate parser and serializer |
| 6 | +- Full AST with source positions (offset, line, column) on every node |
| 7 | +- Comments preserved and attached to nearest nodes |
| 8 | +- `printAst()` reconstructs source from AST, including comments |
| 9 | +- `errorSnippet()` for user-friendly error messages pointing at source locations |
6 | 10 | - Zero dependencies |
7 | | -- 100% test coverage (classes, methods, lines) |
| 11 | +- 100% test coverage |
8 | 12 |
|
9 | 13 | ## Installation |
10 | 14 |
|
11 | 15 | ``` |
12 | 16 | composer require maml/maml |
13 | 17 | ``` |
14 | 18 |
|
15 | | -## Usage |
| 19 | +Requires PHP 8.2+ with `mbstring`. |
| 20 | + |
| 21 | +## Quick Start |
16 | 22 |
|
17 | 23 | ```php |
18 | 24 | use Maml\Maml; |
19 | 25 |
|
20 | | -$data = Maml::parse('{ |
21 | | - project: "MAML" |
22 | | - tags: [ |
23 | | - "minimal" |
24 | | - "readable" |
25 | | - ] |
26 | | - |
27 | | - # A simple nested object |
28 | | - spec: { |
29 | | - version: 1 |
30 | | - author: "Anton Medvedev" |
31 | | - } |
32 | | - |
33 | | - notes: """ |
34 | | -This is a raw multiline string. |
35 | | -Keeps formatting as‑is. |
36 | | -""" |
37 | | -}'); |
38 | | - |
39 | | -echo $data['project']; // "MAML" |
40 | | - |
41 | | -$text = Maml::stringify(['foo' => 'bar', 'list' => [1, 2, 3]]); |
42 | | -/* |
43 | | -{ |
44 | | - foo: "bar" |
45 | | - list: [ |
46 | | - 1 |
47 | | - 2 |
48 | | - 3 |
49 | | - ] |
50 | | -} |
51 | | -*/ |
| 26 | +// Parse to plain PHP values |
| 27 | +$data = Maml::parse('{name: "MAML", version: 1}'); |
| 28 | +$data['name']; // "MAML" |
| 29 | + |
| 30 | +// Serialize back to MAML |
| 31 | +Maml::stringify(['name' => 'MAML', 'version' => 1]); |
| 32 | +// { |
| 33 | +// name: "MAML" |
| 34 | +// version: 1 |
| 35 | +// } |
| 36 | +``` |
| 37 | + |
| 38 | +## AST |
| 39 | + |
| 40 | +```php |
| 41 | +$source = '{ |
| 42 | + # Database config |
| 43 | + host: "localhost" |
| 44 | + port: 5432 |
| 45 | +}'; |
| 46 | + |
| 47 | +$doc = Maml::parseAst($source); |
| 48 | +``` |
| 49 | + |
| 50 | +Every node has a `type` string and a `span` with start/end positions: |
| 51 | + |
| 52 | +```php |
| 53 | +$doc->value->type; // "Object" |
| 54 | +$doc->value->span->start->line; // 1 |
| 55 | +$doc->value->properties[0]->key->value; // "host" |
| 56 | +``` |
| 57 | + |
| 58 | +### Printing |
| 59 | + |
| 60 | +`printAst()` reconstructs MAML source from an AST, preserving comments and blank lines: |
| 61 | + |
| 62 | +```php |
| 63 | +Maml::printAst($doc); |
| 64 | +// { |
| 65 | +// # Database config |
| 66 | +// host: "localhost" |
| 67 | +// port: 5432 |
| 68 | +// } |
| 69 | +``` |
| 70 | + |
| 71 | +### Converting to plain values |
| 72 | + |
| 73 | +`toValue()` strips AST metadata and returns plain PHP values: |
| 74 | + |
| 75 | +```php |
| 76 | +Maml::toValue($doc); // ["host" => "localhost", "port" => 5432] |
| 77 | +``` |
| 78 | + |
| 79 | +### Error snippets |
| 80 | + |
| 81 | +Point at any AST node in source for user-friendly error messages: |
| 82 | + |
| 83 | +```php |
| 84 | +$node = $doc->value->properties[1]->value; |
| 85 | +Maml::errorSnippet($source, $node->span->start, 'Port out of range'); |
| 86 | +// Port out of range on line 4. |
| 87 | +// |
| 88 | +// port: 5432 |
| 89 | +// ........^ |
52 | 90 | ``` |
53 | 91 |
|
| 92 | +## Node Types |
| 93 | + |
| 94 | +| Node | `type` | `value` | `raw` | |
| 95 | +|------------|----------------|----------|-------| |
| 96 | +| String | `"String"` | `string` | yes | |
| 97 | +| Raw String | `"RawString"` | `string` | yes | |
| 98 | +| Integer | `"Integer"` | `int` | yes | |
| 99 | +| Float | `"Float"` | `float` | yes | |
| 100 | +| Boolean | `"Boolean"` | `bool` | -- | |
| 101 | +| Null | `"Null"` | `null` | -- | |
| 102 | +| Object | `"Object"` | `properties: Property[]` | -- | |
| 103 | +| Array | `"Array"` | `elements: Element[]` | -- | |
| 104 | + |
| 105 | +Object keys are either `IdentifierKey` for bare keys like `host`, or `StringNode` for quoted keys like `"host name"`. |
| 106 | + |
| 107 | +### Comments |
| 108 | + |
| 109 | +Comments are attached to the nearest node: |
| 110 | + |
| 111 | +- **`Property.leadingComments`** / **`Element.leadingComments`** -- comments on lines before |
| 112 | +- **`Property.trailingComment`** / **`Element.trailingComment`** -- comment on the same line after |
| 113 | +- **`ObjectNode.danglingComments`** / **`ArrayNode.danglingComments`** -- comments inside empty containers or after the last entry |
| 114 | +- **`Document.leadingComments`** / **`Document.danglingComments`** -- comments before/after the root value |
| 115 | + |
| 116 | +### Blank lines |
| 117 | + |
| 118 | +`Property.emptyLineBefore` and `Element.emptyLineBefore` are `true` when there is a blank line separating from the previous entry. |
| 119 | + |
54 | 120 | ## License |
55 | 121 |
|
56 | 122 | [MIT](LICENSE) |
0 commit comments