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 }