formcalc_lexer.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.Token = exports.TOKEN = exports.Lexer = void 0;
  27. const KEYWORDS = new Set(["and", "break", "continue", "do", "downto", "else", "elseif", "end", "endfor", "endfunc", "endif", "endwhile", "eq", "exit", "for", "foreach", "func", "ge", "gt", "if", "in", "infinity", "le", "lt", "nan", "ne", "not", "null", "or", "return", "step", "then", "this", "throw", "upto", "var", "while"]);
  28. const TOKEN = {
  29. and: 0,
  30. divide: 1,
  31. dot: 2,
  32. dotDot: 3,
  33. dotHash: 4,
  34. dotStar: 5,
  35. eq: 6,
  36. ge: 7,
  37. gt: 8,
  38. le: 9,
  39. leftBracket: 10,
  40. leftParen: 11,
  41. lt: 12,
  42. minus: 13,
  43. ne: 14,
  44. not: 15,
  45. null: 16,
  46. number: 17,
  47. or: 18,
  48. plus: 19,
  49. rightBracket: 20,
  50. rightParen: 21,
  51. string: 22,
  52. this: 23,
  53. times: 24,
  54. identifier: 25,
  55. break: 26,
  56. continue: 27,
  57. do: 28,
  58. for: 29,
  59. foreach: 30,
  60. func: 31,
  61. if: 32,
  62. var: 33,
  63. while: 34,
  64. assign: 35,
  65. comma: 36,
  66. downto: 37,
  67. else: 38,
  68. elseif: 39,
  69. end: 40,
  70. endif: 41,
  71. endfor: 42,
  72. endfunc: 43,
  73. endwhile: 44,
  74. eof: 45,
  75. exit: 46,
  76. in: 47,
  77. infinity: 48,
  78. nan: 49,
  79. return: 50,
  80. step: 51,
  81. then: 52,
  82. throw: 53,
  83. upto: 54
  84. };
  85. exports.TOKEN = TOKEN;
  86. const hexPattern = /^[uU]([0-9a-fA-F]{4,8})/;
  87. const numberPattern = /^\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?/;
  88. const dotNumberPattern = /^\d*(?:[Ee][+-]?\d+)?/;
  89. const eolPattern = /[\r\n]+/;
  90. const identifierPattern = new RegExp("^[\\p{L}_$!][\\p{L}\\p{N}_$]*", "u");
  91. class Token {
  92. constructor(id, value = null) {
  93. this.id = id;
  94. this.value = value;
  95. }
  96. }
  97. exports.Token = Token;
  98. const Singletons = function () {
  99. const obj = Object.create(null);
  100. const nonSingleton = new Set(["identifier", "string", "number", "nan", "infinity"]);
  101. for (const [name, id] of Object.entries(TOKEN)) {
  102. if (!nonSingleton.has(name)) {
  103. obj[name] = new Token(id);
  104. }
  105. }
  106. obj.nan = new Token(TOKEN.number, NaN);
  107. obj.infinity = new Token(TOKEN.number, Infinity);
  108. return obj;
  109. }();
  110. class Lexer {
  111. constructor(data) {
  112. this.data = data;
  113. this.pos = 0;
  114. this.len = data.length;
  115. this.strBuf = [];
  116. }
  117. skipUntilEOL() {
  118. const match = this.data.slice(this.pos).match(eolPattern);
  119. if (match) {
  120. this.pos += match.index + match[0].length;
  121. } else {
  122. this.pos = this.len;
  123. }
  124. }
  125. getIdentifier() {
  126. this.pos--;
  127. const match = this.data.slice(this.pos).match(identifierPattern);
  128. if (!match) {
  129. throw new Error(`Invalid token in FormCalc expression at position ${this.pos}.`);
  130. }
  131. const identifier = this.data.slice(this.pos, this.pos + match[0].length);
  132. this.pos += match[0].length;
  133. const lower = identifier.toLowerCase();
  134. if (!KEYWORDS.has(lower)) {
  135. return new Token(TOKEN.identifier, identifier);
  136. }
  137. return Singletons[lower];
  138. }
  139. getString() {
  140. const str = this.strBuf;
  141. const data = this.data;
  142. let start = this.pos;
  143. while (this.pos < this.len) {
  144. const char = data.charCodeAt(this.pos++);
  145. if (char === 0x22) {
  146. if (data.charCodeAt(this.pos) === 0x22) {
  147. str.push(data.slice(start, this.pos++));
  148. start = this.pos;
  149. continue;
  150. }
  151. break;
  152. }
  153. if (char === 0x5c) {
  154. const match = data.substring(this.pos, this.pos + 10).match(hexPattern);
  155. if (!match) {
  156. continue;
  157. }
  158. str.push(data.slice(start, this.pos - 1));
  159. const code = match[1];
  160. if (code.length === 4) {
  161. str.push(String.fromCharCode(parseInt(code, 16)));
  162. start = this.pos += 5;
  163. } else if (code.length !== 8) {
  164. str.push(String.fromCharCode(parseInt(code.slice(0, 4), 16)));
  165. start = this.pos += 5;
  166. } else {
  167. str.push(String.fromCharCode(parseInt(code, 16)));
  168. start = this.pos += 9;
  169. }
  170. }
  171. }
  172. const lastChunk = data.slice(start, this.pos - 1);
  173. if (str.length === 0) {
  174. return new Token(TOKEN.string, lastChunk);
  175. }
  176. str.push(lastChunk);
  177. const string = str.join("");
  178. str.length = 0;
  179. return new Token(TOKEN.string, string);
  180. }
  181. getNumber(first) {
  182. const match = this.data.substring(this.pos).match(numberPattern);
  183. if (!match[0]) {
  184. return new Token(TOKEN.number, first - 0x30);
  185. }
  186. const number = parseFloat(this.data.substring(this.pos - 1, this.pos + match[0].length));
  187. this.pos += match[0].length;
  188. return new Token(TOKEN.number, number);
  189. }
  190. getCompOperator(alt1, alt2) {
  191. if (this.data.charCodeAt(this.pos) === 0x3d) {
  192. this.pos++;
  193. return alt1;
  194. }
  195. return alt2;
  196. }
  197. getLower() {
  198. const char = this.data.charCodeAt(this.pos);
  199. if (char === 0x3d) {
  200. this.pos++;
  201. return Singletons.le;
  202. }
  203. if (char === 0x3e) {
  204. this.pos++;
  205. return Singletons.ne;
  206. }
  207. return Singletons.lt;
  208. }
  209. getSlash() {
  210. if (this.data.charCodeAt(this.pos) === 0x2f) {
  211. this.skipUntilEOL();
  212. return false;
  213. }
  214. return true;
  215. }
  216. getDot() {
  217. const char = this.data.charCodeAt(this.pos);
  218. if (char === 0x2e) {
  219. this.pos++;
  220. return Singletons.dotDot;
  221. }
  222. if (char === 0x2a) {
  223. this.pos++;
  224. return Singletons.dotStar;
  225. }
  226. if (char === 0x23) {
  227. this.pos++;
  228. return Singletons.dotHash;
  229. }
  230. if (0x30 <= char && char <= 0x39) {
  231. this.pos++;
  232. const match = this.data.substring(this.pos).match(dotNumberPattern);
  233. if (!match) {
  234. return new Token(TOKEN.number, (char - 0x30) / 10);
  235. }
  236. const end = this.pos + match[0].length;
  237. const number = parseFloat(this.data.substring(this.pos - 2, end));
  238. this.pos = end;
  239. return new Token(TOKEN.number, number);
  240. }
  241. return Singletons.dot;
  242. }
  243. next() {
  244. while (this.pos < this.len) {
  245. const char = this.data.charCodeAt(this.pos++);
  246. switch (char) {
  247. case 0x09:
  248. case 0x0a:
  249. case 0x0b:
  250. case 0x0c:
  251. case 0x0d:
  252. case 0x20:
  253. break;
  254. case 0x22:
  255. return this.getString();
  256. case 0x26:
  257. return Singletons.and;
  258. case 0x28:
  259. return Singletons.leftParen;
  260. case 0x29:
  261. return Singletons.rightParen;
  262. case 0x2a:
  263. return Singletons.times;
  264. case 0x2b:
  265. return Singletons.plus;
  266. case 0x2c:
  267. return Singletons.comma;
  268. case 0x2d:
  269. return Singletons.minus;
  270. case 0x2e:
  271. return this.getDot();
  272. case 0x2f:
  273. if (this.getSlash()) {
  274. return Singletons.divide;
  275. }
  276. break;
  277. case 0x30:
  278. case 0x31:
  279. case 0x32:
  280. case 0x33:
  281. case 0x34:
  282. case 0x35:
  283. case 0x36:
  284. case 0x37:
  285. case 0x38:
  286. case 0x39:
  287. return this.getNumber(char);
  288. case 0x3b:
  289. this.skipUntilEOL();
  290. break;
  291. case 0x3c:
  292. return this.getLower();
  293. case 0x3d:
  294. return this.getCompOperator(Singletons.eq, Singletons.assign);
  295. case 0x3e:
  296. return this.getCompOperator(Singletons.ge, Singletons.gt);
  297. case 0x5b:
  298. return Singletons.leftBracket;
  299. case 0x5d:
  300. return Singletons.rightBracket;
  301. case 0x7c:
  302. return Singletons.or;
  303. default:
  304. return this.getIdentifier();
  305. }
  306. }
  307. return Singletons.eof;
  308. }
  309. }
  310. exports.Lexer = Lexer;