GitHub - zaporter-work/pragma_twice: #pragma twice VAR

6 min read Original article ↗
================================================================================
                              PRAGMA_TWICE
                         Preprocessor Library
                             Version 1.0.0
================================================================================

INTRODUCTION
------------

pragma_twice is a C++ preprocessor library that adds support for the
#pragma twice directive. This directive causes a block of code to be
emitted exactly twice during preprocessing, with a configurable iteration
variable that changes between passes.

This is conceptually the opposite of #pragma once (which prevents multiple
inclusion) -- #pragma twice guarantees exactly two expansions.


WHAT IS #pragma twice?
----------------------

The #pragma twice directive marks a block of code for duplication:

    #pragma twice VARNAME
    ... code ...
    #pragma end_twice

The code between these directives is output twice:
  - First pass:  VARNAME = 0
  - Second pass: VARNAME = 1


WHY USE IT?
-----------

The primary use case is generating paired functions that mirror each other's
structure. Common examples include:

  * Serialization / Deserialization
  * Encoding / Decoding
  * Pack / Unpack
  * Save / Load
  * Push / Pop operations

By defining the logic once with conditional compilation based on the
iteration variable, you ensure both functions stay synchronized.


                    +------------------+
                    |  Source File     |
                    |  (.twice.cpp)    |
                    +--------+---------+
                             |
                             v
                    +------------------+
                    |   pp_twice       |
                    |   preprocessor   |
                    +--------+---------+
                             |
                             v
                    +------------------+
                    |  Processed File  |
                    |  (.cpp)          |
                    +--------+---------+
                             |
                             v
                    +------------------+
                    |  C++ Compiler    |
                    |  (g++, clang++)  |
                    +--------+---------+
                             |
                             v
                    +------------------+
                    |  Executable      |
                    +------------------+


BUILDING
--------

Requirements:
  - C++ compiler with C++98 support (g++, clang++, etc.)
  - GNU Make

To build:

    $ make

This produces:
  - lib/libpragma_twice.a    Static library
  - bin/pp_twice             Command-line tool

To build and run tests:

    $ make test

To build examples:

    $ make examples


USAGE
-----

Command-line tool:

    pp_twice [options] <input_file>
    pp_twice [options] -              (read from stdin)

Options:
    -o <file>     Write output to file (default: stdout)
    -l            Emit #line directives
    -c            Strip comments from output
    -h, --help    Show help message
    -v, --version Show version

Example workflow:

    $ pp_twice input.twice.cpp > input.cpp
    $ g++ input.cpp -o program


SYNTAX
------

Basic syntax:

    #pragma twice VARNAME
    ... code using VARNAME ...
    #pragma end_twice

VARNAME must be a valid C identifier. Within the block, occurrences of
VARNAME are replaced with 0 (first pass) or 1 (second pass).


EXAMPLES
--------

Example 1: Simple function duplication
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Input:
                                            Output:
+------------------------------+            +------------------------------+
| #pragma twice PASS           |            | void first() {               |
| #if PASS == 0                |            |     printf("first\n");       |
| void first() {               |            | }                            |
|     printf("first\n");       |    ===>    | void second() {              |
| }                            |            |     printf("second\n");      |
| #else                        |            | }                            |
| void second() {              |            +------------------------------+
|     printf("second\n");      |
| }                            |
| #endif                       |
| #pragma end_twice            |
+------------------------------+


Example 2: Serialize/Deserialize
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Input:
+----------------------------------------------+
| #pragma twice WRITE                          |
|                                              |
| #if WRITE == 0                               |
| void serialize(Stream& s, const Data& d)    |
| #else                                        |
| void deserialize(Stream& s, Data& d)        |
| #endif                                       |
| {                                            |
| #if WRITE == 0                               |
|     s.write(d.field1);                       |
|     s.write(d.field2);                       |
|     s.write(d.field3);                       |
| #else                                        |
|     s.read(d.field1);                        |
|     s.read(d.field2);                        |
|     s.read(d.field3);                        |
| #endif                                       |
| }                                            |
|                                              |
| #pragma end_twice                            |
+----------------------------------------------+

