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