1 /** 2 * Authors: 3 * Mike Bierlee, m.bierlee@lostmoment.com 4 * Copyright: 2023 Mike Bierlee 5 * License: 6 * This software is licensed under the terms of the MIT license. 7 * The full terms of the license can be found in the LICENSE.txt file. 8 */ 9 10 module preprocessor.processing; 11 12 import preprocessor.artifacts : BuildContext, PreprocessException, ParseException, FileMacro, LineMacro, MacroMap, 13 builtInMacros; 14 import preprocessor.parsing : ParseContext, parse, collect, DirectiveStart, MacroStartEnd, skipWhiteSpaceTillEol, peek, 15 replaceStartToEnd, clearStartToEnd, endOfLineDelims, peekLast, seekNextDirective, calculateLineColumn, seekNext, 16 collectTillString; 17 import preprocessor.debugging; 18 19 import std.conv : to; 20 import std.path : dirName; 21 import std.algorithm : canFind; 22 import std.string : toLower, startsWith, endsWith, strip; 23 import std.array : replaceInPlace; 24 25 private enum IncludeDirective = "include"; 26 private enum IfDirective = "if"; 27 private enum IfDefDirective = "ifdef"; 28 private enum IfNDefDirective = "ifndef"; 29 private enum ElIfDirective = "elif"; 30 private enum ElseDirective = "else"; 31 private enum EndIfDirective = "endif"; 32 private enum DefineDirective = "define"; 33 private enum UndefDirective = "undef"; 34 private enum ErrorDirective = "error"; 35 private enum PragmaDirective = "pragma"; 36 37 private enum PragmaOnceExtension = "once"; 38 39 private static const string[] conditionalTerminators = [ 40 ElIfDirective, ElseDirective, EndIfDirective 41 ]; 42 43 package void processFile( 44 const string name, 45 const ref string inSource, 46 const ref BuildContext buildCtx, 47 ref MacroMap macros, 48 ref string[] guardedInclusions, 49 out string outSource, 50 const uint currentInclusionDepth = 0 51 ) { 52 macros[FileMacro] = name; 53 macros[LineMacro] = "true"; // For #if eval 54 55 ParseContext parseCtx; 56 parseCtx.name = name; 57 parseCtx.source = inSource; 58 parseCtx.macros = macros; 59 parseCtx.guardedInclusions = guardedInclusions; 60 parseCtx.inclusionDepth = currentInclusionDepth; 61 62 bool foundMacroTokenBefore = false; 63 parse(parseCtx, (const char chr, out bool stop) { 64 if (chr == DirectiveStart) { 65 foundMacroTokenBefore = false; 66 parseCtx.replaceStart = parseCtx.codePos - 1; 67 parseCtx.directive = parseCtx.collect(); 68 processDirective(parseCtx, buildCtx); 69 70 parseCtx.directive = ""; 71 parseCtx.replaceStart = 0; 72 parseCtx.replaceEnd = 0; 73 } else if (chr == MacroStartEnd) { 74 if (foundMacroTokenBefore) { 75 expandMacro(parseCtx); 76 foundMacroTokenBefore = false; 77 } else { 78 foundMacroTokenBefore = true; 79 } 80 } else { 81 foundMacroTokenBefore = false; 82 } 83 }); 84 85 macros = parseCtx.macros; 86 guardedInclusions = parseCtx.guardedInclusions; 87 outSource = parseCtx.source; 88 } 89 90 private void processDirective(ref ParseContext parseCtx, const ref BuildContext buildCtx) { 91 switch (parseCtx.directive) { 92 case IncludeDirective: 93 if (buildCtx.enableIncludeDirectives) { 94 processInclude(parseCtx, buildCtx); 95 } 96 break; 97 98 case IfDirective: 99 case IfDefDirective: 100 case IfNDefDirective: 101 if (buildCtx.enableConditionalDirectives) { 102 processConditionalDirective(parseCtx, parseCtx.directive); 103 } 104 break; 105 106 case DefineDirective: 107 if (buildCtx.enableMacroDefineDirectives) { 108 processDefineDirective(parseCtx); 109 } 110 break; 111 112 case UndefDirective: 113 if (buildCtx.enableMacroUndefineDirectives) { 114 processUndefDirective(parseCtx); 115 } 116 break; 117 118 case EndIfDirective: 119 processUnexpectedConditional(parseCtx, buildCtx); 120 break; 121 122 case ElseDirective: 123 processUnexpectedConditional(parseCtx, buildCtx); 124 break; 125 126 case ElIfDirective: 127 processUnexpectedConditional(parseCtx, buildCtx); 128 break; 129 130 case ErrorDirective: 131 if (buildCtx.enableErrorDirectives) { 132 processErrorDirective(parseCtx); 133 } 134 break; 135 136 case PragmaDirective: 137 if (buildCtx.enablePragmaDirectives) { 138 processPragmaDirective(parseCtx); 139 } 140 break; 141 142 default: 143 // Ignore directive. It may be of semantic importance to the source in another way. 144 } 145 } 146 147 private void processInclude(ref ParseContext parseCtx, const ref BuildContext buildCtx) { 148 if (parseCtx.inclusionDepth >= buildCtx.inclusionLimit) { 149 throw new PreprocessException(parseCtx, "Inclusions has exceeded the limit of " ~ 150 buildCtx.inclusionLimit.to!string ~ ". Adjust BuildContext.inclusionLimit to increase."); 151 } 152 153 parseCtx.codePos -= 1; 154 parseCtx.skipWhiteSpaceTillEol(); 155 char startChr = parseCtx.peek; 156 bool absoluteInclusion; 157 if (startChr == '"') { 158 absoluteInclusion = false; 159 } else if (startChr == '<') { 160 absoluteInclusion = true; 161 } else { 162 throw new ParseException(parseCtx, "Failed to parse include directive: Expected \" or <."); 163 } 164 165 parseCtx.codePos += 1; 166 const string includeName = parseCtx.collect(['"', '>']); 167 parseCtx.replaceEnd = parseCtx.codePos; 168 169 auto includeSource = includeName in buildCtx.sources; 170 if (includeSource is null && !absoluteInclusion) { 171 string currentDir = parseCtx.name.dirName; 172 includeSource = currentDir ~ "/" ~ includeName in buildCtx.sources; 173 } 174 175 if (includeSource is null) { 176 throw new PreprocessException(parseCtx, parseCtx.replaceStart, "Failed to include '" ~ includeName ~ "': It does not exist."); 177 } 178 179 if (parseCtx.guardedInclusions.canFind(includeName)) { 180 parseCtx.clearStartToEnd(); 181 return; 182 } 183 184 string processedIncludeSource; 185 string[] guardedInclusions = parseCtx.guardedInclusions; 186 processFile( 187 includeName, 188 *includeSource, 189 buildCtx, 190 parseCtx.macros, 191 guardedInclusions, 192 processedIncludeSource, 193 parseCtx.inclusionDepth + 1 194 ); 195 196 parseCtx.macros[FileMacro] = parseCtx.name; 197 parseCtx.guardedInclusions = guardedInclusions; 198 parseCtx.replaceStartToEnd(processedIncludeSource); 199 } 200 201 private void processConditionalDirective(ref ParseContext parseCtx, const string directiveName) { 202 bool negate = directiveName == IfNDefDirective; 203 bool onlyCheckExistence = directiveName != IfDirective; 204 processConditionalDirective(parseCtx, negate, onlyCheckExistence); 205 } 206 207 private void processConditionalDirective(ref ParseContext parseCtx, const bool negate, const bool onlyCheckExistence) { 208 auto startOfConditionalBlock = parseCtx.replaceStart; 209 parseCtx.codePos -= 1; 210 parseCtx.skipWhiteSpaceTillEol(); 211 212 enum ConditionalBlockStartDirective = "startconditional"; 213 auto conditionalDirective = ConditionalBlockStartDirective; 214 bool acceptedBody = false; 215 bool processedElse = false; 216 while (conditionalDirective != EndIfDirective) { 217 if (conditionalDirective == ConditionalBlockStartDirective || conditionalDirective == ElIfDirective) { 218 bool isTrue = evaluateCondition(parseCtx, negate, onlyCheckExistence); 219 if (isTrue && !acceptedBody) { 220 parseCtx.acceptConditionalBody(); 221 acceptedBody = true; 222 } else { 223 parseCtx.rejectConditionalBody(); 224 } 225 } else if (conditionalDirective == ElseDirective) { 226 if (processedElse) { 227 throw new ParseException(parseCtx, "#else directive defined multiple times. Only one #else block is allowed."); 228 } 229 230 if (acceptedBody) { 231 parseCtx.rejectConditionalBody(); 232 } else { 233 parseCtx.acceptConditionalBody(); 234 } 235 236 processedElse = true; 237 } 238 239 parseCtx.replaceStart = parseCtx.codePos - 1; 240 conditionalDirective = parseCtx.collect(); 241 } 242 243 parseCtx.replaceEnd = parseCtx.codePos; 244 parseCtx.clearStartToEnd(); 245 246 parseCtx.codePos = startOfConditionalBlock; 247 } 248 249 private void processDefineDirective(ref ParseContext parseCtx) { 250 auto macroName = parseCtx.collect(); 251 if (macroName.length == 0) { 252 throw new ParseException(parseCtx, "#define directive is missing name of macro."); 253 } 254 255 assertNotBuiltinMacro(parseCtx, macroName); 256 257 string macroValue = null; 258 auto isEndOfDefinition = endOfLineDelims.canFind(parseCtx.peekLast); 259 if (!isEndOfDefinition) { 260 macroValue = parseCtx.collect(endOfLineDelims).strip; 261 if (macroValue[0] == '"' && macroValue[$ - 1] == '"') { 262 macroValue = macroValue[1 .. $ - 1]; 263 } 264 } 265 266 parseCtx.macros[macroName] = macroValue; 267 parseCtx.replaceEnd = parseCtx.codePos; 268 parseCtx.clearStartToEnd(); 269 } 270 271 private void processUndefDirective(ref ParseContext parseCtx) { 272 auto macroName = parseCtx.collect(); 273 if (macroName.length == 0) { 274 throw new ParseException(parseCtx, "#undef directive is missing name of macro."); 275 } 276 277 assertNotBuiltinMacro(parseCtx, macroName); 278 279 parseCtx.macros.remove(macroName); 280 parseCtx.replaceEnd = parseCtx.codePos; 281 parseCtx.clearStartToEnd(); 282 } 283 284 private void processErrorDirective(ref ParseContext parseCtx) { 285 parseCtx.seekNext('"'); 286 auto errorMessage = parseCtx.collect(endOfLineDelims ~ '"'); 287 throw new PreprocessException(parseCtx, errorMessage); 288 } 289 290 private void processPragmaDirective(ref ParseContext parseCtx) { 291 auto extensionName = parseCtx.collect(); 292 if (extensionName != PragmaOnceExtension) { 293 throw new PreprocessException(parseCtx, "Pragma extension '" ~ extensionName ~ "' is unsupported."); 294 } 295 296 parseCtx.guardedInclusions ~= parseCtx.name; 297 parseCtx.replaceEnd = parseCtx.codePos; 298 parseCtx.clearStartToEnd(); 299 } 300 301 private void processUnexpectedConditional(const ref ParseContext parseCtx, const ref BuildContext buildCtx) { 302 if (buildCtx.enableConditionalDirectives && !buildCtx.ignoreUnmatchedConditionalDirectives) { 303 throw new ParseException(parseCtx, "#" ~ parseCtx.directive ~ " directive found without accompanying starting conditional (#if/#ifdef)"); 304 } 305 } 306 307 private bool evaluateCondition(ref ParseContext parseCtx, const bool negate, const bool onlyCheckExistence) { 308 auto expression = parseCtx.collect(); 309 if (expression.startsWith("__") && expression.endsWith("__")) { 310 expression = expression[2 .. $ - 2]; 311 } 312 313 auto macroValue = expression in parseCtx.macros; 314 bool isTrue = macroValue !is null; 315 if (!onlyCheckExistence) { 316 isTrue = isTrue && *macroValue != "0" && *macroValue != null 317 && (*macroValue).toLower != "false"; 318 } 319 320 if (negate) { 321 isTrue = !isTrue; 322 } 323 324 return isTrue; 325 } 326 327 private void acceptConditionalBody(ref ParseContext parseCtx) { 328 parseCtx.replaceEnd = parseCtx.codePos; 329 parseCtx.clearStartToEnd(); 330 parseCtx.seekNextDirective(conditionalTerminators); 331 } 332 333 private void rejectConditionalBody(ref ParseContext parseCtx) { 334 parseCtx.seekNextDirective(conditionalTerminators); 335 parseCtx.replaceEnd = parseCtx.codePos; 336 parseCtx.clearStartToEnd(); 337 } 338 339 private void expandMacro(ref ParseContext parseCtx) { 340 auto macroStart = parseCtx.codePos - 2; 341 auto macroName = parseCtx.collectTillString("__"); 342 auto macroEnd = parseCtx.codePos; 343 if (parseCtx.peek == MacroStartEnd) { 344 macroEnd += 1; 345 } 346 347 string macroValue; 348 if (macroName == LineMacro) { 349 ulong line, column; 350 calculateLineColumn(parseCtx, line, column); 351 macroValue = line.to!string; 352 } else { 353 auto macroValuePtr = macroName in parseCtx.macros; 354 if (macroValuePtr is null) { 355 throw new ParseException(parseCtx, "Cannot expand macro __" ~ macroName ~ "__, it is undefined."); 356 } 357 358 macroValue = *macroValuePtr; 359 } 360 361 parseCtx.source.replaceInPlace(macroStart, macroEnd, macroValue); 362 parseCtx.codePos = macroStart + macroValue.length; 363 } 364 365 private void assertNotBuiltinMacro(ref ParseContext parseCtx, string macroName) { 366 if (builtInMacros.canFind(macroName)) { 367 throw new PreprocessException(parseCtx, "Cannot use macro name '" ~ macroName ~ "', it is a built-in macro."); 368 } 369 }