1/* 2 * Copyright (c) 2022-2023 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http:// www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const { XMessage } = require('../message/XMessage'); 17const { NapiLog } = require('./NapiLog'); 18 19function code(s) { 20 return s.charCodeAt(0); 21} 22 23function isSpace(c) { 24 return c === code(' ') || c === code('\t') || c === code('\r'); 25} 26 27function isalpha(c) { 28 if (code('a') <= c[0] && c[0] <= code('z')) { 29 return true; 30 } 31 if (code('A') <= c[0] && c[0] <= code('Z')) { 32 return true; 33 } 34 return false; 35} 36 37function isalnum(c) { 38 return isalpha(c) || isNum(c); 39} 40 41function isNum(c) { 42 return code('0') <= c[0] && c[0] <= code('9'); 43} 44 45function toStr(c) { 46 return String.fromCharCode(c[0]); 47} 48 49class TokenType {} 50TokenType.NUMBER = 256; 51TokenType.TEMPLATE = 257; 52TokenType.LITERAL = 258; 53TokenType.STRING = 259; 54TokenType.REF_PATH = 260; 55TokenType.FILE_PATH = 261; 56TokenType.ROOT = 262; 57TokenType.INCLUDE = 263; 58TokenType.DELETE = 264; 59TokenType.BOOL = 265; 60TokenType.EOF = -1; 61 62class Lexer { 63 constructor() { 64 this.keyWords_ = { 65 '#include': TokenType.INCLUDE, 66 root: TokenType.ROOT, 67 delete: TokenType.DELETE, 68 template: TokenType.TEMPLATE, 69 }; 70 } 71 72 initialize(sourceName) { 73 this.srcName_ = sourceName; 74 if (!(sourceName in Lexer.FILE_AND_DATA)) { 75 XMessage.gi().send('getfiledata', sourceName); 76 return false; 77 } 78 this.data_ = Lexer.FILE_AND_DATA[sourceName]; 79 NapiLog.logError('------------data start----------------'); 80 NapiLog.logError(sourceName); 81 NapiLog.logError(Lexer.FILE_AND_DATA[sourceName]); 82 NapiLog.logError('------------data end------------------'); 83 84 this.bufferStart_ = 0; 85 this.bufferEnd_ = this.data_.length - 1; 86 this.lineno_ = 1; 87 this.lineLoc_ = 1; 88 89 return true; 90 } 91 92 lexInclude(token) { 93 this.consumeChar(); 94 this.lexFromLiteral(token); 95 if (token.strval !== 'include') { 96 return false; 97 } 98 99 token.type = TokenType.INCLUDE; 100 return true; 101 } 102 103 isConsumeCharCode(c) { 104 if ( 105 c[0] === code(';') || 106 c[0] === code(',') || 107 c[0] === code('[') || 108 c[0] === code(']') || 109 c[0] === code('{') || 110 c[0] === code('}') || 111 c[0] === code('=') || 112 c[0] === code('&') || 113 c[0] === code(':') 114 ) { 115 return true; 116 } 117 return false; 118 } 119 120 lex(token) { 121 let c = []; 122 this.initToken(token); 123 do { 124 if (!this.peekChar(c, true)) { 125 token.type = TokenType.EOF; 126 return true; 127 } 128 if (c[0] === code('#')) { 129 return this.lexInclude(token); 130 } 131 if (isalpha(c)) { 132 this.lexFromLiteral(token); 133 return true; 134 } 135 136 if (isNum(c)) { 137 return this.lexFromNumber(token); 138 } 139 140 if (c[0] === code('/')) { 141 if (!this.processComment()) { 142 return false; 143 } 144 } else if (this.isConsumeCharCode(c)) { 145 this.consumeChar(); 146 token.type = c[0]; 147 token.lineNo = this.lineno_; 148 break; 149 } else if (c[0] === code('"')) { 150 return this.lexFromString(token); 151 } else if (c[0] === code('+') || c[0] === code('-')) { 152 return lexFromNumber(token); 153 } else if (c[0] === -1) { 154 token.type = -1; 155 break; 156 } else { 157 dealWithError( 158 "can not recognized character '" + toStr(c) + "'" + this.bufferStart_ 159 ); 160 return false; 161 } 162 } while (true); 163 return true; 164 } 165 166 peekChar(c, skipSpace) { 167 if (skipSpace) { 168 while ( 169 this.bufferStart_ <= this.bufferEnd_ && 170 (isSpace(this.data_[this.bufferStart_]) || 171 this.data_[this.bufferStart_] === code('\n')) 172 ) { 173 this.lineLoc_++; 174 if (this.data_[this.bufferStart_] === code('\n')) { 175 this.lineLoc_ = 0; 176 this.lineno_++; 177 } 178 this.bufferStart_++; 179 } 180 } 181 182 if (this.bufferStart_ > this.bufferEnd_) { 183 return false; 184 } 185 c[0] = this.data_[this.bufferStart_]; 186 return true; 187 } 188 189 initToken(token) { 190 token.type = 0; 191 token.numval = 0; 192 token.strval = ''; 193 token.src = this.srcName_; 194 token.lineNo = this.lineno_; 195 } 196 197 lexFromLiteral(token) { 198 let value = ''; 199 let c = []; 200 201 while (this.peekChar(c, false) && !isSpace(c[0])) { 202 if (!isalnum(c) && c[0] !== code('_') && c[0] !== code('.') && c[0] !== code('\\')) { 203 break; 204 } 205 value += toStr(c); 206 this.consumeChar(); 207 } 208 209 do { 210 if (value === 'true') { 211 token.type = TokenType.BOOL; 212 token.numval = 1; 213 break; 214 } else if (value === 'false') { 215 token.type = TokenType.BOOL; 216 token.numval = 0; 217 break; 218 } 219 220 if (value in this.keyWords_) { 221 token.type = this.keyWords_[value]; 222 break; 223 } 224 225 if (value.indexOf('.') >= 0) { 226 token.type = TokenType.REF_PATH; 227 } else { 228 token.type = TokenType.LITERAL; 229 } 230 } while (false); 231 232 token.strval = value; 233 token.lineNo = this.lineno_; 234 } 235 getRawChar() { 236 this.lineLoc_++; 237 let ret = this.data_[this.bufferStart_]; 238 this.bufferStart_++; 239 return ret; 240 } 241 getChar(c, skipSpace) { 242 let chr = this.getRawChar(); 243 if (skipSpace) { 244 while (isSpace(chr)) { 245 chr = this.getRawChar(); 246 } 247 } 248 249 if (chr === code('\n')) { 250 this.lineno_++; 251 this.lineLoc_ = 0; 252 } 253 c[0] = chr; 254 return true; 255 } 256 consumeChar() { 257 let c = []; 258 this.getChar(c, false); 259 } 260 261 lexFromOctalNumber(c, param) { 262 while (this.peekChar(c) && isNum(c)) { 263 this.consumeChar(); 264 param.value += toStr(c); 265 } 266 param.v = parseInt(param.value, 8); 267 param.baseSystem = 8; 268 } 269 270 lexFromHexNumber(c, param) { 271 this.consumeChar(); 272 while ( 273 this.peekChar(c, false) && 274 (isNum(c) || 275 (c[0] >= code('a') && c[0] <= code('f')) || 276 (c[0] >= code('A') && c[0] <= code('F'))) 277 ) { 278 param.value += toStr(c); 279 this.consumeChar(); 280 } 281 param.v = parseInt(param.value, 16); 282 param.baseSystem = 16; 283 } 284 285 lexFromBinaryNumber(c, param) { 286 this.consumeChar(); 287 while ( 288 this.peekChar(c, false) && 289 (c[0] === code('0') || c[0] === code('1')) 290 ) { 291 param.value += toStr(c); 292 this.consumeChar(); 293 } 294 param.v = parseInt(param.value, 2); 295 param.baseSystem = 2; 296 } 297 298 lexFromNumber(token) { 299 let c = []; 300 301 let errno = 0; 302 let param = { 303 value: '', 304 v: 0, 305 baseSystem: 10, 306 }; 307 308 this.getChar(c, false); 309 switch (c[0]) { 310 case code('0'): 311 if (!this.peekChar(c, true)) { 312 break; 313 } 314 if (isNum(c)) { 315 this.lexFromOctalNumber(c, param); 316 break; 317 } 318 if (c[0] === code('x') || code('x') === code('X')) { 319 this.lexFromHexNumber(c, param); 320 break; 321 } else if (c[0] === code('b')) { 322 this.lexFromBinaryNumber(c, param); 323 break; 324 } 325 break; 326 case code('+'): 327 case code('-'): 328 default: 329 param.value += toStr(c); 330 while (this.peekChar(c, true) && isNum(c)) { 331 this.consumeChar(); 332 param.value += toStr(c); 333 } 334 let baseSystem = 10; 335 param.v = BigInt(param.value, baseSystem); 336 param.baseSystem = baseSystem; 337 break; 338 } 339 340 if (errno !== 0) { 341 dealWithError('illegal number: ' + param.value); 342 return false; 343 } 344 token.type = TokenType.NUMBER; 345 token.numval = param.v; 346 token.lineNo = this.lineno_; 347 token.baseSystem = param.baseSystem; 348 return true; 349 } 350 351 lexFromString(token) { 352 let c = []; 353 this.getChar(c, false); 354 let value = ''; 355 while (this.getChar(c, false) && c[0] !== code('"')) { 356 value += toStr(c); 357 } 358 359 if (c[0] !== code('"')) { 360 dealWithError('unterminated string'); 361 return false; 362 } 363 token.type = TokenType.STRING; 364 token.strval = value; 365 token.lineNo = this.lineno_; 366 return true; 367 } 368 processComment() { 369 let c = []; 370 this.consumeChar(); 371 if (!this.getChar(c, true)) { 372 dealWithError('unterminated comment'); 373 return false; 374 } 375 376 if (c[0] === code('/')) { 377 while (c[0] !== code('\n')) { 378 if (!this.getChar(c, true)) { 379 break; 380 } 381 } 382 if (c[0] !== code('\n') && c[0] !== TokenType.EOF) { 383 dealWithError('unterminated signal line comment'); 384 return false; 385 } 386 } else if (c[0] === code('*')) { 387 while (this.getChar(c, true)) { 388 if (c[0] === code('*') && this.getChar(c, true) && c[0] === code('/')) { 389 return true; 390 } 391 } 392 if (c[0] !== code('/')) { 393 dealWithError('unterminated multi-line comment'); 394 return false; 395 } 396 } else { 397 dealWithError('invalid character'); 398 return false; 399 } 400 401 return true; 402 } 403 404 dealWithError(message) { 405 XMessage.gi().send('error', message); 406 NapiLog.logError(message + "'"); 407 } 408} 409Lexer.FILE_AND_DATA = {}; 410 411module.exports = { 412 Lexer, 413 TokenType, 414 code, 415}; 416