Output:
+----------------------------------------------+
| void serialize(Stream& s, const Data& d)    |
| {                                            |
|     s.write(d.field1);                       |
|     s.write(d.field2);                       |
|     s.write(d.field3);                       |
| }                                            |
| void deserialize(Stream& s, Data& d)        |
| {                                            |
|     s.read(d.field1);                        |
|     s.read(d.field2);                        |
|     s.read(d.field3);                        |
| }                                            |
+----------------------------------------------+

Notice how the field order is defined only once, ensuring serialize and
deserialize stay in sync when fields are added, removed, or reordered.


Example 3: Nested twice blocks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Twice blocks can be nested for generating multiple variants:

    #pragma twice ENCODE
    #pragma twice TYPE

    // ENCODE=0, TYPE=0 -> encode_int()
    // ENCODE=0, TYPE=1 -> encode_float()
    // ENCODE=1, TYPE=0 -> decode_int()
    // ENCODE=1, TYPE=1 -> decode_float()

    #pragma end_twice
    #pragma end_twice

This generates 2 x 2 = 4 function variants.


LIBRARY API
-----------

Include the main header:

    #include <pragma_twice/preprocessor.h>

Basic usage:

    pragma_twice::Preprocessor pp;
    std::string output = pp.process(input_source);

    if (pp.has_error()) {
        std::cerr << pp.last_error() << std::endl;
    }

Configuration:

    pragma_twice::PreprocessorConfig config;
    config.preserve_comments = false;      // Strip comments
    config.preserve_line_numbers = true;   // Emit #line directives

    pragma_twice::Preprocessor pp(config);


DIRECTORY STRUCTURE
-------------------

    pragma_twice/
    |
    +-- README.txt              This file
    +-- Makefile                Build configuration
    |
    +-- include/
    |   +-- pragma_twice/
    |       +-- preprocessor.h  Main API header
    |       +-- tokenizer.h     Lexical analysis
    |       +-- parser.h        Structural analysis
    |       +-- directive.h     Directive handling
    |       +-- error.h         Error types
    |
    +-- src/
    |   +-- preprocessor.cpp    Main implementation
    |   +-- tokenizer.cpp       Tokenizer implementation
    |   +-- parser.cpp          Parser implementation
    |   +-- directive.cpp       Directive handling
    |   +-- main.cpp            CLI tool
    |
    +-- examples/
    |   +-- simple.twice.cpp    Basic example
    |   +-- serialize.twice.cpp Practical serialize/deserialize
    |   +-- paired.twice.cpp    Nested blocks example
    |
    +-- tests/
        +-- run_tests.sh        Test runner script


PROCESSING PIPELINE
-------------------

    +-------------+     +----------+     +----------+     +----------+
    |   Source    | --> | Tokenize | --> |  Parse   | --> |   Emit   |
    |   String    |     |          |     |          |     |          |
    +-------------+     +----------+     +----------+     +----------+
                             |                |                |
                             v                v                v
                        +---------+     +-----------+    +---------+
                        | Tokens  |     | Parse     |    | Output  |
                        | Vector  |     | Tree      |    | String  |
                        +---------+     +-----------+    +---------+

    1. Tokenizer breaks source into tokens (preserving whitespace)
    2. Parser builds a tree identifying twice blocks
    3. Emitter walks tree, duplicating twice blocks with substitution


LIMITATIONS
-----------

  * Variable substitution is simple text replacement within the twice
    block. Use unique variable names to avoid conflicts.

  * The preprocessor handles #pragma twice before the C preprocessor
    runs, so macro expansion happens after twice expansion.

  * Maximum nesting depth is limited by available stack memory.


ERROR HANDLING
--------------

The preprocessor reports errors for:

  * Missing variable name:     #pragma twice (no name)
  * Invalid variable name:     #pragma twice 123invalid
  * Unmatched end_twice:       #pragma end_twice without #pragma twice
  * Unterminated block:        #pragma twice without #pragma end_twice
  * Duplicate variable:        Nested blocks with same variable name


LICENSE
-------

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

See the LICENSE file for the full license text.


================================================================================
                              END OF README
================================================================================