================================================================================
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
================================================================================