PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/app/node_modules/jade/lib/parser.js

https://bitbucket.org/jorritposthuma/inventory-counter-mobile
JavaScript | 714 lines | 433 code | 114 blank | 167 comment | 87 complexity | 031ce6015d1d5e53eefc5cbb349ea262 MD5 | raw file
Possible License(s): MIT, Apache-2.0
  1. /*!
  2. * Jade - Parser
  3. * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Lexer = require('./lexer')
  10. , nodes = require('./nodes');
  11. /**
  12. * Initialize `Parser` with the given input `str` and `filename`.
  13. *
  14. * @param {String} str
  15. * @param {String} filename
  16. * @param {Object} options
  17. * @api public
  18. */
  19. var Parser = exports = module.exports = function Parser(str, filename, options){
  20. this.input = str;
  21. this.lexer = new Lexer(str, options);
  22. this.filename = filename;
  23. this.blocks = {};
  24. this.mixins = {};
  25. this.options = options;
  26. this.contexts = [this];
  27. };
  28. /**
  29. * Tags that may not contain tags.
  30. */
  31. var textOnly = exports.textOnly = ['script', 'style'];
  32. /**
  33. * Parser prototype.
  34. */
  35. Parser.prototype = {
  36. /**
  37. * Push `parser` onto the context stack,
  38. * or pop and return a `Parser`.
  39. */
  40. context: function(parser){
  41. if (parser) {
  42. this.contexts.push(parser);
  43. } else {
  44. return this.contexts.pop();
  45. }
  46. },
  47. /**
  48. * Return the next token object.
  49. *
  50. * @return {Object}
  51. * @api private
  52. */
  53. advance: function(){
  54. return this.lexer.advance();
  55. },
  56. /**
  57. * Skip `n` tokens.
  58. *
  59. * @param {Number} n
  60. * @api private
  61. */
  62. skip: function(n){
  63. while (n--) this.advance();
  64. },
  65. /**
  66. * Single token lookahead.
  67. *
  68. * @return {Object}
  69. * @api private
  70. */
  71. peek: function() {
  72. return this.lookahead(1);
  73. },
  74. /**
  75. * Return lexer lineno.
  76. *
  77. * @return {Number}
  78. * @api private
  79. */
  80. line: function() {
  81. return this.lexer.lineno;
  82. },
  83. /**
  84. * `n` token lookahead.
  85. *
  86. * @param {Number} n
  87. * @return {Object}
  88. * @api private
  89. */
  90. lookahead: function(n){
  91. return this.lexer.lookahead(n);
  92. },
  93. /**
  94. * Parse input returning a string of js for evaluation.
  95. *
  96. * @return {String}
  97. * @api public
  98. */
  99. parse: function(){
  100. var block = new nodes.Block, parser;
  101. block.line = this.line();
  102. while ('eos' != this.peek().type) {
  103. if ('newline' == this.peek().type) {
  104. this.advance();
  105. } else {
  106. block.push(this.parseExpr());
  107. }
  108. }
  109. if (parser = this.extending) {
  110. this.context(parser);
  111. var ast = parser.parse();
  112. this.context();
  113. // hoist mixins
  114. for (var name in this.mixins)
  115. ast.unshift(this.mixins[name]);
  116. return ast;
  117. }
  118. return block;
  119. },
  120. /**
  121. * Expect the given type, or throw an exception.
  122. *
  123. * @param {String} type
  124. * @api private
  125. */
  126. expect: function(type){
  127. if (this.peek().type === type) {
  128. return this.advance();
  129. } else {
  130. throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
  131. }
  132. },
  133. /**
  134. * Accept the given `type`.
  135. *
  136. * @param {String} type
  137. * @api private
  138. */
  139. accept: function(type){
  140. if (this.peek().type === type) {
  141. return this.advance();
  142. }
  143. },
  144. /**
  145. * tag
  146. * | doctype
  147. * | mixin
  148. * | include
  149. * | filter
  150. * | comment
  151. * | text
  152. * | each
  153. * | code
  154. * | yield
  155. * | id
  156. * | class
  157. * | interpolation
  158. */
  159. parseExpr: function(){
  160. switch (this.peek().type) {
  161. case 'tag':
  162. return this.parseTag();
  163. case 'mixin':
  164. return this.parseMixin();
  165. case 'block':
  166. return this.parseBlock();
  167. case 'case':
  168. return this.parseCase();
  169. case 'when':
  170. return this.parseWhen();
  171. case 'default':
  172. return this.parseDefault();
  173. case 'extends':
  174. return this.parseExtends();
  175. case 'include':
  176. return this.parseInclude();
  177. case 'doctype':
  178. return this.parseDoctype();
  179. case 'filter':
  180. return this.parseFilter();
  181. case 'comment':
  182. return this.parseComment();
  183. case 'text':
  184. return this.parseText();
  185. case 'each':
  186. return this.parseEach();
  187. case 'code':
  188. return this.parseCode();
  189. case 'call':
  190. return this.parseCall();
  191. case 'interpolation':
  192. return this.parseInterpolation();
  193. case 'yield':
  194. this.advance();
  195. var block = new nodes.Block;
  196. block.yield = true;
  197. return block;
  198. case 'id':
  199. case 'class':
  200. var tok = this.advance();
  201. this.lexer.defer(this.lexer.tok('tag', 'div'));
  202. this.lexer.defer(tok);
  203. return this.parseExpr();
  204. default:
  205. throw new Error('unexpected token "' + this.peek().type + '"');
  206. }
  207. },
  208. /**
  209. * Text
  210. */
  211. parseText: function(){
  212. var tok = this.expect('text')
  213. , node = new nodes.Text(tok.val);
  214. node.line = this.line();
  215. return node;
  216. },
  217. /**
  218. * ':' expr
  219. * | block
  220. */
  221. parseBlockExpansion: function(){
  222. if (':' == this.peek().type) {
  223. this.advance();
  224. return new nodes.Block(this.parseExpr());
  225. } else {
  226. return this.block();
  227. }
  228. },
  229. /**
  230. * case
  231. */
  232. parseCase: function(){
  233. var val = this.expect('case').val
  234. , node = new nodes.Case(val);
  235. node.line = this.line();
  236. node.block = this.block();
  237. return node;
  238. },
  239. /**
  240. * when
  241. */
  242. parseWhen: function(){
  243. var val = this.expect('when').val
  244. return new nodes.Case.When(val, this.parseBlockExpansion());
  245. },
  246. /**
  247. * default
  248. */
  249. parseDefault: function(){
  250. this.expect('default');
  251. return new nodes.Case.When('default', this.parseBlockExpansion());
  252. },
  253. /**
  254. * code
  255. */
  256. parseCode: function(){
  257. var tok = this.expect('code')
  258. , node = new nodes.Code(tok.val, tok.buffer, tok.escape)
  259. , block
  260. , i = 1;
  261. node.line = this.line();
  262. while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i;
  263. block = 'indent' == this.lookahead(i).type;
  264. if (block) {
  265. this.skip(i-1);
  266. node.block = this.block();
  267. }
  268. return node;
  269. },
  270. /**
  271. * comment
  272. */
  273. parseComment: function(){
  274. var tok = this.expect('comment')
  275. , node;
  276. if ('indent' == this.peek().type) {
  277. node = new nodes.BlockComment(tok.val, this.block(), tok.buffer);
  278. } else {
  279. node = new nodes.Comment(tok.val, tok.buffer);
  280. }
  281. node.line = this.line();
  282. return node;
  283. },
  284. /**
  285. * doctype
  286. */
  287. parseDoctype: function(){
  288. var tok = this.expect('doctype')
  289. , node = new nodes.Doctype(tok.val);
  290. node.line = this.line();
  291. return node;
  292. },
  293. /**
  294. * filter attrs? text-block
  295. */
  296. parseFilter: function(){
  297. var block
  298. , tok = this.expect('filter')
  299. , attrs = this.accept('attrs');
  300. this.lexer.pipeless = true;
  301. block = this.parseTextBlock();
  302. this.lexer.pipeless = false;
  303. var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
  304. node.line = this.line();
  305. return node;
  306. },
  307. /**
  308. * tag ':' attrs? block
  309. */
  310. parseASTFilter: function(){
  311. var block
  312. , tok = this.expect('tag')
  313. , attrs = this.accept('attrs');
  314. this.expect(':');
  315. block = this.block();
  316. var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
  317. node.line = this.line();
  318. return node;
  319. },
  320. /**
  321. * each block
  322. */
  323. parseEach: function(){
  324. var tok = this.expect('each')
  325. , node = new nodes.Each(tok.code, tok.val, tok.key);
  326. node.line = this.line();
  327. node.block = this.block();
  328. if (this.peek().type == 'code' && this.peek().val == 'else') {
  329. this.advance();
  330. node.alternative = this.block();
  331. }
  332. return node;
  333. },
  334. /**
  335. * 'extends' name
  336. */
  337. parseExtends: function(){
  338. var path = require('path')
  339. , fs = require('fs')
  340. , dirname = path.dirname
  341. , basename = path.basename
  342. , join = path.join;
  343. if (!this.filename)
  344. throw new Error('the "filename" option is required to extend templates');
  345. var path = this.expect('extends').val.trim()
  346. , dir = dirname(this.filename);
  347. var path = join(dir, path + '.jade')
  348. , str = fs.readFileSync(path, 'utf8')
  349. , parser = new Parser(str, path, this.options);
  350. parser.blocks = this.blocks;
  351. parser.contexts = this.contexts;
  352. this.extending = parser;
  353. // TODO: null node
  354. return new nodes.Literal('');
  355. },
  356. /**
  357. * 'block' name block
  358. */
  359. parseBlock: function(){
  360. var block = this.expect('block')
  361. , mode = block.mode
  362. , name = block.val.trim();
  363. block = 'indent' == this.peek().type
  364. ? this.block()
  365. : new nodes.Block(new nodes.Literal(''));
  366. var prev = this.blocks[name];
  367. if (prev) {
  368. switch (prev.mode) {
  369. case 'append':
  370. block.nodes = block.nodes.concat(prev.nodes);
  371. prev = block;
  372. break;
  373. case 'prepend':
  374. block.nodes = prev.nodes.concat(block.nodes);
  375. prev = block;
  376. break;
  377. }
  378. }
  379. block.mode = mode;
  380. return this.blocks[name] = prev || block;
  381. },
  382. /**
  383. * include block?
  384. */
  385. parseInclude: function(){
  386. var path = require('path')
  387. , fs = require('fs')
  388. , dirname = path.dirname
  389. , basename = path.basename
  390. , join = path.join;
  391. var path = this.expect('include').val.trim()
  392. , dir = dirname(this.filename);
  393. if (!this.filename)
  394. throw new Error('the "filename" option is required to use includes');
  395. // no extension
  396. if (!~basename(path).indexOf('.')) {
  397. path += '.jade';
  398. }
  399. // non-jade
  400. if ('.jade' != path.substr(-5)) {
  401. var path = join(dir, path)
  402. , str = fs.readFileSync(path, 'utf8');
  403. return new nodes.Literal(str);
  404. }
  405. var path = join(dir, path)
  406. , str = fs.readFileSync(path, 'utf8')
  407. , parser = new Parser(str, path, this.options);
  408. parser.blocks = this.blocks;
  409. parser.mixins = this.mixins;
  410. this.context(parser);
  411. var ast = parser.parse();
  412. this.context();
  413. ast.filename = path;
  414. if ('indent' == this.peek().type) {
  415. ast.includeBlock().push(this.block());
  416. }
  417. return ast;
  418. },
  419. /**
  420. * call ident block
  421. */
  422. parseCall: function(){
  423. var tok = this.expect('call')
  424. , name = tok.val
  425. , args = tok.args
  426. , mixin = new nodes.Mixin(name, args, new nodes.Block, true);
  427. this.tag(mixin);
  428. if (mixin.block.isEmpty()) mixin.block = null;
  429. return mixin;
  430. },
  431. /**
  432. * mixin block
  433. */
  434. parseMixin: function(){
  435. var tok = this.expect('mixin')
  436. , name = tok.val
  437. , args = tok.args
  438. , mixin;
  439. // definition
  440. if ('indent' == this.peek().type) {
  441. mixin = new nodes.Mixin(name, args, this.block(), false);
  442. this.mixins[name] = mixin;
  443. return mixin;
  444. // call
  445. } else {
  446. return new nodes.Mixin(name, args, null, true);
  447. }
  448. },
  449. /**
  450. * indent (text | newline)* outdent
  451. */
  452. parseTextBlock: function(){
  453. var block = new nodes.Block;
  454. block.line = this.line();
  455. var spaces = this.expect('indent').val;
  456. if (null == this._spaces) this._spaces = spaces;
  457. var indent = Array(spaces - this._spaces + 1).join(' ');
  458. while ('outdent' != this.peek().type) {
  459. switch (this.peek().type) {
  460. case 'newline':
  461. this.advance();
  462. break;
  463. case 'indent':
  464. this.parseTextBlock().nodes.forEach(function(node){
  465. block.push(node);
  466. });
  467. break;
  468. default:
  469. var text = new nodes.Text(indent + this.advance().val);
  470. text.line = this.line();
  471. block.push(text);
  472. }
  473. }
  474. if (spaces == this._spaces) this._spaces = null;
  475. this.expect('outdent');
  476. return block;
  477. },
  478. /**
  479. * indent expr* outdent
  480. */
  481. block: function(){
  482. var block = new nodes.Block;
  483. block.line = this.line();
  484. this.expect('indent');
  485. while ('outdent' != this.peek().type) {
  486. if ('newline' == this.peek().type) {
  487. this.advance();
  488. } else {
  489. block.push(this.parseExpr());
  490. }
  491. }
  492. this.expect('outdent');
  493. return block;
  494. },
  495. /**
  496. * interpolation (attrs | class | id)* (text | code | ':')? newline* block?
  497. */
  498. parseInterpolation: function(){
  499. var tok = this.advance();
  500. var tag = new nodes.Tag(tok.val);
  501. tag.buffer = true;
  502. return this.tag(tag);
  503. },
  504. /**
  505. * tag (attrs | class | id)* (text | code | ':')? newline* block?
  506. */
  507. parseTag: function(){
  508. // ast-filter look-ahead
  509. var i = 2;
  510. if ('attrs' == this.lookahead(i).type) ++i;
  511. if (':' == this.lookahead(i).type) {
  512. if ('indent' == this.lookahead(++i).type) {
  513. return this.parseASTFilter();
  514. }
  515. }
  516. var tok = this.advance()
  517. , tag = new nodes.Tag(tok.val);
  518. tag.selfClosing = tok.selfClosing;
  519. return this.tag(tag);
  520. },
  521. /**
  522. * Parse tag.
  523. */
  524. tag: function(tag){
  525. var dot;
  526. tag.line = this.line();
  527. // (attrs | class | id)*
  528. out:
  529. while (true) {
  530. switch (this.peek().type) {
  531. case 'id':
  532. case 'class':
  533. var tok = this.advance();
  534. tag.setAttribute(tok.type, "'" + tok.val + "'");
  535. continue;
  536. case 'attrs':
  537. var tok = this.advance()
  538. , obj = tok.attrs
  539. , escaped = tok.escaped
  540. , names = Object.keys(obj);
  541. if (tok.selfClosing) tag.selfClosing = true;
  542. for (var i = 0, len = names.length; i < len; ++i) {
  543. var name = names[i]
  544. , val = obj[name];
  545. tag.setAttribute(name, val, escaped[name]);
  546. }
  547. continue;
  548. default:
  549. break out;
  550. }
  551. }
  552. // check immediate '.'
  553. if ('.' == this.peek().val) {
  554. dot = tag.textOnly = true;
  555. this.advance();
  556. }
  557. // (text | code | ':')?
  558. switch (this.peek().type) {
  559. case 'text':
  560. tag.block.push(this.parseText());
  561. break;
  562. case 'code':
  563. tag.code = this.parseCode();
  564. break;
  565. case ':':
  566. this.advance();
  567. tag.block = new nodes.Block;
  568. tag.block.push(this.parseExpr());
  569. break;
  570. }
  571. // newline*
  572. while ('newline' == this.peek().type) this.advance();
  573. tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name);
  574. // script special-case
  575. if ('script' == tag.name) {
  576. var type = tag.getAttribute('type');
  577. if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
  578. tag.textOnly = false;
  579. }
  580. }
  581. // block?
  582. if ('indent' == this.peek().type) {
  583. if (tag.textOnly) {
  584. this.lexer.pipeless = true;
  585. tag.block = this.parseTextBlock();
  586. this.lexer.pipeless = false;
  587. } else {
  588. var block = this.block();
  589. if (tag.block) {
  590. for (var i = 0, len = block.nodes.length; i < len; ++i) {
  591. tag.block.push(block.nodes[i]);
  592. }
  593. } else {
  594. tag.block = block;
  595. }
  596. }
  597. }
  598. return tag;
  599. }
  600. };