Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lexer.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. 'use strict';
  2. var lib = require('./lib');
  3. var whitespaceChars = " \n\t\r\xA0";
  4. var delimChars = '()[]{}%*-+~/#,:|.<>=!';
  5. var intChars = '0123456789';
  6. var BLOCK_START = '{%';
  7. var BLOCK_END = '%}';
  8. var VARIABLE_START = '{{';
  9. var VARIABLE_END = '}}';
  10. var COMMENT_START = '{#';
  11. var COMMENT_END = '#}';
  12. var TOKEN_STRING = 'string';
  13. var TOKEN_WHITESPACE = 'whitespace';
  14. var TOKEN_DATA = 'data';
  15. var TOKEN_BLOCK_START = 'block-start';
  16. var TOKEN_BLOCK_END = 'block-end';
  17. var TOKEN_VARIABLE_START = 'variable-start';
  18. var TOKEN_VARIABLE_END = 'variable-end';
  19. var TOKEN_COMMENT = 'comment';
  20. var TOKEN_LEFT_PAREN = 'left-paren';
  21. var TOKEN_RIGHT_PAREN = 'right-paren';
  22. var TOKEN_LEFT_BRACKET = 'left-bracket';
  23. var TOKEN_RIGHT_BRACKET = 'right-bracket';
  24. var TOKEN_LEFT_CURLY = 'left-curly';
  25. var TOKEN_RIGHT_CURLY = 'right-curly';
  26. var TOKEN_OPERATOR = 'operator';
  27. var TOKEN_COMMA = 'comma';
  28. var TOKEN_COLON = 'colon';
  29. var TOKEN_TILDE = 'tilde';
  30. var TOKEN_PIPE = 'pipe';
  31. var TOKEN_INT = 'int';
  32. var TOKEN_FLOAT = 'float';
  33. var TOKEN_BOOLEAN = 'boolean';
  34. var TOKEN_NONE = 'none';
  35. var TOKEN_SYMBOL = 'symbol';
  36. var TOKEN_SPECIAL = 'special';
  37. var TOKEN_REGEX = 'regex';
  38. function token(type, value, lineno, colno) {
  39. return {
  40. type: type,
  41. value: value,
  42. lineno: lineno,
  43. colno: colno
  44. };
  45. }
  46. var Tokenizer = /*#__PURE__*/function () {
  47. function Tokenizer(str, opts) {
  48. this.str = str;
  49. this.index = 0;
  50. this.len = str.length;
  51. this.lineno = 0;
  52. this.colno = 0;
  53. this.in_code = false;
  54. opts = opts || {};
  55. var tags = opts.tags || {};
  56. this.tags = {
  57. BLOCK_START: tags.blockStart || BLOCK_START,
  58. BLOCK_END: tags.blockEnd || BLOCK_END,
  59. VARIABLE_START: tags.variableStart || VARIABLE_START,
  60. VARIABLE_END: tags.variableEnd || VARIABLE_END,
  61. COMMENT_START: tags.commentStart || COMMENT_START,
  62. COMMENT_END: tags.commentEnd || COMMENT_END
  63. };
  64. this.trimBlocks = !!opts.trimBlocks;
  65. this.lstripBlocks = !!opts.lstripBlocks;
  66. }
  67. var _proto = Tokenizer.prototype;
  68. _proto.nextToken = function nextToken() {
  69. var lineno = this.lineno;
  70. var colno = this.colno;
  71. var tok;
  72. if (this.in_code) {
  73. // Otherwise, if we are in a block parse it as code
  74. var cur = this.current();
  75. if (this.isFinished()) {
  76. // We have nothing else to parse
  77. return null;
  78. } else if (cur === '"' || cur === '\'') {
  79. // We've hit a string
  80. return token(TOKEN_STRING, this._parseString(cur), lineno, colno);
  81. } else if (tok = this._extract(whitespaceChars)) {
  82. // We hit some whitespace
  83. return token(TOKEN_WHITESPACE, tok, lineno, colno);
  84. } else if ((tok = this._extractString(this.tags.BLOCK_END)) || (tok = this._extractString('-' + this.tags.BLOCK_END))) {
  85. // Special check for the block end tag
  86. //
  87. // It is a requirement that start and end tags are composed of
  88. // delimiter characters (%{}[] etc), and our code always
  89. // breaks on delimiters so we can assume the token parsing
  90. // doesn't consume these elsewhere
  91. this.in_code = false;
  92. if (this.trimBlocks) {
  93. cur = this.current();
  94. if (cur === '\n') {
  95. // Skip newline
  96. this.forward();
  97. } else if (cur === '\r') {
  98. // Skip CRLF newline
  99. this.forward();
  100. cur = this.current();
  101. if (cur === '\n') {
  102. this.forward();
  103. } else {
  104. // Was not a CRLF, so go back
  105. this.back();
  106. }
  107. }
  108. }
  109. return token(TOKEN_BLOCK_END, tok, lineno, colno);
  110. } else if ((tok = this._extractString(this.tags.VARIABLE_END)) || (tok = this._extractString('-' + this.tags.VARIABLE_END))) {
  111. // Special check for variable end tag (see above)
  112. this.in_code = false;
  113. return token(TOKEN_VARIABLE_END, tok, lineno, colno);
  114. } else if (cur === 'r' && this.str.charAt(this.index + 1) === '/') {
  115. // Skip past 'r/'.
  116. this.forwardN(2); // Extract until the end of the regex -- / ends it, \/ does not.
  117. var regexBody = '';
  118. while (!this.isFinished()) {
  119. if (this.current() === '/' && this.previous() !== '\\') {
  120. this.forward();
  121. break;
  122. } else {
  123. regexBody += this.current();
  124. this.forward();
  125. }
  126. } // Check for flags.
  127. // The possible flags are according to https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
  128. var POSSIBLE_FLAGS = ['g', 'i', 'm', 'y'];
  129. var regexFlags = '';
  130. while (!this.isFinished()) {
  131. var isCurrentAFlag = POSSIBLE_FLAGS.indexOf(this.current()) !== -1;
  132. if (isCurrentAFlag) {
  133. regexFlags += this.current();
  134. this.forward();
  135. } else {
  136. break;
  137. }
  138. }
  139. return token(TOKEN_REGEX, {
  140. body: regexBody,
  141. flags: regexFlags
  142. }, lineno, colno);
  143. } else if (delimChars.indexOf(cur) !== -1) {
  144. // We've hit a delimiter (a special char like a bracket)
  145. this.forward();
  146. var complexOps = ['==', '===', '!=', '!==', '<=', '>=', '//', '**'];
  147. var curComplex = cur + this.current();
  148. var type;
  149. if (lib.indexOf(complexOps, curComplex) !== -1) {
  150. this.forward();
  151. cur = curComplex; // See if this is a strict equality/inequality comparator
  152. if (lib.indexOf(complexOps, curComplex + this.current()) !== -1) {
  153. cur = curComplex + this.current();
  154. this.forward();
  155. }
  156. }
  157. switch (cur) {
  158. case '(':
  159. type = TOKEN_LEFT_PAREN;
  160. break;
  161. case ')':
  162. type = TOKEN_RIGHT_PAREN;
  163. break;
  164. case '[':
  165. type = TOKEN_LEFT_BRACKET;
  166. break;
  167. case ']':
  168. type = TOKEN_RIGHT_BRACKET;
  169. break;
  170. case '{':
  171. type = TOKEN_LEFT_CURLY;
  172. break;
  173. case '}':
  174. type = TOKEN_RIGHT_CURLY;
  175. break;
  176. case ',':
  177. type = TOKEN_COMMA;
  178. break;
  179. case ':':
  180. type = TOKEN_COLON;
  181. break;
  182. case '~':
  183. type = TOKEN_TILDE;
  184. break;
  185. case '|':
  186. type = TOKEN_PIPE;
  187. break;
  188. default:
  189. type = TOKEN_OPERATOR;
  190. }
  191. return token(type, cur, lineno, colno);
  192. } else {
  193. // We are not at whitespace or a delimiter, so extract the
  194. // text and parse it
  195. tok = this._extractUntil(whitespaceChars + delimChars);
  196. if (tok.match(/^[-+]?[0-9]+$/)) {
  197. if (this.current() === '.') {
  198. this.forward();
  199. var dec = this._extract(intChars);
  200. return token(TOKEN_FLOAT, tok + '.' + dec, lineno, colno);
  201. } else {
  202. return token(TOKEN_INT, tok, lineno, colno);
  203. }
  204. } else if (tok.match(/^(true|false)$/)) {
  205. return token(TOKEN_BOOLEAN, tok, lineno, colno);
  206. } else if (tok === 'none') {
  207. return token(TOKEN_NONE, tok, lineno, colno);
  208. /*
  209. * Added to make the test `null is null` evaluate truthily.
  210. * Otherwise, Nunjucks will look up null in the context and
  211. * return `undefined`, which is not what we want. This *may* have
  212. * consequences is someone is using null in their templates as a
  213. * variable.
  214. */
  215. } else if (tok === 'null') {
  216. return token(TOKEN_NONE, tok, lineno, colno);
  217. } else if (tok) {
  218. return token(TOKEN_SYMBOL, tok, lineno, colno);
  219. } else {
  220. throw new Error('Unexpected value while parsing: ' + tok);
  221. }
  222. }
  223. } else {
  224. // Parse out the template text, breaking on tag
  225. // delimiters because we need to look for block/variable start
  226. // tags (don't use the full delimChars for optimization)
  227. var beginChars = this.tags.BLOCK_START.charAt(0) + this.tags.VARIABLE_START.charAt(0) + this.tags.COMMENT_START.charAt(0) + this.tags.COMMENT_END.charAt(0);
  228. if (this.isFinished()) {
  229. return null;
  230. } else if ((tok = this._extractString(this.tags.BLOCK_START + '-')) || (tok = this._extractString(this.tags.BLOCK_START))) {
  231. this.in_code = true;
  232. return token(TOKEN_BLOCK_START, tok, lineno, colno);
  233. } else if ((tok = this._extractString(this.tags.VARIABLE_START + '-')) || (tok = this._extractString(this.tags.VARIABLE_START))) {
  234. this.in_code = true;
  235. return token(TOKEN_VARIABLE_START, tok, lineno, colno);
  236. } else {
  237. tok = '';
  238. var data;
  239. var inComment = false;
  240. if (this._matches(this.tags.COMMENT_START)) {
  241. inComment = true;
  242. tok = this._extractString(this.tags.COMMENT_START);
  243. } // Continually consume text, breaking on the tag delimiter
  244. // characters and checking to see if it's a start tag.
  245. //
  246. // We could hit the end of the template in the middle of
  247. // our looping, so check for the null return value from
  248. // _extractUntil
  249. while ((data = this._extractUntil(beginChars)) !== null) {
  250. tok += data;
  251. if ((this._matches(this.tags.BLOCK_START) || this._matches(this.tags.VARIABLE_START) || this._matches(this.tags.COMMENT_START)) && !inComment) {
  252. if (this.lstripBlocks && this._matches(this.tags.BLOCK_START) && this.colno > 0 && this.colno <= tok.length) {
  253. var lastLine = tok.slice(-this.colno);
  254. if (/^\s+$/.test(lastLine)) {
  255. // Remove block leading whitespace from beginning of the string
  256. tok = tok.slice(0, -this.colno);
  257. if (!tok.length) {
  258. // All data removed, collapse to avoid unnecessary nodes
  259. // by returning next token (block start)
  260. return this.nextToken();
  261. }
  262. }
  263. } // If it is a start tag, stop looping
  264. break;
  265. } else if (this._matches(this.tags.COMMENT_END)) {
  266. if (!inComment) {
  267. throw new Error('unexpected end of comment');
  268. }
  269. tok += this._extractString(this.tags.COMMENT_END);
  270. break;
  271. } else {
  272. // It does not match any tag, so add the character and
  273. // carry on
  274. tok += this.current();
  275. this.forward();
  276. }
  277. }
  278. if (data === null && inComment) {
  279. throw new Error('expected end of comment, got end of file');
  280. }
  281. return token(inComment ? TOKEN_COMMENT : TOKEN_DATA, tok, lineno, colno);
  282. }
  283. }
  284. };
  285. _proto._parseString = function _parseString(delimiter) {
  286. this.forward();
  287. var str = '';
  288. while (!this.isFinished() && this.current() !== delimiter) {
  289. var cur = this.current();
  290. if (cur === '\\') {
  291. this.forward();
  292. switch (this.current()) {
  293. case 'n':
  294. str += '\n';
  295. break;
  296. case 't':
  297. str += '\t';
  298. break;
  299. case 'r':
  300. str += '\r';
  301. break;
  302. default:
  303. str += this.current();
  304. }
  305. this.forward();
  306. } else {
  307. str += cur;
  308. this.forward();
  309. }
  310. }
  311. this.forward();
  312. return str;
  313. };
  314. _proto._matches = function _matches(str) {
  315. if (this.index + str.length > this.len) {
  316. return null;
  317. }
  318. var m = this.str.slice(this.index, this.index + str.length);
  319. return m === str;
  320. };
  321. _proto._extractString = function _extractString(str) {
  322. if (this._matches(str)) {
  323. this.forwardN(str.length);
  324. return str;
  325. }
  326. return null;
  327. };
  328. _proto._extractUntil = function _extractUntil(charString) {
  329. // Extract all non-matching chars, with the default matching set
  330. // to everything
  331. return this._extractMatching(true, charString || '');
  332. };
  333. _proto._extract = function _extract(charString) {
  334. // Extract all matching chars (no default, so charString must be
  335. // explicit)
  336. return this._extractMatching(false, charString);
  337. };
  338. _proto._extractMatching = function _extractMatching(breakOnMatch, charString) {
  339. // Pull out characters until a breaking char is hit.
  340. // If breakOnMatch is false, a non-matching char stops it.
  341. // If breakOnMatch is true, a matching char stops it.
  342. if (this.isFinished()) {
  343. return null;
  344. }
  345. var first = charString.indexOf(this.current()); // Only proceed if the first character doesn't meet our condition
  346. if (breakOnMatch && first === -1 || !breakOnMatch && first !== -1) {
  347. var t = this.current();
  348. this.forward(); // And pull out all the chars one at a time until we hit a
  349. // breaking char
  350. var idx = charString.indexOf(this.current());
  351. while ((breakOnMatch && idx === -1 || !breakOnMatch && idx !== -1) && !this.isFinished()) {
  352. t += this.current();
  353. this.forward();
  354. idx = charString.indexOf(this.current());
  355. }
  356. return t;
  357. }
  358. return '';
  359. };
  360. _proto._extractRegex = function _extractRegex(regex) {
  361. var matches = this.currentStr().match(regex);
  362. if (!matches) {
  363. return null;
  364. } // Move forward whatever was matched
  365. this.forwardN(matches[0].length);
  366. return matches;
  367. };
  368. _proto.isFinished = function isFinished() {
  369. return this.index >= this.len;
  370. };
  371. _proto.forwardN = function forwardN(n) {
  372. for (var i = 0; i < n; i++) {
  373. this.forward();
  374. }
  375. };
  376. _proto.forward = function forward() {
  377. this.index++;
  378. if (this.previous() === '\n') {
  379. this.lineno++;
  380. this.colno = 0;
  381. } else {
  382. this.colno++;
  383. }
  384. };
  385. _proto.backN = function backN(n) {
  386. for (var i = 0; i < n; i++) {
  387. this.back();
  388. }
  389. };
  390. _proto.back = function back() {
  391. this.index--;
  392. if (this.current() === '\n') {
  393. this.lineno--;
  394. var idx = this.src.lastIndexOf('\n', this.index - 1);
  395. if (idx === -1) {
  396. this.colno = this.index;
  397. } else {
  398. this.colno = this.index - idx;
  399. }
  400. } else {
  401. this.colno--;
  402. }
  403. } // current returns current character
  404. ;
  405. _proto.current = function current() {
  406. if (!this.isFinished()) {
  407. return this.str.charAt(this.index);
  408. }
  409. return '';
  410. } // currentStr returns what's left of the unparsed string
  411. ;
  412. _proto.currentStr = function currentStr() {
  413. if (!this.isFinished()) {
  414. return this.str.substr(this.index);
  415. }
  416. return '';
  417. };
  418. _proto.previous = function previous() {
  419. return this.str.charAt(this.index - 1);
  420. };
  421. return Tokenizer;
  422. }();
  423. module.exports = {
  424. lex: function lex(src, opts) {
  425. return new Tokenizer(src, opts);
  426. },
  427. TOKEN_STRING: TOKEN_STRING,
  428. TOKEN_WHITESPACE: TOKEN_WHITESPACE,
  429. TOKEN_DATA: TOKEN_DATA,
  430. TOKEN_BLOCK_START: TOKEN_BLOCK_START,
  431. TOKEN_BLOCK_END: TOKEN_BLOCK_END,
  432. TOKEN_VARIABLE_START: TOKEN_VARIABLE_START,
  433. TOKEN_VARIABLE_END: TOKEN_VARIABLE_END,
  434. TOKEN_COMMENT: TOKEN_COMMENT,
  435. TOKEN_LEFT_PAREN: TOKEN_LEFT_PAREN,
  436. TOKEN_RIGHT_PAREN: TOKEN_RIGHT_PAREN,
  437. TOKEN_LEFT_BRACKET: TOKEN_LEFT_BRACKET,
  438. TOKEN_RIGHT_BRACKET: TOKEN_RIGHT_BRACKET,
  439. TOKEN_LEFT_CURLY: TOKEN_LEFT_CURLY,
  440. TOKEN_RIGHT_CURLY: TOKEN_RIGHT_CURLY,
  441. TOKEN_OPERATOR: TOKEN_OPERATOR,
  442. TOKEN_COMMA: TOKEN_COMMA,
  443. TOKEN_COLON: TOKEN_COLON,
  444. TOKEN_TILDE: TOKEN_TILDE,
  445. TOKEN_PIPE: TOKEN_PIPE,
  446. TOKEN_INT: TOKEN_INT,
  447. TOKEN_FLOAT: TOKEN_FLOAT,
  448. TOKEN_BOOLEAN: TOKEN_BOOLEAN,
  449. TOKEN_NONE: TOKEN_NONE,
  450. TOKEN_SYMBOL: TOKEN_SYMBOL,
  451. TOKEN_SPECIAL: TOKEN_SPECIAL,
  452. TOKEN_REGEX: TOKEN_REGEX
  453. };