PP(3)
π NAME π
pp β ANSI C preprocessor library. (Yep bestie, this is the PP(3) manual page β Library Functions Manual energy, but make it fashion. π
)
β¨ SYNOPSIS β¨
:PACKAGE: ast
#include <pp.h>
%include "pptokens.yacc
-lpp
π«Ά DESCRIPTION π«Ά
Okay so the pp library is literally a tokenizing implementation of the C language preprocessor, and it supports K&R (Reiser), ANSI and C++ dialects. The preprocessor is comprised of 12 public functions, a global character class table accessed by macros, and a single global struct with 10 public elements. Periodt.
pp operates in two modes. Standalone mode is used to implement the traditional standalone C preprocessor. Tokeinizing mode provides a function interface to a stream of preprocessed tokens. pp is by default ANSI; the only default predefined symbols are __STDC__ and __STDPP__.
Dialects (K&R, C++) and local conventions are determined by compiler specific probe(1) information that is included at runtime. The probe information can be overridden by providing a file pp_default.h with pragmas and definitions for each compiler implementation. This file is usually located in the compiler specific default include directory.
Directive, command line argument, option and pragma syntax is described in cpp(1). pp specific semantics are described below. Most semantic differences with standard or classic implementations are in the form of optimizations.
Options and pragmas map to ppop function calls described below. For the remaining descriptions, setting ppop(PP_operation) is a shorthand for calling ppop with the arguments appropriate for PP_operation.
The library interface describes only the public functions and struct elements. Static structs and pointers to structs are provided by the library.
π¨ Bestie, do not attempt to allocate structs. In particular, sizeof is meaningless for pp supplied structs.
The global struct pp provides readonly information. Any changes to pp must be done using the functions described below. pp has the following public elements:
char* versionβ The pp implementaion version string.char* lineidβ The current line sync directive name. Used for standalone line sync output. The default value is the empty string. See thepplinefunction below.char* outfileβ The current output file name.char* passβ The pragma pass name forpp. The default value ispp.char* tokenβ The string representation for the current input token.int flagsβ The inclusive or of:PP_commentβ Set ifppop(PP_COMMENT)was set.PP_compatibilityβ Set ifppop(PP_COMPATIBILITY)was set.PP_linefileβ Set if standalone line syncs require a file argument.PP_linetypeβ Set if standalone line syncs require a third argument. The third argument is1for include file push,2for include file pop andnullotherwise.PP_strictβ Set ifppop(PP_STRICT)was set.PP_transitionβ Set ifppop(PP_TRANSITION)was set.
struct ppdirs* lcldirsβ The list of directories to be searched for"..."include files. If the first directory name is""then it is replaced by the directory of the including file at include time. The public elements ofstruct ppdirsare:char* nameβ The directory pathname.struct ppdirs* nextβ The next directory,0if it is the last in the list.
struct ppdirs* stddirsβpp.stddirs->nextis the list of directories to be searched for<...>include files. This list may be0.struct ppsymbol* symbolβ Ifppop(PP_COMPILE)was set thenpp.symbolpoints to the symbol table entry for the current identifier token.pp.symbolis undefined for non-identifier tokens. Once defined, an identifier will always have the same ppsymbol pointer. Ifppop(PP_NOHASH)was also set thenpp.symbolis defined for macro and keyword tokens and0for all other identifiers. The elements ofstruct ppsymbolare:char* nameβ The identifier name.int flagsβ The inclusive or of the following flags:SYM_ACTIVEβ Currently being expanded.SYM_BUILTINβ Builtin macro.SYM_DISABLEDβ Macro expansion currently disabled.SYM_FUNCTIONβ Function-like macro.SYM_INITβ Initialization macro.SYM_KEYWORDβ Keyword identifier.SYM_LOADEDβ Loaded checkpoint macro.SYM_MULTILINEβ#macdefmacro.SYM_NOEXPANDβ No identifiers in macro body.SYM_PREDEFINEDβ Predefined macro.SYM_PREDICATEβ Also a#assertpredicate.SYM_READONLYβ Readonly macro.SYM_REDEFINEβ Ok to redefine.SYM_VARIADICβ Variadic function-like macro.SYM_UNUSEDβ First unused symbol flag bit index. The bits from(1<<SYM_UNUSED)on are initially unset and may be set by the user.
struct ppmacro* macroβ Non-zero if the identifier is a macro.int macro->arityis the number of formal arguments for function-like macros andchar* macro->valueis the macro definition value, a0terminated string that may contain internal mark sequences.char* valueβ Initially set to0and never modified bypp. This field may be set by the user.
Hash_table_t* symtabβ The macro and identifierstruct ppsymbolhash table. Thehash(3)routines may be used to examine the table, with the exception that the following macros must be used for individualpp.symtabsymbol lookup:struct ppsymbol* ppsymget(Hash_table_t* table, char* name)β Return the ppsymbol pointer forname,0ifnamenot defined.struct ppsymbol* ppsymset(Hash_table_t* table, char* name)β Return the ppsymbol pointer forname. Ifnameis not defined then allocate and return a new ppsymbol for it.
Error messages are reported using error(3) and the following globals relate to pp:
int error_info.errorsβ The level 2 error count. Error levels above 2 cause immediate exit. Iferror_info.errorsis non-zero then the user program exit status should also be non-zero.char* error_info.fileβ The current input file name.int error_info.lineβ The current input line number.int error_info.traceβ The debug trace level,0by default. Larger negative numbers produce more trace information. Enabled when the user program is linked with the-gcc(1)option.int error_info.warningsβ The level 1 error count. Warnings do not affect the exit status.
The functions are:
extern int ppargs(char** argv, int last);β Passed tooptjoin(3)to parsecpp(1)style options and arguments. The user may also supply application specific option parsers. Also handles non-standard options like the sun-undefand GNU-trigraphs. Hello in there, ever here ofgetopt(3)?extern void ppcpp(void);β This is the standalonecpp(1)entry point.ppcppconsumes all of the input and writes the preprocessed text to the output. A single call toppcppis equivalent to, but more efficient than:
ppop(PP_SPACEOUT, 1);
while (pplex())
ppprintf(" %s", pp.token);
extern int ppcomment(char* head, char* comment, char* tail, int line);β The default comment handler that passes comments to the output. May be used as an argument toppop(PP_COMMENT), or the user may supply an application specific handler.headis the comment head text,/*for C and//for C++,commentis the comment body,tailis the comment tail text,*/for C and newline for C++, andlineis the comment starting line number.extern void pperror(int level, char* format, ...);β Equivalent toerror(3). All pp error and warning messages pass throughpperror. The user may link with an application specificpperrorto override the library default.extern int ppincref(char* parent, char* file, int line, int push);β The default include reference handler that outputsfileto the standard error. May be used as an argument to theppop(PP_INCREF), or the user may supply an application specific handler.parentis the including file name,fileis the current include file name,lineis the current line number infile, andpushis non-zero iffileis being pushed or0iffileis being popped.extern void ppinput(char* buffer, char* file, int line);β Pushes the0terminatedbufferon the pp input stack.fileis the pseudo file name used in line syncs forbufferandlineis the starting line number.int pplex(void)β Returns the token type of the next input token.pp.tokenand where applicablepp.symbolare updated to refer to the new token. The token type constants are defined inpp.hfor#includeandpp.yaccforyacc(1)%include. The token constant names matchT_[A-Z_]*; some are encoded by oring withN_[A-Z_]*tokens.
okay so let's talk about the tokens themselves, because this is the part where it gets good π
β¨ the numeric constant tokens β¨
these are the encodings your numeric constants come tagged with, and yes you're gonna read all of them, i believe in you π«Ά
T_DOUBLE (N_NUMBER|N_REAL)
T_DOUBLE_L (N_NUMBER|N_REAL|N_LONG)
T_FLOAT (N_NUMBER|N_REAL|N_FLOAT)
T_DECIMAL (N_NUMBER)
T_DECIMAL_L (N_NUMBER|N_LONG)
T_DECIMAL_U (N_NUMBER|N_UNSIGNED)
T_DECIMAL_UL (N_NUMBER|N_UNSIGNED|N_LONG)
T_OCTAL (N_NUMBER|N_OCTAL)
T_OCTAL_L (N_NUMBER|N_OCTAL|N_LONG)
T_OCTAL_U (N_NUMBER|N_OCTAL|N_UNSIGNED)
T_OCTAL_UL (N_NUMBER|N_OCTAL|N_UNSIGNED|N_LONG)
T_HEXADECIMAL (N_NUMBER|N_HEXADECIMAL)
T_HEXADECIMAL_L (N_NUMBER|N_HEXADECIMAL|N_LONG)
T_HEXADECIMAL_U (N_NUMBER|N_HEXADECIMAL|N_UNSIGNED)
T_HEXADECIMAL_UL (N_NUMBER|N_HEXADECIMAL|N_UNSIGNED|N_LONG)
like, the parenthetical bit is the encoding, bestie β that's the whole vibe check right there πΈ
β¨ the normal C tokens β¨
and here's the rest of the crew, the regular C tokens, with their literal spellings so there's zero confusion:
T_ID C identifier
T_INVALID invalid token
T_HEADER <..>
T_CHARCONST '..'
T_WCHARCONST L'..'
T_STRING ".."
T_WSTRING L".."
T_PTRMEM ->
T_ADDADD ++
T_SUBSUB --
T_LSHIFT <<
T_RSHIFT >>
T_LE <=
T_GE >=
T_EQ ==
T_NE !=
T_ANDAND &&
T_OROR ||
T_MPYEQ *=
T_DIVEQ /=
T_MODEQ %=
T_ADDEQ +=
T_SUBEQ -=
T_LSHIFTEQ <<=
T_RSHIFTEQ >>=
T_ANDEQ &=
T_XOREQ ^=
T_OREQ |=
T_TOKCAT ##
T_VARIADIC ...
T_DOTREF .* [if PP_PLUSPLUS]
T_PTRMEMREF ->* [if PP_PLUSPLUS]
T_SCOPE :: [if PP_PLUSPLUS]
T_UMINUS unary minus
notice the last four before T_UMINUS? those only show up [if PP_PLUSPLUS] β the man page says so right there, so like, don't come for me π
π keywords, but only if you asked nicely π
so here's the tea: if ppop(PP_COMPILE) was set, then the keyword tokens get defined too. compiler differences and dialects get detected using the pp probe(1) information, and only the appropriate keywords get enabled. iconic behavior honestly.
the ANSI keyword tokens are:
T_AUTO T_BREAK T_CASE T_CHAR
T_CONTINUE T_DEFAULT T_DO T_DOUBLE_T
T_ELSE T_EXTERN T_FLOAT_T T_FOR
T_GOTO T_IF T_INT T_LONG
T_REGISTER T_RETURN T_SHORT T_SIZEOF
T_STATIC T_STRUCT T_SWITCH T_TYPEDEF
T_UNION T_UNSIGNED T_WHILE T_CONST
T_ENUM T_SIGNED T_VOID T_VOLATILE
and the C++ keyword tokens are:
T_CATCH T_CLASS T_DELETE T_FRIEND
T_INLINE T_NEW T_OPERATOR T_OVERLOAD
T_PRIVATE T_PROTECTED T_PUBLIC T_TEMPLATE
T_THIS T_THROW T_TRY T_VIRTUAL
plus, T_ASM is recognized where appropriate. and you can add more keyword tokens >= T_KEYWORD using ppop(PP_COMPILE) if you're feeling extra π
π the noise keyword situation π
okay so real talk. many C implementations show no restraint in adding new keywords β some PC compilers have literally tripled the number of keywords?? like ma'am, calm down. for the most part these new keywords introduce noise constructs that can be ignored for standard (reasonable) analysis and compilation.
the noise keywords fall into four syntactic categories, and they map into the two noise keyword tokens T_NOISE and T_NOISES. for T_NOISES, pp.token points to the entire noise construct, including the offending noise keyword. the basic noise keyword categories are:
T_NOISEβ the simplest noise: a single keyword that is noise in any context, and it maps toT_NOISE.T_X_GROUPβ a noise keyword that precedes an optional grouping construct, either(..)or{..}, and it maps toT_NOISES.T_X_LINEβ a noise keyword that consumes the remaining tokens in the line, and it maps toT_NOISES.T_X_STATEMENTβ a noise keyword that consumes the tokens up to the next;, and it maps toT_NOISES.
and the rules for what actually happens, listen close because this is the whole vibe:
- if
ppop(PP_NOISE)is> 0, then implementation specific noise constructs get mapped to eitherT_NOISEorT_NOISES. - otherwise, if
ppop(PP_NOISE)is< 0, then noise constructs are completely ignored. - otherwise (neither of those), the unmapped grouping noise tokens
T_X_.*are returned.
three-way branch, no in-between. pick your fighter π₯
π token-testing macros π
you can test token encodings with these, and they all return non-zero for "yes bestie":
int isnumber(int token);β non-zero iftokenis an integral or floating point numeric constant.int isinteger(int token);β non-zero iftokenis an integral numeric constant.int isreal(int token);β non-zero iftokenis a floating point numeric constant.int isassignop(int token);β non-zero iftokenis a C assignment operator.int isseparate(int token);β non-zero iftokenmust be separated from other tokens by space.int isnoise(int token);β non-zero iftokenis a noise keyword.
π the handlers π
ppline β the line sync girlie
extern int ppline(int line, char* file);
the default line sync handler, and what it does is output line sync pragmas for the C compiler front end. you can use it as an argument to ppop(PP_LINE), or supply your own application specific handler instead β your call, no pressure π¦
line is the line number and file is the file name. and ooh, this part: if ppop(PP_LINEID) was set, then the directive # lineid line "file" is output. fancy.
ppmacref β the macro reference handler
extern int ppmacref(struct ppsymbol* symbol, char* file, int line, int type);
the default macro reference handler that outputs a macro reference pragmas. again, usable as an argument to ppop(PP_MACREF), or you bring your own application specific handler β¨
symbol is the macro ppsymbol pointer, file is the reference file, line is the reference line, and if type is non-zero a macro value checksum is also output. the pragma syntax is:
#pragma pp:macref "symbol->name" line checksum
π ppop β the option control interface π
int ppop(int op, ...)
this is the interface, bestie. op determines the type(s) of the remaining argument(s). and warning time:
options marked by/*INIT*/must be done beforePP_INIT. like, before. not after. don't play with me on this π
the ones with actual descriptions
(PP_ASSERT, char* string)/*INIT*/βstringis asserted as if by#assert.(PP_BUILTIN, char*(*fun)(char* buf, char* name, char* args))/*INIT*/β installsfunas the unknown builtin macro handler. builtin macros are of the form#(name args).funis called withnameset to the unknown builtin macro name andargsset to the arguments.bufis aMAXTOKEN+1buffer that can be used for thefunreturn value.0should be returned on error.(PP_DEFINE, char* string)/*INIT*/βstringis defined as if by#define.(PP_DIRECTIVE, char* string)/*INIT*/β the directive#stringis executed.(PP_OPTION, char* string)/*INIT*/β the directive#pragma pp:stringis executed.
the rest of the roster (man page doesn't describe these, so i won't either β no cap)
these all take a char* string and all carry the /*INIT*/ tag. that's literally all the info the page gives, so don't @ me asking what they do π
(PP_COMMENT,void (*fun)(char*head,char*body,char*tail,int line)/*INIT*/(PP_COMPATIBILITY, char* string)/*INIT*/(PP_COMPILE, char* string)/*INIT*/(PP_DEBUG, char* string)/*INIT*/(PP_DEFAULT, char* string)/*INIT*/(PP_DONE, char* string)/*INIT*/(PP_DUMP, char* string)/*INIT*/(PP_FILEDEPS, char* string)/*INIT*/(PP_FILENAME, char* string)/*INIT*/(PP_HOSTDIR, char* string)/*INIT*/(PP_HOSTED, char* string)/*INIT*/(PP_ID, char* string)/*INIT*/(PP_IGNORE, char* string)/*INIT*/(PP_INCLUDE, char* string)/*INIT*/(PP_INCREF, char* string)/*INIT*/(PP_INIT, char* string)/*INIT*/(PP_INPUT, char* string)/*INIT*/(PP_LINE, char* string)/*INIT*/(PP_LINEFILE, char* string)/*INIT*/(PP_LINEID, char* string)/*INIT*/(PP_LINETYPE, char* string)/*INIT*/(PP_LOCAL, char* string)/*INIT*/(PP_MACREF, char* string)/*INIT*/(PP_MULTIPLE, char* string)/*INIT*/(PP_NOHASH, char* string)/*INIT*/(PP_NOID, char* string)/*INIT*/(PP_NOISE, char* string)/*INIT*/(PP_OPTARG, char* string)/*INIT*/(PP_OUTPUT, char* string)/*INIT*/(PP_PASSNEWLINE, char* string)/*INIT*/(PP_PASSTHROUGH, char* string)/*INIT*/(PP_PLUSPLUS, char* string)/*INIT*/(PP_PRAGMA, char* string)/*INIT*/(PP_PREFIX, char* string)/*INIT*/(PP_PROBE, char* string)/*INIT*/(PP_READ, char* string)/*INIT*/(PP_RESERVED, char* string)/*INIT*/(PP_SPACEOUT, char* string)/*INIT*/(PP_STANDALONE, char* string)/*INIT*/(PP_STANDARD, char* string)/*INIT*/(PP_STRICT, char* string)/*INIT*/(PP_TEST, char* string)/*INIT*/(PP_TRUNCATE, char* string)/*INIT*/(PP_UNDEF, char* string)/*INIT*/(PP_WARN, char* string)/*INIT*/
π pppragma β the copy-the-unknowns handler π
int pppragma(char* dir, char* pass, char* name, char* value, int nl);
the default handler that copies unknown directives and pragmas to the output. use it as an argument to ppop(PP_PRAGMA), or supply your own application specific handler, whichever slays harder for your app π
this function is most often called after directive and pragma mapping, fyi. any of the arguments may be 0 β zero is fine, zero is valid, we love a flexible signature.
diris the directive name.passis the pragma pass name.nameis the pragma option name.valueis the pragma option value.nlis non-zero if a trailing newline is required if the pragma is copied to the output.
π§ ppprintf and the output buffer crew π§
int ppprintf(char* format, ...);
a printf(3) interface to the standalone pp output buffer. literally printf but for pp, obsessed.
and there are macros giving you limited control over output buffering:
void ppflushout()β flushes the output buffer.void ppcheckout()β flushes the output buffer if overPPBUFSIZcharacter are buffered.int pppendout()β returns the number of pending character in the output buffer.void ppputchar(int c)β places the charactercin the output buffer.
yes, the man page says "character" singular in both of those. we keep it real here β¨
π CAVEATS π
Okay bestie, gather round, because this is the part where we're honest about stuff π«Ά
The ANSI mode is intended to be true to the standard. Like, that's the whole goal, that's the assignment, it's giving "follow the spec to the letter" energy β¨
The compatibility mode? That one has been proven in practice β it's been out there living its life, doing the work, passing the vibe check fr fr πͺ But ngl, there are surely dark corners of some implementations that may have been omitted π So like, don't come for us if your fave weird little edge case isn't in there β the caveat is literally right here, out in the open, no cap π
π¦ SEE ALSO π¦
Your reading list, bestie β go be nosy π
π AUTHOR π
Glenn Fowler β¨
And a very special shoutout, because we stan credited collabs: Dennis Ritchie provided the original table driven lexer πΈ iconic behavior, honestly π
AT&T Bell Laboratories π β the whole vibe, the whole origin story, we love to see it π§
Okay that's the whole page, bestie!! π You made it to the end β that's main character energy fr fr π This little legend right here is PP(3) from section 3 of the manual, and yes, that footer is the version/source line doing its quiet little bow at the bottom of the page π Go forth and lex, ily, mwah π
β¨
π¦ where this page comes from
- opensuse-leap 16.0: ksh-devel 93vu-bp160.1.33 Β· CPL-1.0
- opensuse-leap 16.0: ksh-devel 93vu-bp160.1.33 Β· CPL-1.0
- opensuse-leap 16.0: ksh-devel 93vu-bp160.1.33 Β· CPL-1.0
- opensuse-leap 16.0: ksh-devel 93vu-bp160.1.33 Β· CPL-1.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms