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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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 (
  38. ![tokenTypes.CPAREN, tokenTypes.EOF].includes(
  39. this.tokenStream.peek().type,
  40. )
  41. ) {
  42. if (this.tokenStream.peek().type === tokenTypes.ATTRIBUTE) {
  43. elementNode.args.push(this.attribute())
  44. } else if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
  45. elementNode.subtree.push(this.quotedString())
  46. } else if (this.tokenStream.peek().type === tokenTypes.OPAREN) {
  47. elementNode.subtree.push(this.expr())
  48. } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  49. elementNode.subtree.push(this.identifier())
  50. }
  51. }
  52. return elementNode
  53. }
  54. attribute() {
  55. let attributeNode = new Node()
  56. attributeNode.attributeName = this.tokenStream.eat(
  57. tokenTypes.ATTRIBUTE,
  58. ).value
  59. if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
  60. attributeNode.attributeValue = this.quotedString()
  61. } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  62. attributeNode.attributeValue = this.identifier()
  63. }
  64. return attributeNode
  65. }
  66. identifier() {
  67. const identifier = this.tokenStream.eat(tokenTypes.LITERAL)
  68. return new Node({
  69. type: 'identifier',
  70. name: identifier.value,
  71. })
  72. }
  73. quotedString() {
  74. return new Node({
  75. type: 'string',
  76. content: this.string(),
  77. })
  78. }
  79. keyword() {
  80. const keyword = this.tokenStream.eat(tokenTypes.KEYWORD)
  81. if (keyword.value === 'each') {
  82. const subject = this.identifier()
  83. const symbol = this.tokenStream.eat(tokenTypes.SYMBOL)
  84. const body = this.expr()
  85. return new Node({
  86. type: 'each',
  87. symbol: symbol,
  88. body: body,
  89. subject: subject,
  90. })
  91. }
  92. }
  93. string() {
  94. this.tokenStream.eat(tokenTypes.QUOTE)
  95. let stringValue = this.tokenStream.eat(tokenTypes.LITERAL).value
  96. this.tokenStream.eat(tokenTypes.QUOTE)
  97. return stringValue
  98. }
  99. }