Skip to content

Commit eb694b5

Browse files
committed
Actualize README
1 parent 3b819cc commit eb694b5

2 files changed

Lines changed: 184 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,185 @@ $ composer require ffi/preprocessor
2929

3030
## Usage
3131

32-
TODO
32+
`Preprocessor::process()` expands the macros and executes the directives of a C source,
33+
the result is a stringable `Result`.
34+
35+
```php
36+
use FFI\Preprocessor\Preprocessor;
37+
38+
$pre = new Preprocessor();
39+
40+
echo $pre->process(<<<'C'
41+
#define SQUARE(x) ((x) * (x))
42+
43+
int area = SQUARE(4);
44+
C);
45+
46+
// int area = ((4) * (4));
47+
```
48+
49+
Method `process()` accepts:
50+
- `SplFileInfo` - file
51+
- `string` - the source text
52+
- `resource` - stream
53+
- `Phplrt\Contracts\Source\ReadableInterface` - any other source object
54+
55+
56+
### Result
57+
58+
Besides the output, the result tells what the run has learned. Each getter reads
59+
the output to the end first, and the result can be read more than once.
60+
61+
```php
62+
$result = $pre->process("#define A 1\n#warning careful\n");
63+
64+
$result->hasMacro('A'); // true, the macros as they are when the run ends
65+
$result->findMacro('A'); // MacroInterface|null
66+
$result->getMacros(); // iterable<MacroInterface>
67+
68+
$result->getFiles(); // the first file and the included ones, each once
69+
$result->getDiagnostics(); // the messages of #error and #warning
70+
71+
72+
echo $result; // preprocessed result
73+
```
74+
75+
The preprocessor itself is not changed by a run: `#define` and `#undef` of the
76+
source affect the result only.
77+
78+
79+
### Macros
80+
81+
The `$pre->macros` holds the macros every run starts with.
82+
83+
```php
84+
$pre->macros->define('DEBUG'); // #define DEBUG 1
85+
$pre->macros->define('LEVEL', '3'); // #define LEVEL 3
86+
$pre->macros->remove('DEBUG'); // #undef DEBUG
87+
88+
89+
// A closure with no parameters is an object-like macro, evaluated at each replacement
90+
$pre->macros->define('REQUEST_ID', fn() => uniqid());
91+
92+
// A closure with parameters is a function-like macro, it gets the arguments as written
93+
$pre->macros->define('MAX', fn(string $a, string $b): string
94+
=> "(($a) > ($b) ? ($a) : ($b))"
95+
);
96+
```
97+
98+
> The `__DATE__` and `__TIME__` are taken from a [PSR-20](https://www.php-fig.org/psr/psr-20/)
99+
> clock, the system one by default.
100+
>
101+
> ```php
102+
> $pre = new Preprocessor(clock: $myClock);
103+
> ```
104+
105+
106+
### File System
107+
108+
`#include` and `#embed` search for files in the file system of the preprocessor.
109+
110+
```php
111+
use Phplrt\Source\VirtualSource;
112+
113+
$pre = new Preprocessor();
114+
115+
// Mount a real directory, `#include <vendor/header.h>` is searched in it
116+
$pre->files->addDirectory('/usr/include', '/');
117+
118+
// Or define a file
119+
$pre->files->addFile('/config.h', VirtualSource::createFromString('config.h', '#define VERSION 2'));
120+
```
121+
122+
123+
### Diagnostics
124+
125+
Ane `#error` and `#warning` are reported to a listener. By default, an error
126+
stops the run with `DiagnosticException` and a warning is skipped.
127+
128+
```php
129+
use FFI\Preprocessor\Diagnostic\DiagnosticListener\StreamDiagnosticListener;
130+
131+
$pre = new Preprocessor(diagnostic: new StreamDiagnosticListener(STDERR));
132+
133+
// {entrypoint}:1:1: warning: #warning careful
134+
```
135+
136+
The ready listeners are:
137+
- `ThrowingDiagnosticListener` - throws errors
138+
- `StreamDiagnosticListener` - writes diagnostic into the stream
139+
- `LoggerDiagnosticListener` - A PSR-3 logger
140+
- `NullDiagnosticListener` - does nothing
141+
- `CompoundListener` - combines several into one
142+
143+
144+
### Directives
145+
146+
Every directive has a handler, `$pre->directives` holds them. Add a
147+
directive of your own or replace a standard one.
148+
149+
```php
150+
use FFI\Preprocessor\Directive\DirectiveContext;
151+
use FFI\Preprocessor\Directive\DirectiveHandlerInterface;
152+
use FFI\Preprocessor\Runtime\Chunk\TaggedChunk\DirectiveChunk;
153+
use FFI\Preprocessor\Runtime\Chunk\TaggedChunk\GeneratedChunk;
154+
155+
$pre->directives->add('note', new class implements DirectiveHandlerInterface {
156+
public function handle(DirectiveChunk $directive, DirectiveContext $context): iterable
157+
{
158+
yield new GeneratedChunk("/* {$directive->body} */\n", $directive->source, 0);
159+
}
160+
});
161+
162+
// #note hi => /* hi */
163+
```
164+
165+
The context gives the handler the macros, the file system, the diagnostic
166+
listener and the executor that preprocesses an included file.
167+
168+
169+
#### Pragmas
170+
171+
`#pragma once` is executed, any other pragma is passed to the output. A pragma
172+
is executed by a processor, add one to react to a pragma of your own.
173+
174+
```php
175+
use FFI\Preprocessor\Directive\Handler\PragmaDirectiveHandler;
176+
use FFI\Preprocessor\Directive\Handler\PragmaDirectiveHandler\OncePragmaProcessor;
177+
use FFI\Preprocessor\Directive\Handler\PragmaDirectiveHandler\PragmaProcessorInterface;
178+
179+
final class PackPragmaProcessor implements PragmaProcessorInterface
180+
{
181+
public function supports(string $body): bool
182+
{
183+
return \str_starts_with($body, 'pack');
184+
}
185+
186+
public function apply(DirectiveChunk $directive, DirectiveContext $context): iterable
187+
{
188+
return []; // `#pragma pack(...)` leaves nothing in the output
189+
}
190+
}
191+
192+
$pre->directives->replace('pragma', new PragmaDirectiveHandler([
193+
new OncePragmaProcessor(),
194+
new PackPragmaProcessor(),
195+
]));
196+
```
197+
198+
## Limitations
199+
200+
List of preprocessor limitations. May be added to in the future.
201+
202+
### Directives
203+
204+
- `#line` is not supported, `__FILE__` and `__LINE__` always tell the real file and line.
205+
- `#embed` has no parameters (`limit`, `prefix`, `suffix`, `if_empty`), and `__has_embed` is not supported.
206+
- `#pragma once` recognizes a file by its pathname, the same file included by different paths is not detected.
207+
208+
### Other Features
209+
210+
- `_Pragma` operator is not supported.
211+
- `__VA_OPT__` is not supported.
212+
- `__COUNTER__` is not defined.
213+
- `__DATE__` and `__TIME__` are taken from the clock at each replacement, not once per run.

‎composer.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"phplrt/exception": "^4.2",
2323
"phplrt/position": "^4.2",
2424
"phplrt/runtime": "^4.2",
25-
"psr/clock": "^1.0"
25+
"psr/clock": "^1.0",
26+
"psr/log": "^3.0"
2627
},
2728
"suggest": {
2829
"psr/log": "To pass the diagnostics to a PSR-3 logger (^1.1|^2.0|^3.0)"

0 commit comments

Comments
 (0)