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.parsing; 11 12 import preprocessor.artifacts : ParseException, MacroMap; 13 import preprocessor.debugging; 14 15 import std.algorithm : canFind; 16 import std.array : replaceInPlace; 17 import std.string : endsWith; 18 19 package enum DirectiveStart = '#'; 20 package enum MacroStartEnd = '_'; 21 package static const char[] endOfLineDelims = ['\n', '\r']; 22 package static const char[] whiteSpaceDelims = [' ', '\t']; 23 package static const char[] endTokenDelims = endOfLineDelims ~ whiteSpaceDelims; 24 25 package struct ParseContext { 26 string name; 27 string source; 28 MacroMap macros; 29 30 ulong codePos; 31 ulong replaceStart; 32 ulong replaceEnd; 33 string directive; 34 uint inclusionDepth; 35 string[] guardedInclusions; 36 } 37 38 package void skipWhiteSpaceTillEol(ref ParseContext parseCtx) { 39 parse(parseCtx, endOfLineDelims, (const char chr, out bool stop) { 40 if (!whiteSpaceDelims.canFind(chr)) { 41 parseCtx.codePos -= 1; 42 stop = true; 43 } 44 }); 45 } 46 47 package void seekNext(ref ParseContext parseCtx, const char delimiter) { 48 parse(parseCtx, [delimiter], (const char chr, out bool stop) {}); 49 } 50 51 package char peek(const ref ParseContext parseCtx) { 52 return parseCtx.source[parseCtx.codePos]; 53 } 54 55 package char peekLast(const ref ParseContext parseCtx) { 56 return parseCtx.source[parseCtx.codePos - 1]; 57 } 58 59 package string collect(ref ParseContext parseCtx, const char[] delimiters = endTokenDelims) { 60 string value; 61 parse(parseCtx, delimiters, (const char chr, out bool stop) { value ~= chr; }); 62 return value; 63 } 64 65 package string collectTillString(ref ParseContext parseCtx, const string delimiter) { 66 string value; 67 parse(parseCtx, (const char chr, out bool stop) { 68 value ~= chr; 69 if (value.endsWith(delimiter)) { 70 stop = true; 71 value = value[0 .. $ - delimiter.length]; 72 } 73 }); 74 75 return value; 76 } 77 78 package void parse(ref ParseContext parseCtx, void delegate(const char chr, out bool stop) func) { 79 parse(parseCtx, [], func); 80 } 81 82 package void parse(ref ParseContext parseCtx, const char[] delimiters, void delegate( 83 const char chr, out bool stop) func) { 84 while (parseCtx.codePos < parseCtx.source.length) { 85 const char chr = parseCtx.source[parseCtx.codePos++]; 86 if (delimiters.canFind(chr)) { 87 break; 88 } 89 90 bool stop; 91 func(chr, stop); 92 93 if (stop) { 94 break; 95 } 96 } 97 } 98 99 package void seekNextDirective(ref ParseContext parseCtx, const string[] delimitingDirectives) { 100 auto nextDirective = ""; 101 while (!delimitingDirectives.canFind(nextDirective) && parseCtx.codePos < 102 parseCtx.source.length) { 103 parseCtx.seekNext('#'); 104 nextDirective = parseCtx.collect(); 105 } 106 107 if (nextDirective.length == 0) { 108 throw new ParseException(parseCtx, "Unexpected end of file while processing directive."); 109 } 110 111 parseCtx.codePos -= nextDirective.length + 1; 112 } 113 114 void clearStartToEnd(ref ParseContext parseCtx) { 115 parseCtx.replaceStartToEnd(""); 116 } 117 118 void replaceStartToEnd(ref ParseContext parseCtx, const string replacement) { 119 parseCtx.source.replaceInPlace(parseCtx.replaceStart, parseCtx.replaceEnd, replacement); 120 parseCtx.codePos = parseCtx.replaceStart + replacement.length; 121 } 122 123 package void calculateLineColumn(const ref ParseContext parseCtx, out ulong line, out ulong column) { 124 calculateLineColumn(parseCtx, parseCtx.codePos, line, column); 125 } 126 127 package void calculateLineColumn(const ref ParseContext parseCtx, in ulong codePos, out ulong line, out ulong column) { 128 foreach (size_t idx, char chr; parseCtx.source) { 129 if (idx == codePos) { 130 break; 131 } 132 133 if (chr == '\n') { 134 line += 1; 135 column = 0; 136 } 137 138 column += 1; 139 } 140 }