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

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