phrep

Check-in [6bd3a9efb0]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Update a few comments, release as 0.3.0.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | trunk | 0.3.0
Files: files | file ages | folders
SHA1:6bd3a9efb0a6ce0315d82adc1bade6929dfef701
User & Date: mario 2015-05-29 12:50:10
Context
2015-05-29
12:50
Update a few comments, release as 0.3.0. Leaf check-in: 6bd3a9efb0 user: mario tags: trunk, 0.3.0
2015-04-28
23:20
Added a few silly development tests. check-in: 58129a2013 user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Makefile.

12
13
14
15
16
17
18
19

20
21
22
23
24
25
26
phrep:
	php $(CLI) -D PKG_COMBINED -i $(CLI) -f -o $(BIN)
	chmod +x $(BIN)

# Update version number from PMD to const= declaration
# (The library version for now diverts from the CLI frontend.)
version:
	which version && version -read $(LIB) -incr -write::2 $(LIB) -write $(CLI)


# Rebuild man page
man:
	a2x --format manpage phrep.1.txt;
	man ./phrep.1 

# Generate DEB/RPM/PHAR







|
>







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
phrep:
	php $(CLI) -D PKG_COMBINED -i $(CLI) -f -o $(BIN)
	chmod +x $(BIN)

# Update version number from PMD to const= declaration
# (The library version for now diverts from the CLI frontend.)
version:
	which version && version -read $(LIB)  -write::2 $(LIB) -write $(CLI)
	#-incr

# Rebuild man page
man:
	a2x --format manpage phrep.1.txt;
	man ./phrep.1 

# Generate DEB/RPM/PHAR

Changes to NEWS.

1



















2
3
4
5
6
7
8




















0.2.0 (2015-02-26)
------------------

* Added `multipass` support for constants and macro-in-macro expansion.
* Introduced `#include CONSTANT_NAME_H` constructs.
* New pragmas to disable either constants, macros or complex replacing.
* Added a few sample config files, a trivial Makefile, Phing binding

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

0.3.0 (2015-05-29)
------------------
 * Fix array += adjoining for pragma options.
 * Only redirect STDOUT for failing tests.
 * A few more development tests were added.
 * Add `qw@` macro for simple list generation from literal words.
 * Document special -i and -o flag values, and new --target .php flag.
 * Cleanup allowed function checks in expression evaluator,
   slightly stricter interpretation of operators, reworked precedences,
   add ternary ?: conditional support.
 * Built in some io\Token macro helper functions.
 * Made regex mode speedier with prejoined list of (MACRO|AND|CONST|NAMES@).
 * Add new #pragma(php_strip_tags=1).
 * Simplify NULLSAFE@ expression.
 * Reworked tokenizer macro processing and interfaces. Key and substition
   code lookup now internal to replacement logic. Parenthesis are now
   recognized as part of $token list for both token and regex modes.
 * Argument lists for complex macros are no longer pre-split on , commas.

0.2.0 (2015-02-26)
------------------

* Added `multipass` support for constants and macro-in-macro expansion.
* Introduced `#include CONSTANT_NAME_H` constructs.
* New pragmas to disable either constants, macros or complex replacing.
* Added a few sample config files, a trivial Makefile, Phing binding

Changes to macro.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

55
56
57
58

59
60
61

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
...
216
217
218
219
220
221
222

223
224
225
226
227
228
229
...
254
255
256
257
258
259
260

261
262
263

264
265
266
267
268
269
270
...
300
301
302
303
304
305
306

307
308
309
310
311
312
313
...
333
334
335
336
337
338
339

340
341
342
343
344
345
346
...
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
...
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921

