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 3.3KB

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