A stylesheet language written in TypeScript that compiles to CSS
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { Error } from './error';
  2. import Token, { TokenTypes } from './token';
  3. export interface LexerError {}
  4. export class LexerError implements Error {
  5. public line: number;
  6. public message: string;
  7. public module?: string;
  8. public constructor(line: number, message: string, module?: string) {
  9. this.line = line;
  10. this.message = message;
  11. this.module = module;
  12. }
  13. }
  14. export type LexerResult = { tokens: Token[]; module?: string } | LexerError;
  15. export default class Lexer {
  16. private source: string;
  17. private module?: string;
  18. private line: number;
  19. private position: number;
  20. private tokens: Token[];
  21. private allowWhitespace: boolean;
  22. public constructor(source: string, module?: string) {
  23. this.source = source;
  24. this.module = module;
  25. this.line = 1;
  26. this.position = 0;
  27. this.tokens = [];
  28. this.allowWhitespace = true;
  29. }
  30. public scan(): LexerResult {
  31. while (this.position < this.source.length) {
  32. const result = this.getToken();
  33. if (!(result instanceof Token)) return result;
  34. if (result.type !== TokenTypes.WHITESPACE && result.type !== TokenTypes.COMMENT) {
  35. this.tokens.push(result);
  36. }
  37. if (result.type !== TokenTypes.LITERAL) {
  38. this.position += result.value.length;
  39. }
  40. if (this.hasSigil(result)) {
  41. this.position += 1;
  42. }
  43. }
  44. this.tokens.push(this.token(TokenTypes.EOF, 'eof'));
  45. return {
  46. tokens: this.tokens,
  47. module: this.module,
  48. };
  49. }
  50. private getToken(): Token | LexerError {
  51. const c = this.currentChar();
  52. const source = this.source.slice(this.position);
  53. if (c === '(') {
  54. this.allowWhitespace = true;
  55. return this.token(TokenTypes.LPAREN, '(');
  56. } else if (c === ')') {
  57. this.allowWhitespace = true;
  58. return this.token(TokenTypes.RPAREN, ')');
  59. } else if (c === ',') {
  60. return this.token(TokenTypes.COMMA, ',');
  61. } else if (c === ':') {
  62. const match = source.match(/^:([a-z][a-zA-Z0-9_-]*)/);
  63. if (match === null) {
  64. return this.error(
  65. `Unexpected character ${this.source[this.position + 1]}`
  66. );
  67. }
  68. return this.token(TokenTypes.PROPERTY, match[1]);
  69. } else if (c === '$') {
  70. const match = source.match(/^\$([a-z][a-zA-Z0-9_-]*)/);
  71. if (match === null) {
  72. return this.error(
  73. `Unexpected character ${this.source[this.position + 1]}`
  74. );
  75. }
  76. return this.token(TokenTypes.IDENTIFIER, match[1]);
  77. } else if (c === '@') {
  78. this.allowWhitespace = false;
  79. const match = source.match(/^\@([a-z][a-zA-Z0-9_-]*)/);
  80. if (match === null) {
  81. return this.error(
  82. `Unexpected character ${this.source[this.position + 1]}`
  83. );
  84. }
  85. return this.token(TokenTypes.FUNCTION_NAME, match[1]);
  86. } else if (c.match(/^\s/)) {
  87. if (c === '\n') this.line += 1;
  88. return this.token(TokenTypes.WHITESPACE, c);
  89. } else if (c === ';') {
  90. while (this.currentChar() !== '\n') {
  91. this.position++;
  92. }
  93. return this.token(TokenTypes.COMMENT, '');
  94. } else {
  95. let literal = '';
  96. const endPattern = this.allowWhitespace ? /^[\(\)\n\,]/ : /^[\(\)\n\, ]/;
  97. while (
  98. this.position < this.source.length &&
  99. !this.source.slice(this.position).match(/^ [\:\$][a-z]/) &&
  100. !this.source.slice(this.position).match(endPattern) &&
  101. !(this.currentChar() === ' ' && this.nextChar() === '(')
  102. ) {
  103. literal += this.currentChar();
  104. this.position += 1;
  105. }
  106. return this.token(TokenTypes.LITERAL, literal);
  107. }
  108. return this.error(`Unexpected character ${c}`);
  109. }
  110. private token(type: TokenTypes, value: string): Token {
  111. return new Token(type, value, this.line, this.module);
  112. }
  113. private error(message: string): LexerError {
  114. return new LexerError(this.line, message, this.module);
  115. }
  116. private currentChar(): string {
  117. return this.source[this.position];
  118. }
  119. private nextChar(): string {
  120. return this.source[this.position + 1];
  121. }
  122. private hasSigil(token: Token): boolean {
  123. return [
  124. TokenTypes.IDENTIFIER,
  125. TokenTypes.PROPERTY,
  126. TokenTypes.FUNCTION_NAME,
  127. ].includes(token.type);
  128. }
  129. }