1 /** 2 Parser utilities. 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inp.format.parse; 14 import nulib.text.ascii; 15 import nulib.io.stream.rw; 16 import nulib.io.stream; 17 import nulib.string; 18 import nulib.math; 19 import numem; 20 21 @nogc: 22 23 // Alias to make things more obvious 24 alias ErrorString = string; 25 26 27 /** 28 Gets whether the end of the stream has been reached. 29 30 Params: 31 reader = The stream reader. 32 33 Returns: 34 $(D true) if the end of the stream was reached, 35 $(D false) otherwise. 36 */ 37 bool eof(StreamReader reader) { 38 return reader.stream.tell() >= reader.stream.length(); 39 } 40 41 /** 42 Skips the given amount of characters in the stream. 43 44 Params: 45 reader = The stream reader. 46 count = Amount of characters to skip. 47 */ 48 pragma(inline, true) 49 void skip(StreamReader reader, ptrdiff_t count) { 50 reader.stream.seek(count, SeekOrigin.relative); 51 } 52 53 /** 54 Skips ascii whitespace characters. 55 56 Params: 57 reader = The stream reader. 58 */ 59 void skipWhitespace(StreamReader reader) { 60 char c; 61 do { c = cast(char)reader.readU8(); } while(c == ' ' || c == '\t' || c == '\r' || c == '\n'); 62 reader.skip(-1); 63 } 64 65 /** 66 Peeks a single ASCII character from the stream. 67 68 Params: 69 reader = The stream reader. 70 71 Returns: 72 The character 1 byte ahead of the current read position. 73 */ 74 char peekChar(StreamReader reader) { 75 char c = cast(char)reader.readU8(); 76 reader.skip(-1); 77 return c; 78 } 79 80 /** 81 Takes a peek at the upcoming string contents of the given stream. 82 83 Params: 84 reader = The stream reader. 85 length = The length of the string. 86 87 Returns: 88 The text read from the stream, length might be shorter 89 than the requested length. 90 */ 91 nstring peekString(StreamReader reader, uint length) { 92 auto s = reader.readUTF8(length); 93 if (s.length > 0) { 94 reader.skip(-cast(ptrdiff_t)s.length); 95 } 96 return s; 97 } 98 99 /** 100 Peeks a string from the stream and checks whether 101 it is the same as the given key. 102 103 Params: 104 reader = The stream reader. 105 key = The key to compare against. 106 107 Returns: 108 $(D true) if the upcoming characters in the stream match 109 $(D key), $(D false) otherwise. 110 */ 111 bool peekString(StreamReader reader, string key) { 112 nstring read = reader.peekString(cast(uint)key.length); 113 return key == read[]; 114 } 115 116 /** 117 Reads JSON encoded numbers from the stream. 118 119 Params: 120 reader = The stream reader. 121 isFloating = Whether the read number was floating point. 122 123 Returns: 124 The JSON number read from the stream. 125 */ 126 nstring readNumberJson(StreamReader reader, ref bool isFloating) { 127 char c = cast(char)reader.readU8(); 128 if (!c.isNumeric && c != '-' && c != '+') { 129 reader.skip(-1); 130 return nstring.init; 131 } 132 133 nstring result; 134 result ~= c; 135 136 // Read characters until they're no longer number-like. 137 bool isExponent; 138 while(true) { 139 c = cast(char)reader.readU8(); 140 141 // Integral part 142 if (!isFloating) { 143 if (c == '.') { 144 isFloating = true; 145 result ~= c; 146 continue; 147 } 148 149 if (c.isNumeric) { 150 result ~= c; 151 continue; 152 } 153 154 // No longer a number. 155 break; 156 } 157 158 // Fractional part 159 if (!isExponent) { 160 if (c == 'e' || c == 'E') { 161 isExponent = true; 162 result ~= c; 163 continue; 164 } 165 166 if (c.isNumeric) { 167 result ~= c; 168 continue; 169 } 170 171 // No longer a number. 172 break; 173 } 174 175 // Exponential part. 176 if (!(c.isNumeric || c == '-' || c == '+')) 177 break; 178 179 result ~= c; 180 } 181 182 reader.skip(-1); 183 return result; 184 } 185 186 /** 187 Reads a JSON string element from the given stream. 188 189 Params: 190 reader = The stream reader. 191 192 Returns: 193 The JSON string read from the stream. 194 */ 195 nstring readStringJson(StreamReader reader) { 196 char c = cast(char)reader.readU8(); 197 198 // Not a string. 199 if (c != '"') { 200 reader.skip(-1); 201 return nstring.init; 202 } 203 204 nstring result; 205 do { 206 c = cast(char)reader.readU8(); 207 if (c == '"') 208 break; 209 210 result ~= c; 211 212 // Read escape codes. 213 if (c == '\\') { 214 c = cast(char)reader.readU8(); 215 result ~= c; 216 } 217 } while(c != '"'); 218 return result; 219 } 220 221 /** 222 Gets whether the upcoming character is a numeric symbol. 223 224 Params: 225 c = the character 226 227 Returns: 228 Whether the upcoming character is a valid 229 */ 230 bool isNumeric(char c) { 231 return (c >= '0' && c <= '9'); 232 }