922
923
924
925
926
927
928
....
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
<?php
/**
 * api: php
 * title: PHP Macro Preprocessor
 * description: Context-sensitive C-style #define/macro directive processing.
 * license: MITL
 * version: 0.2.9
 * state: beta
 * depends: php (>= 5.4.0)
 * type: rewrite
 * category: macro
 * url: http://fossil.include-once.org/phrep/
 * pack: macro.php=MacroProcessor.php
 *
 *
 * Preprocessor to apply C-style #define, #ifdef, #include statements. Intended
 * as build helper and complex macro injector. It's language-specific to PHP,
 * but can consume most common C header #defines.       
 *
 * Source code/file transformation works in two phases:
 *
 *   1.  All #define, #include, #if* directives are expanded in a regex splitting
 *       state machine. Which joins includes, or omits conditional sections. This

 *       step is syntax-insensitive, but strips open/close tokens from injected
 *       files, and searches *.ph/.php/.inc/.h includes.
 *
 *   2.  Constants and macros are applied on the accumulated source code using
 *       a tokenized representation. This allows context-sensitive expansion of
 *       defined preprocessor constants, standard macros, and even more complex
 *       transformation callbacks - for constructs such as IFSET@(expr).

 *
 * Directives take the form `#define X Y` and must be noted leftmost in source
 * files. Following preprocessor directives are known:
 *
 *     #define CONST 1.2.3
 *     #ifdef CONST
 *     #include <filename.ph>
 *     #elif CONST < 1.0 || CONST >= 2.0
 *     #undef CONST
 *     #define MACRO(x,y) (x+y*2)
 *     #stderr Print some message
 *     #endif
 *     #pragma(output=0)
 *     #srcout inject_literal($source);
 *     #macro VA@(fn,expr...) {return "$fn([$expr])";}
 *
 * For compatibility with preprocess.py there's also support for doubly-commented
 * directives such as:
 *
 *     # #include <linux/errno.h>
 *     // #define X Y
 *
 * They're even valid with one ␣ space before the init // comment, and up to two
 * spaces ␣␣ before the actual #directive name. Which allows minimal structuring.

 *
 * Various #pragma(options=...) can control processing and substitution. For
 * example, output=0 allows to suppress #included file source code. Or use the
 * crude interpolate=regex mode for constant replacements, disable macros=0 etc.

 *
 * Per default #include scripts are stripped off opening <?php and closing PHP
 * tags. And literal C header .h files have content output disabled per default.

 *
 */

