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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. const AST = require('./ast')
  2. const Error = require('./Error')
  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. let expr = this.expr()
  12. tree.push(expr)
  13. if (this.tokenStream.error) {
  14. return {
  15. error: this.tokenStream.error,
  16. }
  17. }
  18. }
  19. return tree
  20. }
  21. expr() {
  22. let token = this.tokenStream.peek()
  23. switch (token.type) {
  24. case tokenTypes.ATTRIBUTE:
  25. return this.attribute()
  26. case tokenTypes.BOOLEAN:
  27. return this.bool()
  28. case tokenTypes.IDENTIFIER:
  29. return this.identifier()
  30. case tokenTypes.NUMBER:
  31. return this.number()
  32. case tokenTypes.QUOTE:
  33. return this.string()
  34. case tokenTypes.SYMBOL:
  35. return this.symbol()
  36. case tokenTypes.OPAREN:
  37. return this.form()
  38. default:
  39. this.tokenStream.error = `Unexpected ${token.type} on line ${
  40. token.line
  41. }`
  42. break
  43. }
  44. }
  45. form() {
  46. this.tokenStream.eat(tokenTypes.OPAREN)
  47. let node
  48. if (this.tokenStream.peek().value === 'define') {
  49. this.tokenStream.eat(tokenTypes.IDENTIFIER)
  50. node = this.functionDefinition()
  51. } else if (this.tokenStream.peek().value === 'lambda') {
  52. this.tokenStream.eat(tokenTypes.IDENTIFIER)
  53. node = this.functionDefinition(true)
  54. } else {
  55. node = new AST.Application()
  56. node.function = this.expr()
  57. node.args = []
  58. while (this.tokenStream.peek().type !== tokenTypes.CPAREN) {
  59. node.args.push(this.expr())
  60. }
  61. }
  62. this.tokenStream.eat(tokenTypes.CPAREN)
  63. return node
  64. }
  65. attribute() {
  66. return new AST.Attribute({
  67. name: this.tokenStream.eat(tokenTypes.ATTRIBUTE).value,
  68. value: this.expr(),
  69. })
  70. }
  71. bool() {
  72. return new AST.Boolean({
  73. value: this.tokenStream.eat(tokenTypes.BOOLEAN).value,
  74. })
  75. }
  76. functionDefinition(anonymous = false) {
  77. let name
  78. if (anonymous) {
  79. name = false
  80. } else {
  81. name = new AST.Identifier({
  82. name: this.tokenStream.eat(tokenTypes.IDENTIFIER).value,
  83. })
  84. }
  85. let parameters = []
  86. this.tokenStream.eat(tokenTypes.OPAREN)
  87. while (this.tokenStream.peek().type !== tokenTypes.CPAREN) {
  88. parameters.push(
  89. new AST.Identifier({
  90. name: this.tokenStream.eat(tokenTypes.IDENTIFIER).value,
  91. }),
  92. )
  93. if (this.tokenStream.peek().type === tokenTypes.COMMA) {
  94. this.tokenStream.eat(tokenTypes.COMMA)
  95. }
  96. }
  97. this.tokenStream.eat(tokenTypes.CPAREN)
  98. return new AST.FunctionDefinition({
  99. name: name,
  100. parameters: parameters,
  101. body: this.form(),
  102. })
  103. }
  104. identifier() {
  105. return new AST.Identifier({
  106. name: this.tokenStream.eat(tokenTypes.IDENTIFIER).value,
  107. })
  108. }
  109. number() {
  110. return new AST.Number({
  111. value: this.tokenStream.eat(tokenTypes.NUMBER).value,
  112. })
  113. }
  114. string() {
  115. this.tokenStream.eat(tokenTypes.QUOTE)
  116. let value = ''
  117. if (this.tokenStream.peek().type === tokenTypes.LITERAL) {
  118. value = this.tokenStream.eat(tokenTypes.LITERAL).value
  119. }
  120. let node = new AST.String({
  121. value: value,
  122. })
  123. this.tokenStream.eat(tokenTypes.QUOTE)
  124. return node
  125. }
  126. symbol() {
  127. return new AST.Symbol({
  128. value: this.tokenStream.eat(tokenTypes.SYMBOL).value,
  129. })
  130. }
  131. }