Class: Rich::SQLLexer

Inherits:
BaseLexer show all
Defined in:
lib/rich/syntax.rb

Overview

SQL Lexer

Constant Summary collapse

KEYWORDS =
%w[
  SELECT FROM WHERE AND OR NOT NULL IS IN LIKE BETWEEN EXISTS
  INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE DROP ALTER
  INDEX VIEW TRIGGER PROCEDURE FUNCTION AS ON JOIN LEFT RIGHT
  INNER OUTER FULL CROSS NATURAL USING ORDER BY ASC DESC GROUP
  HAVING LIMIT OFFSET UNION ALL DISTINCT CASE WHEN THEN ELSE END
  IF BEGIN COMMIT ROLLBACK TRANSACTION PRIMARY KEY FOREIGN
  REFERENCES UNIQUE DEFAULT CHECK CONSTRAINT CASCADE RESTRICT
  TRUE FALSE GRANT REVOKE WITH RECURSIVE
].freeze
BUILTINS =
%w[
  COUNT SUM AVG MIN MAX LENGTH UPPER LOWER TRIM CONCAT SUBSTRING
  REPLACE COALESCE NULLIF CAST CONVERT DATE TIME DATETIME
  YEAR MONTH DAY HOUR MINUTE SECOND NOW CURRENT_DATE
  CURRENT_TIME CURRENT_TIMESTAMP ABS ROUND FLOOR CEILING
  POWER SQRT MOD ROW_NUMBER RANK DENSE_RANK OVER PARTITION
].freeze
TYPES =
%w[
  INT INTEGER BIGINT SMALLINT TINYINT FLOAT DOUBLE DECIMAL
  NUMERIC REAL CHAR VARCHAR TEXT NCHAR NVARCHAR NTEXT
  DATE TIME DATETIME TIMESTAMP BOOLEAN BOOL BLOB BINARY
  VARBINARY UUID JSON XML
].freeze

Instance Method Summary collapse

Instance Method Details

#tokenize(line, theme) ⇒ Object



857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
# File 'lib/rich/syntax.rb', line 857

def tokenize(line, theme)
  segments = []
  pos = 0

  while pos < line.length
    if line[pos].match?(/\s/)
      ws_end = pos
      ws_end += 1 while ws_end < line.length && line[ws_end].match?(/\s/)
      segments << Segment.new(line[pos...ws_end])
      pos = ws_end
      next
    end

    # Comment
    if line[pos..pos + 1] == "--"
      segments << Segment.new(line[pos..], style: theme[:comment])
      break
    end

    # String
    if line[pos] == "'"
      str_end = pos + 1
      str_end += 1 while str_end < line.length && line[str_end] != "'"
      str_end = [str_end, line.length - 1].min
      segments << Segment.new(line[pos..str_end], style: theme[:string])
      pos = str_end + 1
      next
    end

    # Number
    if line[pos].match?(/\d/)
      num_end = pos
      num_end += 1 while num_end < line.length && line[num_end].match?(/[\d.]/)
      segments << Segment.new(line[pos...num_end], style: theme[:number])
      pos = num_end
      next
    end

    # Identifier
    if line[pos].match?(/[a-zA-Z_]/)
      word_end = pos
      word_end += 1 while word_end < line.length && line[word_end].match?(/\w/)
      word = line[pos...word_end]
      upper_word = word.upcase

      style = if KEYWORDS.include?(upper_word)
                theme[:keyword]
              elsif BUILTINS.include?(upper_word)
                theme[:name_builtin] || theme[:name]
              elsif TYPES.include?(upper_word)
                theme[:keyword_type] || theme[:keyword]
              else
                theme[:name]
              end

      segments << Segment.new(word, style: style)
      pos = word_end
      next
    end

    # Operators
    if line[pos].match?(/[+\-*\/%<>=!]/)
      op_end = pos + 1
      op_end += 1 while op_end < line.length && line[op_end].match?(/[+\-*\/%<>=!]/)
      segments << Segment.new(line[pos...op_end], style: theme[:operator])
      pos = op_end
      next
    end

    # Punctuation
    if line[pos].match?(/[(),;.]/)
      segments << Segment.new(line[pos], style: theme[:punctuation])
      pos += 1
      next
    end

    segments << Segment.new(line[pos])
    pos += 1
  end

  segments
end