#ifndef OMIT_NAMESPACE
namespace io
{
use \stdClass, \ArrayAccess, \SplStack,
    \Exception, \OutOfBoundsException;
#endif


/**
 * MacroProcessor assembles the input source file with all its #include deps,
 * and handles conditional sections (via MacroExpression).
 * 
 *  · Main invocation method is ->on($filename). Which is meant/named as simple
 *    array_map() callback for file lists.
 *    Can also be pre-invoked to parse any includes.h, but discard output.
 *
 *  · Basic source assembly could alternatively be run via ->block($source).
 *
 * After the complete source code has been collected, passes over macro and
 * constant expansion to MacroInjector for tokenizer/regex substitutions.
 *
 */
................................................................................
        "interpolate" => "token", # Or "regex" mode, or "erb", "phpp", or "delim << >>" for custom delimiters.
        "dirs" => NULL,           # Append include -I search paths.
        "preserve" => "output,file,line",  # Pragma states to retain between #includes
        "strip_php_tags" => 1,    # Remove <?php open and close tokens from #include files
    ];

    // Updated via versionnum
    const PHREP_VERSION = "0.2.9";

    /**
     * Predefined and collected constants and macros.
     */
    var $defines = [

       #-- Predeclare some C-style constants
................................................................................
    public function stderr(/*int*/$errno, /*string*/$msg, $file="macro.php", $line=0, $context=[]) {
        if ($this->pragma["fail"]) {
            throw new Exception($msg, $errno);
        }
        elseif ($this->pragma["quiet"]) {
            return;
        }

        $msg = "\e[1;41m$msg\e[0m";
        if ($errno) {
            $msg = "\e[33;1m[$errno]\e[0m $msg";
        }
        if (1||$this->pragma["file"]) {
            $msg .= " (\e[2mprocessing \e[0;33m{$this->pragma['file']}:{$this->pragma['line']}\e[0m)";
        }
................................................................................

        // Read main file, process #directives between regex-split source blocks
        $this->pragma["input"] = $fn;
        $this->pragma["file"] = $fn;
        $output = $this->block(file_get_contents($fn));

        // Apply macros and constants in tokenization phase

        if ($this->pragma["substitution"] && ($this->pragma["constants"] || $this->pragma["macros"] || $this->pragma["complex"])) {
            $inj = new MacroInjector($this->filter_defines($this->defines), $this->pragma["multipass"]);
            $output = $inj->substitute($output, $this->pragma["interpolate"]);

        }
        
        restore_error_handler();        
        return $output;
    }


................................................................................
            $directive = $block[0];
            $args = trim(preg_replace("/\\s*\\\\(\R)/", "$1", $block[1]));
            $pragma_lines = substr_count($block[1], "\n");

            // Dispatch or handle #directives
            switch ($directive) {


                case "if": 
                case "ifdef": 
                case "ifndef":
                    if ($¶->act) {
                        if ($directive == "if") {
                            $args = MacroExpression::strval($args, $this);
                        }
................................................................................
                    }
                    break;

                case "endif":
                    $¶->done();
                    break;


                case "macro": $directive = "define";
                case "define":
                case "undef":
                case "include":
                case "stderr":
                case "srcout":
                case "pragma":
................................................................................

    /**
     * Custom or predefined delimiters around constant/macro names.
     *
     */
    function rx_delim($delim) {
        $rx = [
            "wbnd" => ["(?<!\w)", "(?!\w)"],
            "erb"  => ["\Q%{\E", "\Q}\E"],
            "phpb" => ["\Q{{{\E", "\Q}}}\E"],
        ];
        if (!empty($delim[3])) {
            return ["\Q$delim[2]\E", "\Q$delim[3]\E"];
        }
        elseif (isset($rx[$delim[1]])) {
            return $rx[$delim[1]];
        }
................................................................................
        $this[0] = [$name => $value] + $this[0];
    }
}



/**
 * #if/#elif expression evaluation. Permits common arithmetics, #define lookups,
 * a few hand-picked PHP callbacks, and decimal/float/tuple comparisons.
 * It's neither fully CPP compatible, nor in line with preprocess.py; and it's
 * likely pointless featuritis anyway.
 *
 * Uses a trivial shunting-yard sorting method for its tokenized input, then
 * evaluates expressions down to a numeric or booleanish scalar. Has only minimal
 * error reporting, and operator/func precedence is incomplete/untuned..

 *
 */
class MacroExpression {

    # Expression tokens
    const RX_EXPR = "{
         (?: ([\"\']) (?<string>.*?) \\1)       # plain strings, no escape checking
................................................................................
        $var = array_merge($var, array_reverse($ops));
        return $var;
    }


    /**
     * Plop values of list, apply expressions or function callbacks as ordered.
     * (Operand relation overriden here as $pop(NN) parameter.)
     *
     */
    function run(&$opvals) {

        // prepare value stack
        $sum = [];
        $pop = function($prev=1) use (&$sum) { 






|








|
|
|



|
|
>
|
|

|
|
|
|
>

|
|













|
|




|
|
>

|
|
|
>

|
|
>












|
|

|
|
|







 







|







 







>







 







>
|
|
|
>







 







>







 







>







 







|
|
|







 







|
|
|
|

|
|
|
>







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
...
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
...
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
...
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
...
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
...
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
....
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
<?php
/**
 * api: php
 * title: PHP Macro Preprocessor
 * description: Context-sensitive C-style #define/macro directive processing.
 * license: MITL
 * version: 0.3.0
 * state: beta
 * depends: php (>= 5.4.0)
 * type: rewrite
 * category: macro
 * url: http://fossil.include-once.org/phrep/
 * pack: macro.php=MacroProcessor.php
 *
 *
 * Preprocessor to apply C-style #define, #ifdef, #include statements. 
 * Intended as build helper and complex macro injector. It's language-
 * specific to PHP, but can consume most common C header #defines.
 *
 * Source code/file transformation works in two phases:
 *
 *   1.  All #define, #include, #if* directives are expanded in a regex
 *       splitting state machine.  Which joins includes, or omits
 *       conditional sections.  This step is syntax-insensitive, but
 *       strips open/close tokens from injected files, and searches
 *       *.ph/.php/.inc/.h includes.
 *
 *   2.  Constants and macros are applied on the accumulated source code
 *       using a tokenized representation.  This allows context-sensitive
 *       expansion of defined preprocessor constants, standard macros, and
 *       even more complex transformation callbacks - for constructs such
 *       as IFSET@(expr).
 *
 * Directives take the form `#define X Y` and must be noted leftmost in
 * source files.  Following preprocessor directives are known:
 *
 *     #define CONST 1.2.3
 *     #ifdef CONST
 *     #include <filename.ph>
 *     #elif CONST < 1.0 || CONST >= 2.0
 *     #undef CONST
 *     #define MACRO(x,y) (x+y*2)
 *     #stderr Print some message
 *     #endif
 *     #pragma(output=0)
 *     #srcout inject_literal($source);
 *     #macro VA@(fn,expr...) {return "$fn([$expr])";}
 *
 * For compatibility with preprocess.py there's also support for doubly-
 * commented directives such as:
 *
 *     # #include <linux/errno.h>
 *     // #define X Y
 *
 * They're even valid with one ␣ space before the init // comment, and
 * up to two spaces ␣␣ before the actual #directive name.  Which allows
 * minimal structuring.
 *
 * Various #pragma(options=...) can control processing and substitution.
 * For example, output=0 allows to suppress #included file source code. 
 * Or use the crude interpolate=regex mode for constant replacements,
 * disable macros=0 etc.
 *
 * Per default #include scripts are stripped off opening <?php and closing
 * PHP tags.  And literal C header .h files have content output disabled
 * per default.
 *
 */

#ifndef OMIT_NAMESPACE
namespace io
{
use \stdClass, \ArrayAccess, \SplStack,
    \Exception, \OutOfBoundsException;
#endif


/**
 * MacroProcessor assembles the input source file with all its #include
 * deps, and handles conditional sections (via MacroExpression).
 * 
 *  · Main invocation method is ->on($filename). Which is meant/named as
 *    simple array_map() callback for file lists.  Can also be pre-invoked
 *    to parse any includes.h, but discard output.
 *
 *  · Basic source assembly could alternatively be run via ->block($source).
 *
 * After the complete source code has been collected, passes over macro and
 * constant expansion to MacroInjector for tokenizer/regex substitutions.
 *
 */
................................................................................
        "interpolate" => "token", # Or "regex" mode, or "erb", "phpp", or "delim << >>" for custom delimiters.
        "dirs" => NULL,           # Append include -I search paths.
        "preserve" => "output,file,line",  # Pragma states to retain between #includes
        "strip_php_tags" => 1,    # Remove <?php open and close tokens from #include files
    ];

    // Updated via versionnum
    const PHREP_VERSION = "0.3.0";

    /**
     * Predefined and collected constants and macros.
     */
    var $defines = [

       #-- Predeclare some C-style constants
................................................................................
    public function stderr(/*int*/$errno, /*string*/$msg, $file="macro.php", $line=0, $context=[]) {
        if ($this->pragma["fail"]) {
            throw new Exception($msg, $errno);
        }
        elseif ($this->pragma["quiet"]) {
            return;
        }
        // colorize output per default
        $msg = "\e[1;41m$msg\e[0m";
        if ($errno) {
            $msg = "\e[33;1m[$errno]\e[0m $msg";
        }
        if (1||$this->pragma["file"]) {
            $msg .= " (\e[2mprocessing \e[0;33m{$this->pragma['file']}:{$this->pragma['line']}\e[0m)";
        }
................................................................................

        // Read main file, process #directives between regex-split source blocks
        $this->pragma["input"] = $fn;
        $this->pragma["file"] = $fn;
        $output = $this->block(file_get_contents($fn));

        // Apply macros and constants in tokenization phase
        if ($this->pragma["substitution"]) {
            if ($this->pragma["constants"] || $this->pragma["macros"] || $this->pragma["complex"]) {
                $inj = new MacroInjector($this->filter_defines($this->defines), $this->pragma["multipass"]);
                $output = $inj->substitute($output, $this->pragma["interpolate"]);
            }
        }
        
        restore_error_handler();        
        return $output;
    }


................................................................................
            $directive = $block[0];
            $args = trim(preg_replace("/\\s*\\\\(\R)/", "$1", $block[1]));
            $pragma_lines = substr_count($block[1], "\n");

            // Dispatch or handle #directives
            switch ($directive) {

                // Condition state tracking
                case "if": 
                case "ifdef": 
                case "ifndef":
                    if ($¶->act) {
                        if ($directive == "if") {
                            $args = MacroExpression::strval($args, $this);
                        }
................................................................................
                    }
                    break;

                case "endif":
                    $¶->done();
                    break;

                // Action directives
                case "macro": $directive = "define";
                case "define":
                case "undef":
                case "include":
                case "stderr":
                case "srcout":
                case "pragma":
................................................................................

    /**
     * Custom or predefined delimiters around constant/macro names.
     *
     */
    function rx_delim($delim) {
        $rx = [
            "wbnd" => ["(?<!\w)", "(?!\w)"],  // just word boundaries
            "erb"  => ["\Q%{\E", "\Q}\E"],    // %{ERB} syntax
            "phpb" => ["\Q{{{\E", "\Q}}}\E"], // {{{PHPP}} scheme
        ];
        if (!empty($delim[3])) {
            return ["\Q$delim[2]\E", "\Q$delim[3]\E"];
        }
        elseif (isset($rx[$delim[1]])) {
            return $rx[$delim[1]];
        }
................................................................................
        $this[0] = [$name => $value] + $this[0];
    }
}



/**
 * #if/#elif expression evaluation. Permits common arithmetics, #define
 * lookups, a few hand-picked PHP callbacks, and decimal/float/tuple
 * comparisons.  It's neither fully CPP compatible, nor in line with
 * preprocess.py; and it's likely pointless featuritis anyway.
 *
 * Uses a trivial shunting-yard sorting method for its tokenized input,
 * then evaluates expressions down to a numeric or booleanish scalar. 
 * Has only minimal error reporting, and operator/func precedence is
 * incomplete/untuned..
 *
 */
class MacroExpression {

    # Expression tokens
    const RX_EXPR = "{
         (?: ([\"\']) (?<string>.*?) \\1)       # plain strings, no escape checking
................................................................................
        $var = array_merge($var, array_reverse($ops));
        return $var;
    }


    /**
     * Plop values of list, apply expressions or function callbacks as ordered.
     * (Operand association overriden here via $pop(NN) parameter.)
     *
     */
    function run(&$opvals) {

        // prepare value stack
        $sum = [];
        $pop = function($prev=1) use (&$sum) { 

Changes to phrep.

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296

297
298
299
300
301
302
303

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327

328
329
330
331

332
333
334

335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
...
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
...
487
488
489
490
491
492
493

494
495
496
497
498
499
500
...
525
526
527
528
529
530
531

532
533
534

535
536
537
538
539
540
541
...
571
572
573
574
575
576
577

578
579
580
581
582
583
584
...
604
605
606
607
608
609
610

611
612
613
614
615
616
617
...
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
....
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189

1190
1191
1192
1193
1194
1195
1196
....
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
<?php
/**
 * api: phrep
 * type: main
 * title: PHreProcessor
 * description: Rewrites preprocessing statements in PHP source files
 * url: http://fossil.include-once.org/phrep/
 * version: 0.2.9
 * license: MITL
 * author: mario@include-once.org
 * category: devel
 * depends: php (>= 5.4)
 * suggests: bin:make
 * config: { type: env list colon, name: PHP_INCLUDE_PATH, description: Additional include search dirs. }
 * architecture: all
................................................................................
} # global namespace

/**
 * api: php
 * title: PHP Macro Preprocessor
 * description: Context-sensitive C-style #define/macro directive processing.
 * license: MITL
 * version: 0.2.9
 * state: beta
 * depends: php (>= 5.4.0)
 * type: rewrite
 * category: macro
 * url: http://fossil.include-once.org/phrep/
 * pack: macro.php=MacroProcessor.php
 *
 *
 * Preprocessor to apply C-style #define, #ifdef, #include statements. Intended
 * as build helper and complex macro injector. It's language-specific to PHP,
 * but can consume most common C header #defines.       
 *
 * Source code/file transformation works in two phases:
 *
 *   1.  All #define, #include, #if* directives are expanded in a regex splitting
 *       state machine. Which joins includes, or omits conditional sections. This

 *       step is syntax-insensitive, but strips open/close tokens from injected
 *       files, and searches *.ph/.php/.inc/.h includes.
 *
 *   2.  Constants and macros are applied on the accumulated source code using
 *       a tokenized representation. This allows context-sensitive expansion of
 *       defined preprocessor constants, standard macros, and even more complex
 *       transformation callbacks - for constructs such as IFSET@(expr).

 *
 * Directives take the form `#define X Y` and must be noted leftmost in source
 * files. Following preprocessor directives are known:
 *
 *     #define CONST 1.2.3
 *     #ifdef CONST
 *     #include <filename.ph>
 *     #elif CONST < 1.0 || CONST >= 2.0
 *     #undef CONST
 *     #define MACRO(x,y) (x+y*2)
 *     #stderr Print some message
 *     #endif
 *     #pragma(output=0)
 *     #srcout inject_literal($source);
 *     #macro VA@(fn,expr...) {return "$fn([$expr])";}
 *
 * For compatibility with preprocess.py there's also support for doubly-commented
 * directives such as:
 *
 *     # #include <linux/errno.h>
 *     // #define X Y
 *
 * They're even valid with one ␣ space before the init // comment, and up to two
 * spaces ␣␣ before the actual #directive name. Which allows minimal structuring.

 *
 * Various #pragma(options=...) can control processing and substitution. For
 * example, output=0 allows to suppress #included file source code. Or use the
 * crude interpolate=regex mode for constant replacements, disable macros=0 etc.

 *
 * Per default #include scripts are stripped off opening <?php and closing PHP
 * tags. And literal C header .h files have content output disabled per default.

 *
 */

namespace io
{
use \stdClass, \ArrayAccess, \SplStack,
    \Exception, \OutOfBoundsException;


/**
 * MacroProcessor assembles the input source file with all its #include deps,
 * and handles conditional sections (via MacroExpression).
 * 
 *  · Main invocation method is ->on($filename). Which is meant/named as simple
 *    array_map() callback for file lists.
 *    Can also be pre-invoked to parse any includes.h, but discard output.
 *
 *  · Basic source assembly could alternatively be run via ->block($source).
 *
 * After the complete source code has been collected, passes over macro and
 * constant expansion to MacroInjector for tokenizer/regex substitutions.
 *
 */
................................................................................
        "interpolate" => "token", # Or "regex" mode, or "erb", "phpp", or "delim << >>" for custom delimiters.
        "dirs" => NULL,           # Append include -I search paths.
        "preserve" => "output,file,line",  # Pragma states to retain between #includes
        "strip_php_tags" => 1,    # Remove <?php open and close tokens from #include files
    ];

    // Updated via versionnum
    const PHREP_VERSION = "0.2.9";

    /**
     * Predefined and collected constants and macros.
     */
    var $defines = [

       #-- Predeclare some C-style constants
................................................................................
    public function stderr(/*int*/$errno, /*string*/$msg, $file="macro.php", $line=0, $context=[]) {
        if ($this->pragma["fail"]) {
            throw new Exception($msg, $errno);
        }
        elseif ($this->pragma["quiet"]) {
            return;
        }

        $msg = "\e[1;41m$msg\e[0m";
        if ($errno) {
            $msg = "\e[33;1m[$errno]\e[0m $msg";
        }
        if (1||$this->pragma["file"]) {
            $msg .= " (\e[2mprocessing \e[0;33m{$this->pragma['file']}:{$this->pragma['line']}\e[0m)";
        }
................................................................................

        // Read main file, process #directives between regex-split source blocks
        $this->pragma["input"] = $fn;
        $this->pragma["file"] = $fn;
        $output = $this->block(file_get_contents($fn));

        // Apply macros and constants in tokenization phase

        if ($this->pragma["substitution"] && ($this->pragma["constants"] || $this->pragma["macros"] || $this->pragma["complex"])) {
            $inj = new MacroInjector($this->filter_defines($this->defines), $this->pragma["multipass"]);
            $output = $inj->substitute($output, $this->pragma["interpolate"]);

        }
        
        restore_error_handler();        
        return $output;
    }


................................................................................
            $directive = $block[0];
            $args = trim(preg_replace("/\\s*\\\\(\R)/", "$1", $block[1]));
            $pragma_lines = substr_count($block[1], "\n");

            // Dispatch or handle #directives
            switch ($directive) {


                case "if": 
                case "ifdef": 
                case "ifndef":
                    if ($¶->act) {
                        if ($directive == "if") {
                            $args = MacroExpression::strval($args, $this);
                        }
................................................................................
                    }
                    break;

                case "endif":
                    $¶->done();
                    break;


                case "macro": $directive = "define";
                case "define":
                case "undef":
                case "include":
                case "stderr":
                case "srcout":
                case "pragma":
................................................................................

    /**
     * Custom or predefined delimiters around constant/macro names.
     *
     */
    function rx_delim($delim) {
        $rx = [
            "wbnd" => ["(?<!\w)", "(?!\w)"],
            "erb"  => ["\Q%{\E", "\Q}\E"],
            "phpb" => ["\Q{{{\E", "\Q}}}\E"],
        ];
        if (!empty($delim[3])) {
            return ["\Q$delim[2]\E", "\Q$delim[3]\E"];
        }
        elseif (isset($rx[$delim[1]])) {
            return $rx[$delim[1]];
        }
................................................................................
        $this[0] = [$name => $value] + $this[0];
    }
}



/**
 * #if/#elif expression evaluation. Permits common arithmetics, #define lookups,
 * a few hand-picked PHP callbacks, and decimal/float/tuple comparisons.
 * It's neither fully CPP compatible, nor in line with preprocess.py; and it's
 * likely pointless featuritis anyway.
 *
 * Uses a trivial shunting-yard sorting method for its tokenized input, then
 * evaluates expressions down to a numeric or booleanish scalar. Has only minimal
 * error reporting, and operator/func precedence is incomplete/untuned..

 *
 */
class MacroExpression {

    # Expression tokens
    const RX_EXPR = "{
         (?: ([\"\']) (?<string>.*?) \\1)       # plain strings, no escape checking
................................................................................
        $var = array_merge($var, array_reverse($ops));
        return $var;
    }


    /**
     * Plop values of list, apply expressions or function callbacks as ordered.
     * (Operand relation overriden here as $pop(NN) parameter.)
     *
     */
    function run(&$opvals) {

        // prepare value stack
        $sum = [];
        $pop = function($prev=1) use (&$sum) { 







|







 







|








|
|
|



|
|
>
|
|

|
|
|
|
>

|
|













|
|




|
|
>

|
|
|
>

|
|
>










|
|

|
|
|







 







|







 







>







 







>
|
|
|
>







 







>







 







>







 







|
|
|







 







|
|
|
|

|
|
|
>







 







|







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
...
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
...
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
...
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
...
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
...
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
...
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
....
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
....
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
<?php
/**
 * api: phrep
 * type: main
 * title: PHreProcessor
 * description: Rewrites preprocessing statements in PHP source files
 * url: http://fossil.include-once.org/phrep/
 * version: 0.3.0
 * license: MITL
 * author: mario@include-once.org
 * category: devel
 * depends: php (>= 5.4)
 * suggests: bin:make
 * config: { type: env list colon, name: PHP_INCLUDE_PATH, description: Additional include search dirs. }
 * architecture: all
................................................................................
} # global namespace

/**
 * api: php
 * title: PHP Macro Preprocessor
 * description: Context-sensitive C-style #define/macro directive processing.
 * license: MITL
 * version: 0.3.0
 * state: beta
 * depends: php (>= 5.4.0)
 * type: rewrite
 * category: macro
 * url: http://fossil.include-once.org/phrep/
 * pack: macro.php=MacroProcessor.php
 *
 *
 * Preprocessor to apply C-style #define, #ifdef, #include statements. 
 * Intended as build helper and complex macro injector. It's language-
 * specific to PHP, but can consume most common C header #defines.
 *
 * Source code/file transformation works in two phases:
 *
 *   1.  All #define, #include, #if* directives are expanded in a regex
 *       splitting state machine.  Which joins includes, or omits
 *       conditional sections.  This step is syntax-insensitive, but
 *       strips open/close tokens from injected files, and searches
 *       *.ph/.php/.inc/.h includes.
 *
 *   2.  Constants and macros are applied on the accumulated source code
 *       using a tokenized representation.  This allows context-sensitive
 *       expansion of defined preprocessor constants, standard macros, and
 *       even more complex transformation callbacks - for constructs such
 *       as IFSET@(expr).
 *
 * Directives take the form `#define X Y` and must be noted leftmost in
 * source files.  Following preprocessor directives are known:
 *
 *     #define CONST 1.2.3
 *     #ifdef CONST
 *     #include <filename.ph>
 *     #elif CONST < 1.0 || CONST >= 2.0
 *     #undef CONST
 *     #define MACRO(x,y) (x+y*2)
 *     #stderr Print some message
 *     #endif
 *     #pragma(output=0)
 *     #srcout inject_literal($source);
 *     #macro VA@(fn,expr...) {return "$fn([$expr])";}
 *
 * For compatibility with preprocess.py there's also support for doubly-
 * commented directives such as:
 *
 *     # #include <linux/errno.h>
 *     // #define X Y
 *
 * They're even valid with one ␣ space before the init // comment, and
 * up to two spaces ␣␣ before the actual #directive name.  Which allows
 * minimal structuring.
 *
 * Various #pragma(options=...) can control processing and substitution.
 * For example, output=0 allows to suppress #included file source code. 
 * Or use the crude interpolate=regex mode for constant replacements,
 * disable macros=0 etc.
 *
 * Per default #include scripts are stripped off opening <?php and closing
 * PHP tags.  And literal C header .h files have content output disabled
 * per default.
 *
 */

namespace io
{
use \stdClass, \ArrayAccess, \SplStack,
    \Exception, \OutOfBoundsException;


/**
 * MacroProcessor assembles the input source file with all its #include
 * deps, and handles conditional sections (via MacroExpression).
 * 
 *  · Main invocation method is ->on($filename). Which is meant/named as
 *    simple array_map() callback for file lists.  Can also be pre-invoked
 *    to parse any includes.h, but discard output.
 *
 *  · Basic source assembly could alternatively be run via ->block($source).
 *
 * After the complete source code has been collected, passes over macro and
 * constant expansion to MacroInjector for tokenizer/regex substitutions.
 *
 */
................................................................................
        "interpolate" => "token", # Or "regex" mode, or "erb", "phpp", or "delim << >>" for custom delimiters.
        "dirs" => NULL,           # Append include -I search paths.
        "preserve" => "output,file,line",  # Pragma states to retain between #includes
        "strip_php_tags" => 1,    # Remove <?php open and close tokens from #include files
    ];

    // Updated via versionnum
    const PHREP_VERSION = "0.3.0";

    /**
     * Predefined and collected constants and macros.
     */
    var $defines = [

       #-- Predeclare some C-style constants
................................................................................
    public function stderr(/*int*/$errno, /*string*/$msg, $file="macro.php", $line=0, $context=[]) {
        if ($this->pragma["fail"]) {
            throw new Exception($msg, $errno);
        }
        elseif ($this->pragma["quiet"]) {
            return;
        }
        // colorize output per default
        $msg = "\e[1;41m$msg\e[0m";
        if ($errno) {
            $msg = "\e[33;1m[$errno]\e[0m $msg";
        }
        if (1||$this->pragma["file"]) {
            $msg .= " (\e[2mprocessing \e[0;33m{$this->pragma['file']}:{$this->pragma['line']}\e[0m)";
        }
................................................................................

        // Read main file, process #directives between regex-split source blocks
        $this->pragma["input"] = $fn;
        $this->pragma["file"] = $fn;
        $output = $this->block(file_get_contents($fn));

        // Apply macros and constants in tokenization phase
        if ($this->pragma["substitution"]) {
            if ($this->pragma["constants"] || $this->pragma["macros"] || $this->pragma["complex"]) {
                $inj = new MacroInjector($this->filter_defines($this->defines), $this->pragma["multipass"]);
                $output = $inj->substitute($output, $this->pragma["interpolate"]);
            }
        }
        
        restore_error_handler();        
        return $output;
    }


................................................................................
            $directive = $block[0];
            $args = trim(preg_replace("/\\s*\\\\(\R)/", "$1", $block[1]));
            $pragma_lines = substr_count($block[1], "\n");

            // Dispatch or handle #directives
            switch ($directive) {

                // Condition state tracking
                case "if": 
                case "ifdef": 
                case "ifndef":
                    if ($¶->act) {
                        if ($directive == "if") {
                            $args = MacroExpression::strval($args, $this);
                        }
................................................................................
                    }
                    break;

                case "endif":
                    $¶->done();
                    break;

                // Action directives
                case "macro": $directive = "define";
                case "define":
                case "undef":
                case "include":
                case "stderr":
                case "srcout":
                case "pragma":
................................................................................

    /**
     * Custom or predefined delimiters around constant/macro names.
     *
     */
    function rx_delim($delim) {
        $rx = [
            "wbnd" => ["(?<!\w)", "(?!\w)"],  // just word boundaries
            "erb"  => ["\Q%{\E", "\Q}\E"],    // %{ERB} syntax
            "phpb" => ["\Q{{{\E", "\Q}}}\E"], // {{{PHPP}} scheme
        ];
        if (!empty($delim[3])) {
            return ["\Q$delim[2]\E", "\Q$delim[3]\E"];
        }
        elseif (isset($rx[$delim[1]])) {
            return $rx[$delim[1]];
        }
................................................................................
        $this[0] = [$name => $value] + $this[0];
    }
}



/**
 * #if/#elif expression evaluation. Permits common arithmetics, #define
 * lookups, a few hand-picked PHP callbacks, and decimal/float/tuple
 * comparisons.  It's neither fully CPP compatible, nor in line with
 * preprocess.py; and it's likely pointless featuritis anyway.
 *
 * Uses a trivial shunting-yard sorting method for its tokenized input,
 * then evaluates expressions down to a numeric or booleanish scalar. 
 * Has only minimal error reporting, and operator/func precedence is
 * incomplete/untuned..
 *
 */
class MacroExpression {

    # Expression tokens
    const RX_EXPR = "{
         (?: ([\"\']) (?<string>.*?) \\1)       # plain strings, no escape checking
................................................................................
        $var = array_merge($var, array_reverse($ops));
        return $var;
    }


    /**
     * Plop values of list, apply expressions or function callbacks as ordered.
     * (Operand association overriden here via $pop(NN) parameter.)
     *
     */
    function run(&$opvals) {

        // prepare value stack
        $sum = [];
        $pop = function($prev=1) use (&$sum) {