A templating language that looks like Lisp and compiles to HTML
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.

parser.js 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. const Node = require('./node')
  2. const tokenTypes = require('./tokenTypes')
  3. module.exports = class Parser {
  4. constructor(tokenStream) {
  5. this.tokenStream = tokenStream
  6. }
  7. parse() {
  8. let tree = []
  9. while (this.tokenStream.peek().type !== tokenTypes.EOF) {
  10. tree.push(this.expr())
  11. }
  12. return tree
  13. }
  14. expr() {
  15. let node
  16. this.tokenStream.eat(tokenTypes.OPAREN)
  17. while (
  18. this.tokenStream.peek().type !== tokenTypes.CPAREN &&
  19. this.tokenStream.peek().type !== tokenTypes.EOF
  20. ) {
  21. if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  22. node = this.element()
  23. } else if (this.tokenStream.peek().type === tokenTypes.KEYWORD) {
  24. node = this.keyword()
  25. }
  26. }
  27. this.tokenStream.eat(tokenTypes.CPAREN)
  28. return node
  29. }
  30. element() {
  31. let elementNode = new Node({
  32. type: 'functionCall',
  33. args: [],
  34. subtree: [],
  35. functionName: this.tokenStream.eat(tokenTypes.LITERAL).value
  36. })
  37. while (![tokenTypes.CPAREN, tokenTypes.EOF].includes(this.tokenStream.peek().type)) {
  38. if (this.tokenStream.peek().type === tokenTypes.ATTRIBUTE) {
  39. elementNode.args.push(this.attribute())
  40. } else if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
  41. elementNode.subtree.push(this.quotedString())
  42. } else if (this.tokenStream.peek().type === tokenTypes.OPAREN) {
  43. elementNode.subtree.push(this.expr())
  44. } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  45. elementNode.subtree.push(this.identifier())
  46. }
  47. }
  48. return elementNode
  49. }
  50. attribute() {
  51. let attributeNode = new Node()
  52. attributeNode.attributeName = this.tokenStream.eat(
  53. tokenTypes.ATTRIBUTE,
  54. ).value
  55. if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
  56. attributeNode.attributeValue = this.quotedString()
  57. } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  58. attributeNode.attributeValue = this.identifier()
  59. }
  60. return attributeNode
  61. }
  62. identifier() {
  63. const identifier = this.tokenStream.eat(tokenTypes.LITERAL)
  64. return new Node({
  65. type: 'identifier',
  66. name: identifier.value,
  67. })
  68. }
  69. quotedString() {
  70. return new Node({
  71. type: 'string',
  72. content: this.string(),
  73. })
  74. }
  75. keyword() {
  76. const keyword = this.tokenStream.eat(tokenTypes.KEYWORD)
  77. if (keyword.value === 'each') {
  78. const subject = this.identifier()
  79. const symbol = this.tokenStream.eat(tokenTypes.SYMBOL)
  80. const body = this.expr()
  81. return new Node({
  82. type: 'each',
  83. symbol: symbol,
  84. body: body,
  85. subject: subject,
  86. })
  87. }
  88. }
  89. string() {
  90. this.tokenStream.eat(tokenTypes.QUOTE)
  91. let stringValue = this.tokenStream.eat(tokenTypes.LITERAL).value
  92. this.tokenStream.eat(tokenTypes.QUOTE)
  93. return stringValue
  94. }
  95. }