@@ -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.
0 commit comments