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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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(/\d/)) {
  46. let number = ''
  47. while (source[pos] && source[pos].match(/\d/)) {
  48. number += source[pos]
  49. pos++
  50. }
  51. tokenStream.tokens.push({
  52. type: tokenTypes.NUMBER,
  53. line: line,
  54. value: parseFloat(number),
  55. })
  56. } else if (source.slice(pos).match(/^#(t|f)/)) {
  57. pos++
  58. tokenStream.tokens.push({
  59. type: tokenTypes.BOOLEAN,
  60. line: line,
  61. value: source[pos] === 't' ? true : false,
  62. })
  63. pos++
  64. } else if (source[pos].match(/\n/)) {
  65. line++
  66. pos++
  67. } else if (source[pos].match(/\s/)) {
  68. pos++
  69. } else {
  70. let endPattern = /[^()"':\s]+/
  71. if (allowSpecialCharactersInLiterals) {
  72. endPattern = /[^"']+/
  73. }
  74. let value = endPattern.exec(source.slice(pos))[0].trim()
  75. if (allowSpecialCharactersInLiterals) {
  76. tokenStream.tokens.push({
  77. type: tokenTypes.LITERAL,
  78. line: line,
  79. value: value,
  80. })
  81. } else {
  82. tokenStream.tokens.push({
  83. type: tokenTypes.IDENTIFIER,
  84. line: line,
  85. value: value.trim(),
  86. })
  87. }
  88. pos += value.length
  89. }
  90. }
  91. tokenStream.tokens.push({
  92. type: tokenTypes.EOF,
  93. line: line,
  94. })
  95. return tokenStream
  96. }
  97. }