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.

lexer.js 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. const TokenStream = require('./tokenStream')
  2. const tokenTypes = require('./tokenTypes')
  3. module.exports = class Lexer {
  4. scan(source) {
  5. let pos = 0
  6. let line = 1
  7. let tokenStream = new TokenStream()
  8. let allowSpecialCharactersInLiterals = false
  9. while (pos < source.length) {
  10. if (source[pos].match(/\(/) && !allowSpecialCharactersInLiterals) {
  11. tokenStream.tokens.push({
  12. type: tokenTypes.OPAREN,
  13. line: line,
  14. })
  15. pos++
  16. } else if (source[pos].match(/\)/)) {
  17. tokenStream.tokens.push({
  18. type: tokenTypes.CPAREN,
  19. line: line,
  20. })
  21. pos++
  22. } else if (source[pos].match(/["]/)) {
  23. allowSpecialCharactersInLiterals = !allowSpecialCharactersInLiterals
  24. tokenStream.tokens.push({
  25. type: tokenTypes.QUOTE,
  26. line: line,
  27. })
  28. pos++
  29. } else if (source[pos].match(/:/)) {
  30. let value = /:([^()'"\s]+)/.exec(source.slice(pos))[1].trim()
  31. tokenStream.tokens.push({
  32. type: tokenTypes.ATTRIBUTE,
  33. line: line,
  34. value: value,
  35. })
  36. pos += value.length + 1 // the +1 is to account for the colon
  37. } else if (source[pos].match(/\'/)) {
  38. let value = /'([^()"\s]+)/.exec(source.slice(pos))[1].trim()
  39. tokenStream.tokens.push({
  40. type: tokenTypes.SYMBOL,
  41. line: line,
  42. value: value,
  43. })
  44. pos += value.length + 1 // the +1 is to account for the apostrophe
  45. } else if (source[pos].match(/\,/)) {
  46. tokenStream.tokens.push({
  47. type: tokenTypes.COMMA,
  48. line: line,
  49. value: value,
  50. })
  51. pos += 1
  52. } else if (source[pos].match(/\d/)) {
  53. let number = ''
  54. while (source[pos] && source[pos].match(/\d/)) {
  55. number += source[pos]
  56. pos++
  57. }
  58. tokenStream.tokens.push({
  59. type: tokenTypes.NUMBER,
  60. line: line,
  61. value: parseFloat(number),
  62. })
  63. } else if (source.slice(pos).match(/^#(t|f)/)) {
  64. pos++
  65. tokenStream.tokens.push({
  66. type: tokenTypes.BOOLEAN,
  67. line: line,
  68. value: source[pos] === 't' ? true : false,
  69. })
  70. pos++
  71. } else if (source[pos].match(/\n/)) {
  72. line++
  73. pos++
  74. } else if (source[pos].match(/\s/)) {
  75. pos++
  76. } else {
  77. let endPattern = /[^()"':\s]+/
  78. if (allowSpecialCharactersInLiterals) {
  79. endPattern = /[^"']+/
  80. }
  81. let value = endPattern.exec(source.slice(pos))[0].trim()
  82. if (allowSpecialCharactersInLiterals) {
  83. tokenStream.tokens.push({
  84. type: tokenTypes.LITERAL,
  85. line: line,
  86. value: value,
  87. })
  88. } else {
  89. tokenStream.tokens.push({
  90. type: tokenTypes.IDENTIFIER,
  91. line: line,
  92. value: value.trim(),
  93. })
  94. }
  95. pos += value.length
  96. }
  97. }
  98. tokenStream.tokens.push({
  99. type: tokenTypes.EOF,
  100. line: line,
  101. })
  102. return tokenStream
  103. }
  104. }