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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  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. this.tokenStream.eat(tokenTypes.OPAREN)
  16. let elementNode = new Node()
  17. elementNode.type = 'functionCall'
  18. elementNode.functionName = this.tokenStream.eat(tokenTypes.LITERAL).value
  19. elementNode.args = []
  20. elementNode.subtree = []
  21. while (
  22. this.tokenStream.peek().type != tokenTypes.CPAREN &&
  23. this.tokenStream.peek().type !== tokenTypes.EOF
  24. ) {
  25. if (this.tokenStream.peek().type === tokenTypes.ATTRIBUTE) {
  26. elementNode.args.push(this.attribute())
  27. } else if (this.tokenStream.peek().type === tokenTypes.OPAREN) {
  28. elementNode.subtree.push(this.expr())
  29. } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  30. elementNode.subtree.push(this.identifier())
  31. } else if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
  32. elementNode.subtree.push(this.quotedString())
  33. } else if (this.tokenStream.peek().type === tokenTypes.KEYWORD) {
  34. elementNode.subtree.push(this.keyword())
  35. }
  36. }
  37. this.tokenStream.eat(tokenTypes.CPAREN)
  38. return elementNode
  39. }
  40. attribute() {
  41. let attributeNode = new Node()
  42. attributeNode.attributeName = this.tokenStream.eat(
  43. tokenTypes.ATTRIBUTE,
  44. ).value
  45. if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
  46. attributeNode.attributeValue = this.quotedString()
  47. } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  48. attributeNode.attributeValue = this.identifier()
  49. }
  50. return attributeNode
  51. }
  52. identifier() {
  53. const identifier = this.tokenStream.eat(tokenTypes.LITERAL)
  54. return new Node({
  55. type: 'identifier',
  56. name: identifier.value,
  57. })
  58. }
  59. quotedString() {
  60. return new Node({
  61. type: 'string',
  62. content: this.string(),
  63. })
  64. }
  65. keyword() {
  66. const keyword = this.tokenStream.eat(tokenTypes.KEYWORD)
  67. if (keyword.value === 'each') {
  68. const subject = this.identifier()
  69. const symbol = this.tokenStream.eat(tokenTypes.SYMBOL)
  70. const body = this.expr()
  71. return new Node({
  72. type: 'each',
  73. symbol: symbol,
  74. body: body,
  75. subject: subject,
  76. })
  77. }
  78. }
  79. string() {
  80. this.tokenStream.eat(tokenTypes.QUOTE)
  81. let stringValue = this.tokenStream.eat(tokenTypes.LITERAL).value
  82. this.tokenStream.eat(tokenTypes.QUOTE)
  83. return stringValue
  84. }
  85. }